diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileDto.cs new file mode 100644 index 000000000..bbf8fa938 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace Unity.GrantManager.Applicants +{ + public class ApplicantProfileDto + { + public Guid ProfileId { get; set; } + public string Subject { get; set; } = string.Empty; + public string Issuer { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileRequest.cs new file mode 100644 index 000000000..4b2d24d87 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantProfileRequest.cs @@ -0,0 +1,16 @@ +using System; + +namespace Unity.GrantManager.Applicants +{ + public class ApplicantProfileRequest + { + public Guid ProfileId { get; set; } = Guid.NewGuid(); + public string Subject { get; set; } = string.Empty; + public string Issuer { get; set; } = string.Empty; + } + + public class TenantedApplicantProfileRequest : ApplicantProfileRequest + { + public Guid TenantId { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantTenantDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantTenantDto.cs new file mode 100644 index 000000000..9794ad422 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/ApplicantTenantDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Unity.GrantManager.Applicants +{ + public class ApplicantTenantDto + { + public Guid TenantId { get; set; } + public string TenantName { get; set; } = string.Empty; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/IApplicantProfileAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/IApplicantProfileAppService.cs new file mode 100644 index 000000000..559d3cdce --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Applicants/IApplicantProfileAppService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Unity.GrantManager.Applicants +{ + public interface IApplicantProfileAppService + { + Task GetApplicantProfileAsync(ApplicantProfileRequest request); + Task> GetApplicantTenantsAsync(ApplicantProfileRequest request); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantProfileAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantProfileAppService.cs new file mode 100644 index 000000000..960dc5c6e --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantProfileAppService.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.GrantManager.Applications; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TenantManagement; + +namespace Unity.GrantManager.Applicants +{ + [RemoteService(false)] + public class ApplicantProfileAppService(ICurrentTenant currentTenant, + ITenantRepository tenantRepository, + IRepository applicationFormSubmissionRepository) + : ApplicationService, IApplicantProfileAppService + { + public async Task GetApplicantProfileAsync(ApplicantProfileRequest request) + { + return await Task.FromResult(new ApplicantProfileDto + { + ProfileId = request.ProfileId, + Subject = request.Subject, + Issuer = request.Issuer, + Email = string.Empty, + DisplayName = string.Empty + }); + } + + public async Task> GetApplicantTenantsAsync(ApplicantProfileRequest request) + { + // Extract the username part from the OIDC sub (part before '@') + var subUsername = request.Subject.Contains('@') + ? request.Subject[..request.Subject.IndexOf('@')].ToUpper() + : request.Subject.ToUpper(); + + var result = new List(); + + // Get all tenants from the host context + using (currentTenant.Change(null)) + { + var tenants = await tenantRepository.GetListAsync(); + + // Query each tenant's database for matching submissions + foreach (var tenant in tenants) + { + using (currentTenant.Change(tenant.Id)) + { + var queryable = await applicationFormSubmissionRepository.GetQueryableAsync(); + var hasMatchingSubmission = queryable.Any(s => s.OidcSub == subUsername); + + if (hasMatchingSubmission) + { + result.Add(new ApplicantTenantDto + { + TenantId = tenant.Id, + TenantName = tenant.Name + }); + } + } + } + } + + return result; + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/ApplicantProfileController.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/ApplicantProfileController.cs new file mode 100644 index 000000000..61dee526b --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/ApplicantProfileController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Unity.GrantManager.Applicants; +using Unity.GrantManager.Controllers.Authentication; +using Volo.Abp.AspNetCore.Mvc; + +namespace Unity.GrantManager.Controllers +{ + [ApiController] + [Route("api/app/applicant-profiles")] + [ServiceFilter(typeof(ApiKeyAuthorizationFilter))] + public class ApplicantProfileController(IApplicantProfileAppService applicantProfileAppService) : AbpControllerBase + { + + [HttpGet] + [Route("profile")] + public async Task GetApplicantProfileAsync([FromQuery] TenantedApplicantProfileRequest applicantProfileRequest) + { + var profile = await applicantProfileAppService.GetApplicantProfileAsync(applicantProfileRequest); + return Ok(profile); + } + + [HttpGet] + [Route("tenants")] + public async Task GetApplicantProfileTenantsAsync([FromQuery] ApplicantProfileRequest applicantProfileRequest) + { + var tenants = await applicantProfileAppService.GetApplicantTenantsAsync(applicantProfileRequest); + return Ok(tenants); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/Authentication/ApiKeyAuthorizationFilter.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/Authentication/ApiKeyAuthorizationFilter.cs new file mode 100644 index 000000000..07602f9c3 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/Authentication/ApiKeyAuthorizationFilter.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Configuration; +using Unity.GrantManager.ApplicationForms; + +namespace Unity.GrantManager.Controllers.Authentication +{ + public class ApiKeyAuthorizationFilter(IConfiguration configuration) : IAuthorizationFilter + { + public void OnAuthorization(AuthorizationFilterContext context) + { + if (!context.HttpContext.Request.Headers.TryGetValue(AuthConstants.ApiKeyHeader, out var extractedApiKey)) + { + context.Result = new UnauthorizedObjectResult(new ProblemDetails + { + Status = StatusCodes.Status401Unauthorized, + Title = "Unauthorized", + Detail = "API Key missing", + Type = "https://tools.ietf.org/html/rfc7235#section-3.1" + }); + return; + } + + var apiKey = configuration["B2BAuth:ApiKey"]; + + if (apiKey is null) + { + context.Result = new UnauthorizedObjectResult(new ProblemDetails + { + Status = StatusCodes.Status401Unauthorized, + Title = "Unauthorized", + Detail = "API Key not configured", + Type = "https://tools.ietf.org/html/rfc7235#section-3.1" + }); + return; + } + + if (apiKey != extractedApiKey) + { + context.Result = new UnauthorizedObjectResult(new ProblemDetails + { + Status = StatusCodes.Status401Unauthorized, + Title = "Unauthorized", + Detail = "Invalid API Key", + Type = "https://tools.ietf.org/html/rfc7235#section-3.1" + }); + } + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/GrantManagerHttpApiModule.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/GrantManagerHttpApiModule.cs index f0467ae56..cb42ce896 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/GrantManagerHttpApiModule.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/GrantManagerHttpApiModule.cs @@ -8,6 +8,8 @@ using Volo.Abp.PermissionManagement.HttpApi; using Volo.Abp.SettingManagement; using Unity.Notifications; +using Unity.GrantManager.Controllers.Authentication; +using Microsoft.Extensions.DependencyInjection; namespace Unity.GrantManager; @@ -25,7 +27,10 @@ public class GrantManagerHttpApiModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + var services = context.Services; + ConfigureLocalization(); + ConfigureFilters(services); } private void ConfigureLocalization() @@ -39,4 +44,9 @@ private void ConfigureLocalization() ); }); } + + private static void ConfigureFilters(IServiceCollection services) + { + services.AddScoped(); + } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Unity.GrantManager.HttpApi.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Unity.GrantManager.HttpApi.csproj index de402457c..77574bfd5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Unity.GrantManager.HttpApi.csproj +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Unity.GrantManager.HttpApi.csproj @@ -16,9 +16,9 @@ - + - + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json index 4ebac5540..bec7eeca4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json @@ -94,5 +94,8 @@ }, "IdentityProfileLogin": { "AutoCreateUser": true - } + }, + "B2BAuth": { + "ApiKey": "" + } } \ No newline at end of file