diff --git a/src/SFA.DAS.AssessorService.Api.Types/Models/Apply/GetApplicationRequest.cs b/src/SFA.DAS.AssessorService.Api.Types/Models/Apply/GetApplicationRequest.cs index 7141f1c520..1e70673ac0 100644 --- a/src/SFA.DAS.AssessorService.Api.Types/Models/Apply/GetApplicationRequest.cs +++ b/src/SFA.DAS.AssessorService.Api.Types/Models/Apply/GetApplicationRequest.cs @@ -1,17 +1,17 @@ using MediatR; using System; -using System.Collections.Generic; -using System.Text; namespace SFA.DAS.AssessorService.Api.Types.Models.Apply { public class GetApplicationRequest : IRequest { public Guid ApplicationId { get; } + public Guid? UserId { get; } - public GetApplicationRequest(Guid applicationId) + public GetApplicationRequest(Guid applicationId, Guid? userId = null) { ApplicationId = applicationId; + UserId = userId; } } } diff --git a/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/ApplicationApiClient.cs b/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/ApplicationApiClient.cs index ac1bd03e2d..ae6d358adc 100644 --- a/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/ApplicationApiClient.cs +++ b/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/ApplicationApiClient.cs @@ -65,7 +65,15 @@ public async Task GetApplication(Guid id) return await RequestAndDeserialiseAsync(request, $"Could not retrieve applications"); } } - + + public async Task GetApplicationForUser(Guid id, Guid userId) + { + using (var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/applications/user/{userId}/application/{id}")) + { + return await RequestAndDeserialiseAsync(request, $"Could not retrieve application {id} for user {userId}"); + } + } + public async Task CreateApplication(CreateApplicationRequest createApplicationRequest) { using (var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/applications/createApplication")) diff --git a/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/IApplicationApiClient.cs b/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/IApplicationApiClient.cs index 62c2fe748c..cf566f994f 100644 --- a/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/IApplicationApiClient.cs +++ b/src/SFA.DAS.AssessorService.Application.Api.Client/Clients/IApplicationApiClient.cs @@ -15,6 +15,7 @@ public interface IApplicationApiClient Task> GetOrganisationWithdrawalApplications(Guid userId); Task> GetStandardWithdrawalApplications(Guid userId); Task GetApplication(Guid id); + Task GetApplicationForUser(Guid id, Guid userId); Task CreateApplication(CreateApplicationRequest createApplicationRequest); Task SubmitApplicationSequence(SubmitApplicationSequenceRequest submitApplicationRequest); diff --git a/src/SFA.DAS.AssessorService.Application.Api/Controllers/Apply/ApplicationController.cs b/src/SFA.DAS.AssessorService.Application.Api/Controllers/Apply/ApplicationController.cs index 20361adf7d..b671ea77b7 100644 --- a/src/SFA.DAS.AssessorService.Application.Api/Controllers/Apply/ApplicationController.cs +++ b/src/SFA.DAS.AssessorService.Application.Api/Controllers/Apply/ApplicationController.cs @@ -73,13 +73,22 @@ public async Task>> GetStandardWithdrawal return Ok(await _mediator.Send(new GetApplicationsRequest(Guid.Parse(userId), ApplicationTypes.StandardWithdrawal))); } - [HttpGet("{Id}/application", Name = "GetApplication")] + [HttpGet("{id}/application", Name = "GetApplication")] [SwaggerResponse((int)HttpStatusCode.OK, Type = typeof(ApplicationResponse))] [SwaggerResponse((int)HttpStatusCode.InternalServerError, Type = typeof(ApiResponse))] - public async Task> GetApplication(string Id) + public async Task> GetApplication(string id) { - _logger.LogInformation($"Received request to retrieve application for ApplicationId {Id}"); - return Ok(await _mediator.Send(new GetApplicationRequest(Guid.Parse(Id)))); + _logger.LogInformation($"Received request to retrieve application with ApplicationId {id}"); + return Ok(await _mediator.Send(new GetApplicationRequest(Guid.Parse(id)))); + } + + [HttpGet("user/{userId}/application/{id}", Name = "GetApplicationForUser")] + [SwaggerResponse((int)HttpStatusCode.OK, Type = typeof(ApplicationResponse))] + [SwaggerResponse((int)HttpStatusCode.InternalServerError, Type = typeof(ApiResponse))] + public async Task> GetApplicationForUser(string userId, string id) + { + _logger.LogInformation($"Received request to retrieve application with ApplicationId {id} for UserId {userId}"); + return Ok(await _mediator.Send(new GetApplicationRequest(Guid.Parse(id), Guid.Parse(userId)))); } [HttpPost("createApplication", Name = "CreateApplication")] diff --git a/src/SFA.DAS.AssessorService.Application/Handlers/Apply/GetApplicationHandler.cs b/src/SFA.DAS.AssessorService.Application/Handlers/Apply/GetApplicationHandler.cs index 1e8a08c343..b40e824a8f 100644 --- a/src/SFA.DAS.AssessorService.Application/Handlers/Apply/GetApplicationHandler.cs +++ b/src/SFA.DAS.AssessorService.Application/Handlers/Apply/GetApplicationHandler.cs @@ -19,7 +19,7 @@ public GetApplicationHandler(IApplyRepository applyRepository) public async Task Handle(GetApplicationRequest request, CancellationToken cancellationToken) { - var result = await _applyRepository.GetApplication(request.ApplicationId); + var result = await _applyRepository.GetApplication(request.ApplicationId, request.UserId); return Mapper.Map(result); } } diff --git a/src/SFA.DAS.AssessorService.Application/Interfaces/IApplyRepository.cs b/src/SFA.DAS.AssessorService.Application/Interfaces/IApplyRepository.cs index 4c743ebe24..444d575235 100644 --- a/src/SFA.DAS.AssessorService.Application/Interfaces/IApplyRepository.cs +++ b/src/SFA.DAS.AssessorService.Application/Interfaces/IApplyRepository.cs @@ -10,7 +10,7 @@ namespace SFA.DAS.AssessorService.Application.Interfaces public interface IApplyRepository { Task GetApply(Guid applicationId); - Task GetApplication(Guid applicationId); + Task GetApplication(Guid applicationId, Guid? userId); Task> GetOrganisationApplications(Guid userId); Task> GetStandardApplications(Guid userId); Task> GetWithdrawalApplications(Guid userId); diff --git a/src/SFA.DAS.AssessorService.Application/Mapping/AutoMapperProfiles/ApplicationResponseProfile.cs b/src/SFA.DAS.AssessorService.Application/Mapping/AutoMapperProfiles/ApplicationResponseProfile.cs index 46e85bf0ca..c079b55536 100644 --- a/src/SFA.DAS.AssessorService.Application/Mapping/AutoMapperProfiles/ApplicationResponseProfile.cs +++ b/src/SFA.DAS.AssessorService.Application/Mapping/AutoMapperProfiles/ApplicationResponseProfile.cs @@ -22,8 +22,8 @@ public ApplicationResponseProfile() .ForMember(dest => dest.ApplyData, opt => opt.MapFrom(source => source.ApplyData)) .ForMember(dest => dest.StandardCode, opt => opt.MapFrom(source => source.StandardCode)) .ForMember(dest => dest.CreatedBy, opt => opt.MapFrom(source => source.CreatedBy)) - .ForMember(dest => dest.ContactName, opt => opt.MapFrom(source => source.ContactName)) - .ForMember(dest => dest.ContactEmail, opt => opt.MapFrom(source => source.ContactEmail)) + .ForMember(dest => dest.ContactName, opt => opt.MapFrom(source => source.CreatedByName)) + .ForMember(dest => dest.ContactEmail, opt => opt.MapFrom(source => source.CreatedByEmail)) .ForMember(dest => dest.ApplicationType, opts => { opts.ResolveUsing(); }) .ForAllOtherMembers(dest => dest.Ignore()); } diff --git a/src/SFA.DAS.AssessorService.Data/Apply/ApplyRepository.cs b/src/SFA.DAS.AssessorService.Data/Apply/ApplyRepository.cs index 9f8ae55939..aaa7e135be 100644 --- a/src/SFA.DAS.AssessorService.Data/Apply/ApplyRepository.cs +++ b/src/SFA.DAS.AssessorService.Data/Apply/ApplyRepository.cs @@ -36,33 +36,51 @@ public ApplyRepository(IUnitOfWork unitOfWork, ILogger logger) transaction: _unitOfWork.Transaction); } - public async Task GetApplication(Guid applicationId) + public async Task GetApplication(Guid applicationId, Guid? userId) { + var query = @"SELECT + a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, + a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, + a.StandardCode, a.CreatedBy, a.UpdatedBy, a.DeletedBy, + o.EndPointAssessorName, c1.DisplayName [CreatedByName] , c1.Email [CreatedByEmail] + FROM Contacts c + INNER JOIN Apply a ON a.OrganisationId = c.OrganisationId + INNER JOIN Organisations o ON a.OrganisationId = o.Id + INNER JOIN Contacts c1 ON c1.Id = a.CreatedBy + WHERE + a.Id = @applicationId + AND (c.Id = @userId OR @userId IS NULL) + GROUP BY + a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, + a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, + a.StandardCode, a.CreatedAt, a.CreatedBy, a.UpdatedAt, a.UpdatedBy, a.DeletedAt, a.DeletedBy, + o.EndPointAssessorName, c1.DisplayName, c1.Email"; + return await _unitOfWork.Connection.QuerySingleOrDefaultAsync( - @"SELECT a.*, o.EndPointAssessorName, c.DisplayName [ContactName] , c.Email [ContactEmail] FROM Apply a - LEFT JOIN Organisations o ON a.OrganisationId = o.Id - LEFT JOIN Contacts c ON c.Id = a.CreatedBy - WHERE a.Id = @applicationId", - param: new { applicationId }, + sql: query, + param: new { applicationId, userId }, transaction: _unitOfWork.Transaction); } public async Task> GetApplications(Guid userId, int[] sequenceNos) { - string query = @"SELECT a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, - a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, - a.StandardCode, a.CreatedAt, a.CreatedBy, a.UpdatedAt, a.UpdatedBy, a.DeletedAt, a.DeletedBy, o.EndPointAssessorName - FROM Contacts c - INNER JOIN Apply a ON a.OrganisationId = c.OrganisationId - LEFT JOIN Organisations o ON a.OrganisationId = o.Id - CROSS APPLY OPENJSON(ApplyData,'$.Sequences') WITH (SequenceNo INT, NotRequired BIT) sequence - WHERE c.Id = @userId - AND sequence.SequenceNo IN @sequenceNos AND sequence.NotRequired = 0 - GROUP BY - a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, - a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, - a.StandardCode, a.CreatedAt, a.CreatedBy, a.UpdatedAt, a.UpdatedBy, a.DeletedAt, a.DeletedBy, - o.EndPointAssessorName"; + var query = @"SELECT + a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, + a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, + a.StandardCode, a.CreatedBy, a.UpdatedBy, a.DeletedBy, + o.EndPointAssessorName, c1.DisplayName [CreatedByName] , c1.Email [CreatedByEmail] + FROM Contacts c + INNER JOIN Apply a ON a.OrganisationId = c.OrganisationId + INNER JOIN Organisations o ON a.OrganisationId = o.Id + INNER JOIN Contacts c1 ON c1.Id = a.CreatedBy + CROSS APPLY OPENJSON(ApplyData,'$.Sequences') WITH (SequenceNo INT, NotRequired BIT) sequence + WHERE c.Id = @userId + AND sequence.SequenceNo IN @sequenceNos AND sequence.NotRequired = 0 + GROUP BY + a.Id, a.ApplicationId, a.OrganisationId, a.ApplicationStatus, a.ReviewStatus, + a.ApplyData, a.FinancialReviewStatus, a.FinancialGrade, + a.StandardCode, a.CreatedAt, a.CreatedBy, a.UpdatedAt, a.UpdatedBy, a.DeletedAt, a.DeletedBy, + o.EndPointAssessorName, c1.DisplayName, c1.Email"; return (await _unitOfWork.Connection.QueryAsync( sql: query, diff --git a/src/SFA.DAS.AssessorService.Domain/DTOs/ApplySummary.cs b/src/SFA.DAS.AssessorService.Domain/DTOs/ApplySummary.cs index dde2067eb8..33b92a593b 100644 --- a/src/SFA.DAS.AssessorService.Domain/DTOs/ApplySummary.cs +++ b/src/SFA.DAS.AssessorService.Domain/DTOs/ApplySummary.cs @@ -18,7 +18,7 @@ public class ApplySummary public string CreatedBy { get; set; } public string UpdatedBy { get; set; } public string DeletedBy { get; set; } - public string ContactName { get; set; } - public string ContactEmail { get; set; } + public string CreatedByName { get; set; } + public string CreatedByEmail { get; set; } } } diff --git a/src/SFA.DAS.AssessorService.Web/Controllers/AccountController.cs b/src/SFA.DAS.AssessorService.Web/Controllers/AccountController.cs index f10ed9148c..450b84caa1 100644 --- a/src/SFA.DAS.AssessorService.Web/Controllers/AccountController.cs +++ b/src/SFA.DAS.AssessorService.Web/Controllers/AccountController.cs @@ -114,7 +114,6 @@ public IActionResult SignOut() [HttpGet] public IActionResult SignedOut() { - if (User.Identity.IsAuthenticated) { // Redirect to home page if the user is authenticated. @@ -127,9 +126,11 @@ public IActionResult SignedOut() [HttpGet] public async Task AccessDenied() { - if (TempData.Keys.Contains("DeniedPrivilegeContext")) + if (TempData.Keys.Contains(nameof(PrivilegeAuthorizationDeniedContext))) { - var deniedPrivilegeContext = JsonConvert.DeserializeObject(TempData["DeniedPrivilegeContext"].ToString()); + var deniedContext = JsonConvert + .DeserializeObject(TempData[nameof(PrivilegeAuthorizationDeniedContext)] + .ToString()); var userId = Guid.Parse(User.FindFirst("UserId").Value); var user = await _contactsApiClient.GetById(userId); @@ -138,26 +139,28 @@ public async Task AccessDenied() { organisation = await _organisationsApiClient.GetOrganisationByUserId(userId); - }catch(Exception ex) + } + catch (Exception ex) { _logger.LogWarning(ex.Message, ex); - if(user.OrganisationId == null && user.Status == ContactStatus.Live) { - return RedirectToAction("Index","OrganisationSearch"); + if (user.OrganisationId == null && user.Status == ContactStatus.Live) + { + return RedirectToAction("Index", "OrganisationSearch"); } } - if(user.OrganisationId != null && user.Status == ContactStatus.InvitePending) + if (user.OrganisationId != null && user.Status == ContactStatus.InvitePending) { return RedirectToAction("InvitePending", "Home"); } - if(organisation != null && organisation.Status == OrganisationStatus.Applying || + if (organisation != null && organisation.Status == OrganisationStatus.Applying || organisation.Status == OrganisationStatus.New) { return RedirectToAction("Index", "Dashboard"); } - var privilege = (await _contactsApiClient.GetPrivileges()).Single(p => p.Id == deniedPrivilegeContext.PrivilegeId); + var privilege = (await _contactsApiClient.GetPrivileges()).Single(p => p.Id == deniedContext.PrivilegeId); var usersPrivileges = await _contactsApiClient.GetContactPrivileges(userId); @@ -165,11 +168,11 @@ public async Task AccessDenied() { Title = privilege.UserPrivilege, Rights = privilege.PrivilegeData.Rights, - PrivilegeId = deniedPrivilegeContext.PrivilegeId, + PrivilegeId = deniedContext.PrivilegeId, ContactId = userId, UserHasUserManagement = usersPrivileges.Any(up => up.Privilege.Key == Privileges.ManageUsers), - ReturnController = deniedPrivilegeContext.Controller, - ReturnAction = deniedPrivilegeContext.Action, + ReturnController = deniedContext.Controller, + ReturnAction = deniedContext.Action, IsUsersOrganisationLive = organisation?.Status == OrganisationStatus.Live }); } diff --git a/src/SFA.DAS.AssessorService.Web/Controllers/Apply/ApplicationController.cs b/src/SFA.DAS.AssessorService.Web/Controllers/Apply/ApplicationController.cs index 2a490ff60b..95f80eda01 100644 --- a/src/SFA.DAS.AssessorService.Web/Controllers/Apply/ApplicationController.cs +++ b/src/SFA.DAS.AssessorService.Web/Controllers/Apply/ApplicationController.cs @@ -14,7 +14,6 @@ using SFA.DAS.AssessorService.ApplyTypes.CompaniesHouse; using SFA.DAS.AssessorService.Domain.Consts; using SFA.DAS.AssessorService.Settings; -using SFA.DAS.AssessorService.Web.Extensions; using SFA.DAS.AssessorService.Web.Helpers; using SFA.DAS.AssessorService.Web.Infrastructure; using SFA.DAS.AssessorService.Web.StartupConfiguration; @@ -143,6 +142,7 @@ public async Task StartApplication() } [HttpGet("/Application/{Id}")] + [ApplicationAuthorize(routeId: "Id")] public async Task SequenceSignPost(Guid Id) { var userId = await GetUserId(); @@ -204,6 +204,7 @@ public async Task SequenceSignPost(Guid Id) } [HttpGet("/Application/{Id}/Sequence/{sequenceNo}")] + [ApplicationAuthorize(routeId: "Id")] public async Task Sequence(Guid Id, int sequenceNo) { var application = await _applicationApiClient.GetApplication(Id); @@ -234,6 +235,7 @@ public async Task Sequence(Guid Id, int sequenceNo) } [HttpGet("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}")] + [ApplicationAuthorize(routeId: "Id")] public async Task Section(Guid Id, int sequenceNo, int sectionNo) { var application = await _applicationApiClient.GetApplication(Id); @@ -263,6 +265,7 @@ public async Task Section(Guid Id, int sequenceNo, int sectionNo) } [HttpGet("/Application/{id}/Cancelled")] + [ApplicationAuthorize(routeId: "Id")] public IActionResult ApplicationCancelled(Guid id) { var standardWithReference = TempData["StandardWithReference"]?.ToString(); @@ -276,6 +279,7 @@ public IActionResult ApplicationCancelled(Guid id) [HttpGet("/Application/{id}/ConfirmCancel")] [ModelStatePersist(ModelStatePersist.RestoreEntry)] + [ApplicationAuthorize(routeId: "Id")] public async Task ConfirmCancelApplication(Guid id) { var application = await _applicationApiClient.GetApplication(id); @@ -300,6 +304,7 @@ public async Task ConfirmCancelApplication(Guid id) [HttpPost("/Application/{id}/ConfirmCancel")] [ModelStatePersist(ModelStatePersist.Store)] + [ApplicationAuthorize(routeId: "Id")] public async Task ConfirmCancelApplication(ConfirmCancelApplicationViewModel viewModel) { if (string.IsNullOrEmpty(viewModel.AreYouSure)) @@ -343,7 +348,9 @@ public async Task ConfirmCancelApplication(ConfirmCancelApplicati return RedirectToAction(nameof(ConfirmCancelApplication)); } - [HttpGet("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}"), ModelStatePersist(ModelStatePersist.RestoreEntry)] + [HttpGet("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}")] + [ModelStatePersist(ModelStatePersist.RestoreEntry)] + [ApplicationAuthorize(routeId: "Id")] public async Task Page(Guid Id, int sequenceNo, int sectionNo, string pageId, string __redirectAction, string __summaryLink = "Show") { var application = await _applicationApiClient.GetApplication(Id); @@ -438,7 +445,9 @@ public async Task Page(Guid Id, int sequenceNo, int sectionNo, st return View("~/Views/Application/Pages/Index.cshtml", viewModel); } - [HttpPost("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}/multiple"), ModelStatePersist(ModelStatePersist.Store)] + [HttpPost("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}/multiple")] + [ModelStatePersist(ModelStatePersist.Store)] + [ApplicationAuthorize(routeId: "Id")] public async Task SaveMultiplePageAnswers(Guid Id, int sequenceNo, int sectionNo, string pageId, string formAction, string __redirectAction, string __summaryLink) { var application = await _applicationApiClient.GetApplication(Id); @@ -535,7 +544,9 @@ public async Task SaveMultiplePageAnswers(Guid Id, int sequenceNo } } - [HttpPost("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}"), ModelStatePersist(ModelStatePersist.Store)] + [HttpPost("/Application/{Id}/Sequences/{sequenceNo}/Sections/{sectionNo}/Pages/{pageId}")] + [ModelStatePersist(ModelStatePersist.Store)] + [ApplicationAuthorize(routeId: "Id")] public async Task SaveAnswers(Guid Id, int sequenceNo, int sectionNo, string pageId, string __redirectAction, string __summaryLink) { var application = await _applicationApiClient.GetApplication(Id); @@ -651,6 +662,7 @@ public async Task SaveAnswers(Guid Id, int sequenceNo, int sectio } [HttpPost("/Application/DeleteAnswer")] + [ApplicationAuthorize(routeId: "Id")] public async Task DeleteAnswer(Guid Id, int sequenceNo, int sectionNo, string pageId, Guid answerId, string __redirectAction, string __summaryLink = "False") { var application = await _applicationApiClient.GetApplication(Id); @@ -673,6 +685,7 @@ public async Task DeleteAnswer(Guid Id, int sequenceNo, int secti } [HttpGet("Application/{Id}/Section/{sectionId}/Page/{pageId}/Question/{questionId}/{filename}/Download")] + [ApplicationAuthorize(routeId: "Id")] public async Task Download(Guid Id, Guid sectionId, string pageId, string questionId, string filename) { var application = await _applicationApiClient.GetApplication(Id); @@ -686,6 +699,7 @@ public async Task Download(Guid Id, Guid sectionId, string pageId } [HttpGet("Application/{Id}/SequenceNo/{sequenceNo}/Section/{sectionId}/Page/{pageId}/Question/{questionId}/Filename/{filename}/RedirectAction/{__redirectAction}")] + [ApplicationAuthorize(routeId: "Id")] public async Task DeleteFile(Guid Id, int sequenceNo, Guid sectionId, string pageId, string questionId, string filename, string __redirectAction) { var application = await _applicationApiClient.GetApplication(Id); @@ -702,6 +716,7 @@ public async Task DeleteFile(Guid Id, int sequenceNo, Guid sectio } [HttpPost("/Application/{Id}/Submit/{sequenceNo}")] + [ApplicationAuthorize(routeId: "Id")] public async Task Submit(Guid Id, int sequenceNo) { var application = await _applicationApiClient.GetApplication(Id); @@ -748,6 +763,7 @@ public async Task Submit(Guid Id, int sequenceNo) } [HttpGet("/Application/{Id}/Submitted")] + [ApplicationAuthorize(routeId: "Id")] public async Task Submitted(Guid Id) { var application = await _applicationApiClient.GetApplication(Id); @@ -760,6 +776,7 @@ public async Task Submitted(Guid Id) } [HttpGet("/Application/{Id}/NotSubmitted")] + [ApplicationAuthorize(routeId: "Id")] public async Task NotSubmitted(Guid Id) { var application = await _applicationApiClient.GetApplication(Id); @@ -772,6 +789,7 @@ public async Task NotSubmitted(Guid Id) } [HttpGet("/Application/{id}/Feedback")] + [ApplicationAuthorize(routeId: "Id")] public async Task Feedback(Guid id) { var application = await _applicationApiClient.GetApplication(id); @@ -816,7 +834,6 @@ private async Task GetDataFedOptions(Page page) return page; } - private void SetAnswerNotUpdated(Page page) { var validationErrors = new List>(); @@ -925,7 +942,6 @@ private static Page StoreEnteredAnswers(List answers, Page page) return page; } - private RedirectToActionResult RedirectToNextAction(Guid Id, int sequenceNo, int sectionNo, string nextAction, string nextActionId, string __redirectAction, string __summaryLink = "Show") { if (nextAction == "NextPage") diff --git a/src/SFA.DAS.AssessorService.Web/Controllers/Apply/StandardController.cs b/src/SFA.DAS.AssessorService.Web/Controllers/Apply/StandardController.cs index 2b3e2cf6db..d0c5a04b60 100644 --- a/src/SFA.DAS.AssessorService.Web/Controllers/Apply/StandardController.cs +++ b/src/SFA.DAS.AssessorService.Web/Controllers/Apply/StandardController.cs @@ -8,6 +8,7 @@ using SFA.DAS.AssessorService.Application.Api.Client.Clients; using SFA.DAS.AssessorService.ApplyTypes; using SFA.DAS.AssessorService.Domain.Consts; +using SFA.DAS.AssessorService.Web.StartupConfiguration; using SFA.DAS.AssessorService.Web.ViewModels.Apply; namespace SFA.DAS.AssessorService.Web.Controllers.Apply @@ -53,6 +54,7 @@ public async Task Search(StandardViewModel model) } [HttpGet("standard/{id}/confirm-standard/{standardCode}")] + [ApplicationAuthorize(routeId: "Id")] public async Task ConfirmStandard(Guid id, int standardCode) { var application = await _apiClient.GetApplication(id); diff --git a/src/SFA.DAS.AssessorService.Web/SFA.DAS.AssessorService.Web.csproj b/src/SFA.DAS.AssessorService.Web/SFA.DAS.AssessorService.Web.csproj index 187d43b3a7..d373901d53 100644 --- a/src/SFA.DAS.AssessorService.Web/SFA.DAS.AssessorService.Web.csproj +++ b/src/SFA.DAS.AssessorService.Web/SFA.DAS.AssessorService.Web.csproj @@ -3,19 +3,15 @@ netcoreapp2.2 - - - - diff --git a/src/SFA.DAS.AssessorService.Web/Services/ClaimService.cs b/src/SFA.DAS.AssessorService.Web/Services/ClaimService.cs new file mode 100644 index 0000000000..636bde6b41 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/Services/ClaimService.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SFA.DAS.AssessorService.Web.Services +{ + public class ClaimService : IClaimService + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public ClaimService(IHttpContextAccessor httpContextAssessor) + { + _httpContextAccessor = httpContextAssessor; + } + + public Guid? UserId + { + get + { + var userIdClaim = _httpContextAccessor.HttpContext.User.FindFirst(nameof(UserId)); + if(userIdClaim != null && Guid.TryParse(userIdClaim.Value, out Guid userId)) + { + return userId; + } + + return null; + } + } + } +} diff --git a/src/SFA.DAS.AssessorService.Web/Services/IClaimService.cs b/src/SFA.DAS.AssessorService.Web/Services/IClaimService.cs new file mode 100644 index 0000000000..587c0dd680 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/Services/IClaimService.cs @@ -0,0 +1,9 @@ +using System; + +namespace SFA.DAS.AssessorService.Web.Services +{ + public interface IClaimService + { + Guid? UserId {get;} + } +} diff --git a/src/SFA.DAS.AssessorService.Web/Startup.cs b/src/SFA.DAS.AssessorService.Web/Startup.cs index 700bbf13a1..cf9db71685 100644 --- a/src/SFA.DAS.AssessorService.Web/Startup.cs +++ b/src/SFA.DAS.AssessorService.Web/Startup.cs @@ -59,9 +59,11 @@ public IServiceProvider ConfigureServices(IServiceCollection services) options.RequestCultureProviders.Clear(); }); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddMvc(options => { options.Filters.Add();}) .AddControllersAsServices() .AddSessionStateTempDataProvider() diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizationHandler.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizationHandler.cs new file mode 100644 index 0000000000..7b334d4985 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizationHandler.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using SFA.DAS.AssessorService.Application.Api.Client.Clients; +using SFA.DAS.AssessorService.Web.Services; +using System; +using System.Threading.Tasks; + +namespace SFA.DAS.AssessorService.Web.StartupConfiguration +{ + public class ApplicationAuthorizationHandler : AuthorizationHandler + { + private readonly IApplicationApiClient _applicationApiClient; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IClaimService _claimService; + private readonly ILogger _logger; + + public ApplicationAuthorizationHandler(IApplicationApiClient applicationApiClient, IHttpContextAccessor httpContextAccessor, + IClaimService claimService, ILogger logger) + { + _applicationApiClient = applicationApiClient; + _httpContextAccessor = httpContextAccessor; + _claimService = claimService; + _logger = logger; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ApplicationRequirement requirement) + { + var userId = _claimService.UserId; + if (userId.HasValue) + { + var routeId = _httpContextAccessor.HttpContext.GetRouteValue(requirement.RouteId) as string + ?? _httpContextAccessor.HttpContext.Request.Query[requirement.RouteId][0]; + + if (Guid.TryParse(routeId, out Guid id)) + { + try + { + if (await _applicationApiClient.GetApplicationForUser(id, userId.Value) != null) + { + context.Succeed(requirement); + } + } + catch + { + _logger.LogError($"Attempt to access application {id} by user {userId.Value} was not allowed."); + context.Fail(); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizeAttribute.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizeAttribute.cs new file mode 100644 index 0000000000..3488e56d82 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationAuthorizeAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; + +namespace SFA.DAS.AssessorService.Web.StartupConfiguration +{ + public class ApplicationAuthorizeAttribute : AuthorizeAttribute + { + public const string POLICY_PREFIX = "ApplicationPolicy_"; + + public ApplicationAuthorizeAttribute(string routeId) + { + Policy = $"{POLICY_PREFIX}{routeId}"; + } + } +} \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationRequirement.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationRequirement.cs new file mode 100644 index 0000000000..ca6e6c8b69 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/ApplicationRequirement.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; + +namespace SFA.DAS.AssessorService.Web.StartupConfiguration +{ + public class ApplicationRequirement : IAuthorizationRequirement + { + public ApplicationRequirement(string routeId) + { + RouteId = routeId; + } + + public string RouteId { get; } + } +} \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegePolicyProvider.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/AssessorPolicyProvider.cs similarity index 56% rename from src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegePolicyProvider.cs rename to src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/AssessorPolicyProvider.cs index 695c1d7b5e..c31a2764f1 100644 --- a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegePolicyProvider.cs +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/AssessorPolicyProvider.cs @@ -4,26 +4,33 @@ namespace SFA.DAS.AssessorService.Web.StartupConfiguration { - public class PrivilegePolicyProvider : IAuthorizationPolicyProvider + public class AssessorPolicyProvider : IAuthorizationPolicyProvider { private DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } - private const string POLICY_PREFIX = "PrivilegePolicy_"; - public PrivilegePolicyProvider(IOptions options) + public AssessorPolicyProvider(IOptions options) { FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); } public Task GetPolicyAsync(string policyName) { - if (policyName.StartsWith(POLICY_PREFIX)) + if (policyName.StartsWith(PrivilegeAuthorizeAttribute.POLICY_PREFIX)) { - var privilege = policyName.Substring(POLICY_PREFIX.Length); + var privilege = policyName.Substring(PrivilegeAuthorizeAttribute.POLICY_PREFIX.Length); var policy = new AuthorizationPolicyBuilder(); policy.AddRequirements(new PrivilegeRequirement(privilege)); return Task.FromResult(policy.Build()); } + else if(policyName.StartsWith(ApplicationAuthorizeAttribute.POLICY_PREFIX)) + { + var routeId = policyName.Substring(ApplicationAuthorizeAttribute.POLICY_PREFIX.Length); + + var policy = new AuthorizationPolicyBuilder(); + policy.AddRequirements(new ApplicationRequirement(routeId)); + return Task.FromResult(policy.Build()); + } return FallbackPolicyProvider.GetPolicyAsync(policyName); diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/DeniedPrivilegeContext.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationDeniedContext.cs similarity index 80% rename from src/SFA.DAS.AssessorService.Web/StartupConfiguration/DeniedPrivilegeContext.cs rename to src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationDeniedContext.cs index 17c7a78f05..a98689d6e6 100644 --- a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/DeniedPrivilegeContext.cs +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationDeniedContext.cs @@ -2,7 +2,7 @@ namespace SFA.DAS.AssessorService.Web.StartupConfiguration { - public class DeniedPrivilegeContext + public class PrivilegeAuthorizationDeniedContext { public Guid PrivilegeId { get; set; } public string Controller { get; set; } diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeHandler.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationHandler.cs similarity index 81% rename from src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeHandler.cs rename to src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationHandler.cs index d58823389c..343196c74b 100644 --- a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeHandler.cs +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizationHandler.cs @@ -12,13 +12,13 @@ namespace SFA.DAS.AssessorService.Web.StartupConfiguration { - public class PrivilegeHandler : AuthorizationHandler + public class PrivilegeAuthorizationHandler : AuthorizationHandler { private readonly IContactsApiClient _contactsApiClient; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ITempDataProvider _tempDataProvider; - public PrivilegeHandler(IContactsApiClient contactsApiClient, IHttpContextAccessor httpContextAccessor, ITempDataProvider tempDataProvider) + public PrivilegeAuthorizationHandler(IContactsApiClient contactsApiClient, IHttpContextAccessor httpContextAccessor, ITempDataProvider tempDataProvider) { _contactsApiClient = contactsApiClient; _httpContextAccessor = httpContextAccessor; @@ -40,28 +40,27 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext var privilegeRequested = (await _contactsApiClient.GetPrivileges()).FirstOrDefault(p => p.Key.Equals(requirement.Privilege, StringComparison.InvariantCultureIgnoreCase)); if (privilegeRequested is null || !privilegeRequested.Enabled) { - var unavailableFeatureContext = new DeniedPrivilegeContext(); + var unavailableFeatureContext = new PrivilegeAuthorizationDeniedContext(); _tempDataProvider.SaveTempData(_httpContextAccessor.HttpContext, new Dictionary {{"UnavailableFeatureContext", JsonConvert.SerializeObject(unavailableFeatureContext)}}); return; } var contactPrivileges = await _contactsApiClient.GetContactPrivileges(Guid.Parse(userid)); - if (contactPrivileges.Any(cp => cp.Privilege.Key.Equals(requirement.Privilege, StringComparison.InvariantCultureIgnoreCase))) { - context.Succeed(requirement); + context.Succeed(requirement); } else { - var deniedPrivilegeContext = new DeniedPrivilegeContext + var deniedContext = new PrivilegeAuthorizationDeniedContext { PrivilegeId = privilegeRequested.Id, Controller = controllerActionDescriptor.ControllerName, Action = controllerActionDescriptor.ActionName }; - _tempDataProvider.SaveTempData(_httpContextAccessor.HttpContext, new Dictionary {{"DeniedPrivilegeContext", JsonConvert.SerializeObject(deniedPrivilegeContext)}}); + _tempDataProvider.SaveTempData(_httpContextAccessor.HttpContext, new Dictionary {{ nameof(PrivilegeAuthorizationDeniedContext), JsonConvert.SerializeObject(deniedContext)}}); } } } diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizeAttribute.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizeAttribute.cs new file mode 100644 index 0000000000..1b39536790 --- /dev/null +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeAuthorizeAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; + +namespace SFA.DAS.AssessorService.Web.StartupConfiguration +{ + public class PrivilegeAuthorizeAttribute : AuthorizeAttribute + { + public const string POLICY_PREFIX = "PrivilegePolicy_"; + + public PrivilegeAuthorizeAttribute(string permission) + { + Policy = $"{POLICY_PREFIX}{permission}"; + } + } +} \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeRequirement.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeRequirement.cs similarity index 100% rename from src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeRequirement.cs rename to src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeRequirement.cs index 12b6cc2ba6..7d6d63b365 100644 --- a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeRequirement.cs +++ b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/Authorization/PrivilegeRequirement.cs @@ -4,11 +4,11 @@ namespace SFA.DAS.AssessorService.Web.StartupConfiguration { public class PrivilegeRequirement : IAuthorizationRequirement { - public string Privilege { get; } - public PrivilegeRequirement(string privilege) { Privilege = privilege; } + + public string Privilege { get; } } } \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeAuthorizeAttribute.cs b/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeAuthorizeAttribute.cs deleted file mode 100644 index f81544fa4e..0000000000 --- a/src/SFA.DAS.AssessorService.Web/StartupConfiguration/PrivilegeAuthorizeAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNetCore.Authorization; - -namespace SFA.DAS.AssessorService.Web.StartupConfiguration -{ - public class PrivilegeAuthorizeAttribute : AuthorizeAttribute - { - private const string POLICY_PREFIX = "PrivilegePolicy_"; - - public PrivilegeAuthorizeAttribute(string permission) => Permission = permission; - - public string Permission - { - get => Policy.Substring(POLICY_PREFIX.Length); - set => Policy = $"{POLICY_PREFIX}{value}"; - } - } -} \ No newline at end of file diff --git a/src/SFA.DAS.AssessorService.Web/Views/Account/AccessDenied.cshtml b/src/SFA.DAS.AssessorService.Web/Views/Account/AccessDenied.cshtml index 5c950fa691..6fd2d5d6a4 100644 --- a/src/SFA.DAS.AssessorService.Web/Views/Account/AccessDenied.cshtml +++ b/src/SFA.DAS.AssessorService.Web/Views/Account/AccessDenied.cshtml @@ -9,7 +9,7 @@

Access denied

Your account does not have sufficient privileges

If you are experiencing difficulty accessing the area of the site you need, first contact an/the account owner to ensure you have the correct role assigned to your account.

- Go back + Go back \ No newline at end of file