Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
5cd9f59
initial endpoint working
Apr 16, 2026
eed8c9c
Added unit tests
Apr 16, 2026
edc9d42
temp
Apr 16, 2026
13c4f61
Added logging
Apr 17, 2026
f678c22
Fixed tests
Apr 17, 2026
f6cf096
UseCase working
Apr 20, 2026
7670d09
parent service fix adn tests updated
Apr 21, 2026
7fccc12
fix
Apr 21, 2026
6ac4a8d
removed node requester
Apr 21, 2026
5ec6c8f
update comments
Apr 21, 2026
f31b1f8
Update
Apr 22, 2026
92e3fd6
removed comments
Apr 22, 2026
0da5da2
Add test coverage for GetApplicationRolesUseCase
Apr 22, 2026
f22d9ed
fixed warnings from sonarqube
Apr 22, 2026
5e8f6a2
sonarqube fixes
Apr 22, 2026
60879a3
Created endpoint skeleton
Apr 23, 2026
a10a147
Resolved review comments
Apr 23, 2026
35fcee6
initial skeleton
Apr 23, 2026
77e1aa3
Working end to end
Apr 28, 2026
caa235b
fixing tests
Apr 28, 2026
ef7e600
fixed test
Apr 28, 2026
bd00eec
clean up
Apr 28, 2026
3150f9d
temp check in
Apr 29, 2026
e07f307
initial endpoint working
Apr 16, 2026
2f7bf37
Added unit tests
Apr 16, 2026
46e4911
temp
Apr 16, 2026
4e3909e
Added logging
Apr 17, 2026
4f53ee3
Fixed tests
Apr 17, 2026
a634820
UseCase working
Apr 20, 2026
a965c89
parent service fix adn tests updated
Apr 21, 2026
71e6415
fix
Apr 21, 2026
663910c
removed node requester
Apr 21, 2026
33e0d54
update comments
Apr 21, 2026
bced67a
Update
Apr 22, 2026
a551f11
removed comments
Apr 22, 2026
0f75e2f
Add test coverage for GetApplicationRolesUseCase
Apr 22, 2026
4042ef7
Created endpoint skeleton
Apr 23, 2026
6e08c4f
initial skeleton
Apr 23, 2026
043eb42
Working end to end
Apr 28, 2026
f7d8ea8
fixing tests
Apr 28, 2026
5b66d49
fixed test
Apr 28, 2026
decbd7a
clean up
Apr 28, 2026
524aaab
temp check in
Apr 29, 2026
e622079
Merge branch 'feature/DSI-8683_public-api-service-users-no-filter' of…
Apr 29, 2026
4e6f36e
initial endpoint working
Apr 16, 2026
e1a74b2
Added unit tests
Apr 16, 2026
f57d1cd
temp
Apr 16, 2026
514380a
Added logging
Apr 17, 2026
eae5e7a
Fixed tests
Apr 17, 2026
d224f18
UseCase working
Apr 20, 2026
96391cc
parent service fix adn tests updated
Apr 21, 2026
47a048f
fix
Apr 21, 2026
2329d42
removed node requester
Apr 21, 2026
6691911
update comments
Apr 21, 2026
598682c
Update
Apr 22, 2026
b71cc82
removed comments
Apr 22, 2026
ec5f8b0
Add test coverage for GetApplicationRolesUseCase
Apr 22, 2026
37f252e
fixed warnings from sonarqube
Apr 22, 2026
2bc4950
sonarqube fixes
Apr 22, 2026
c54ef5e
Resolved review comments
Apr 23, 2026
af170b2
Merge branch 'feature/DSI-8680_public-api-get-roles-for-services' of …
Apr 29, 2026
a64c210
Clean up
Apr 29, 2026
7deffff
Cleanup
Apr 30, 2026
ce1c52d
fix failed check
Apr 30, 2026
c78d815
fix failed check
Apr 30, 2026
8e1fb9a
fixes
Apr 30, 2026
7b11fff
resolved manual conflicts
Apr 30, 2026
98d2ebc
Partially working with filters
May 1, 2026
80eaa13
working
May 1, 2026
d522e0e
Added tests
May 1, 2026
74ecff9
added additional test cases
May 1, 2026
bf1b3ef
Tests updated
May 1, 2026
d76e8a9
added fluent validation
May 5, 2026
af66a01
a little refactoring
May 5, 2026
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
5 changes: 5 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.4" />
<PackageVersion Include="System.Net.Http.Json" Version="9.0.9" />
</ItemGroup>
<!-- Validation -->
<ItemGroup>
<PackageVersion Include="FluentValidation" Version="11.9.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
</ItemGroup>
<!-- Security -->
<ItemGroup>
<!-- Headers -->
Expand Down
8 changes: 7 additions & 1 deletion dsi-platform.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.4.11626.88 stable
VisualStudioVersion = 18.4.11626.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3F245DBD-544F-4DD4-9B56-06B116BB9CFE}"
EndProject
Expand Down Expand Up @@ -107,6 +107,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dfe.SignIn.AppHost", "src\D
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dfe.SignIn.ServiceDefaults", "src\Dfe.SignIn.ServiceDefaults\Dfe.SignIn.ServiceDefaults.csproj", "{282680D1-4103-4AC6-9238-0038A937DAA2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6EEA84BA-E9AD-4CD5-8E6F-4ABB0C0D4613}"
ProjectSection(SolutionItems) = preProject
Directory.Packages.props = Directory.Packages.props
Directory.Solution.props = Directory.Solution.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
19 changes: 18 additions & 1 deletion src/Dfe.SignIn.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
var internalApiConfig = builder.Configuration.GetSection("InternalApiClient");
var redisConnectionString = redis.Resource.ConnectionStringExpression;

// EntityFramework configuration sections
var efOrganisationsConfig = builder.Configuration.GetSection("EntityFramework:Organisations");
var efDirectoriesConfig = builder.Configuration.GetSection("EntityFramework:Directories");
var efAuditConfig = builder.Configuration.GetSection("EntityFramework:Audit");

builder.AddProject<Projects.Dfe_SignIn_Web_Help>("app-help", launchProfileName: "http")
.WithSharedConfiguration(builder.Configuration, frontend.GetEndpoint("http"))
.WithEnvironment("InteractionsRedisCache__ConnectionString", redisConnectionString)
Expand Down Expand Up @@ -64,8 +69,20 @@
.WithEnvironment("SelectOrganisation__SelectOrganisationBaseAddress", selectOrgConfig["SelectOrganisationBaseAddress"])
.WithEnvironment("InternalApiClient__Access__BaseAddress", internalApiConfig["Access:BaseAddress"])
.WithEnvironment("InternalApiClient__Organisations__BaseAddress", internalApiConfig["Organisations:BaseAddress"])
.WithEnvironment("EntityFramework__Organisations__Username", efOrganisationsConfig["Username"])
.WithEnvironment("EntityFramework__Organisations__Password", efOrganisationsConfig["Password"])
.WithEnvironment("EntityFramework__Organisations__Name", efOrganisationsConfig["Name"])
.WithEnvironment("EntityFramework__Organisations__Host", efOrganisationsConfig["Host"])
.WithEnvironment("EntityFramework__Directories__Username", efDirectoriesConfig["Username"])
.WithEnvironment("EntityFramework__Directories__Password", efDirectoriesConfig["Password"])
.WithEnvironment("EntityFramework__Directories__Name", efDirectoriesConfig["Name"])
.WithEnvironment("EntityFramework__Directories__Host", efDirectoriesConfig["Host"])
.WithEnvironment("EntityFramework__Audit__Username", efAuditConfig["Username"])
.WithEnvironment("EntityFramework__Audit__Password", efAuditConfig["Password"])
.WithEnvironment("EntityFramework__Audit__Name", efAuditConfig["Name"])
.WithEnvironment("EntityFramework__Audit__Host", efAuditConfig["Host"])
.WaitFor(redis);

builder.AddExecutable("tool-tls-proxy", "pwsh", "../../", "-Command", "Start-DsiTlsProxy");

builder.Build().Run();
await builder.Build().RunAsync();
24 changes: 24 additions & 0 deletions src/Dfe.SignIn.Base.Framework/Extensions/DateTimeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Dfe.SignIn.Base.Framework.Extensions;

/// <summary>
/// Extensions for DateTime to handle conversion to UTC, treating unspecified kinds as UTC by default.
/// </summary>
public static class DateTimeExtensions
{
/// <summary>
/// Returns the UTC equivalent of the given DateTime. If the input DateTime has an
/// unspecified kind, it will be treated as UTC.
/// </summary>
/// <param name="date">The DateTime to convert to UTC.</param>
/// <returns>The UTC equivalent of the given DateTime.</returns>
public static DateTime ToUtc(this DateTime date)
=> date.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : date.ToUniversalTime();

/// <summary>
/// Returns the UTC equivalent of the given DateTime, or null if the input is null.
/// </summary>
/// <param name="date">The nullable DateTime to convert to UTC.</param>
/// <returns>The UTC equivalent of the given DateTime, or null if the input is null.</returns>
public static DateTime? ToUtc(this DateTime? date)
=> date.HasValue ? ToUtc(date.Value) : null;
}
2 changes: 1 addition & 1 deletion src/Dfe.SignIn.Core.Contracts/Access/ApplicationRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public sealed record ApplicationRole
/// <summary>
/// The numeric identifier of the role.
/// </summary>
public required int NumericId { get; init; }
public required long NumericId { get; init; }

/// <summary>
/// A value indicating the status of the role.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace Dfe.SignIn.Core.Contracts.Access;
/// <summary>
/// Request to get the roles that are associated with an application.
/// </summary>
[AssociatedResponse(typeof(GetRolesOfApplicationResponse))]
public sealed record GetRolesOfApplicationRequest
[AssociatedResponse(typeof(GetApplicationRolesResponse))]
public sealed record GetApplicationRolesRequest
{
/// <summary>
/// The unique identifier of the application.
Expand All @@ -15,9 +15,9 @@ public sealed record GetRolesOfApplicationRequest
}

/// <summary>
/// Response model for request <see cref="GetRolesOfApplicationRequest"/>.
/// Response model for request <see cref="GetApplicationRolesRequest"/>.
/// </summary>
public sealed record GetRolesOfApplicationResponse
public sealed record GetApplicationRolesResponse
{
/// <summary>
/// An enumerable collection of application roles.
Expand Down
10 changes: 10 additions & 0 deletions src/Dfe.SignIn.Core.Contracts/Applications/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,14 @@ public sealed record Application
/// page if possible. Role based services are not hidden.
/// </summary>
public required bool IsHiddenService { get; init; }

/// <summary>
/// Parent application ID, if this application is a child of another application.
/// </summary>
public Guid? ParentId { get; init; }

/// <summary>
/// The unique client identifier (e.g., "GIAS") of the parent application, if this application is a child of another application.
/// </summary>
public string? ParentClientId { get; init; }
}
56 changes: 56 additions & 0 deletions src/Dfe.SignIn.Core.Contracts/Applications/ApplicationRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.ComponentModel.DataAnnotations;

namespace Dfe.SignIn.Core.Contracts.Applications;

/// <summary>
/// A model representing a role in DfE Sign-in.
/// </summary>
public sealed record ApplicationRole
{
/// <summary>
/// The unique value that identifies the role.
/// </summary>
public required Guid Id { get; init; }

/// <summary>
/// The parent role.
/// </summary>
public ApplicationRole? Parent { get; init; } = null;

/// <summary>
/// The code of the role.
/// </summary>
public required string Code { get; init; }

/// <summary>
/// The name of the role.
/// </summary>
public required string Name { get; init; }

/// <summary>
/// The numeric identifier of the role.
/// </summary>
public required long NumericId { get; init; }

/// <summary>
/// A value indicating the status of the role.
/// </summary>
[EnumDataType(typeof(ApplicationRoleStatus))]
public required ApplicationRoleStatus Status { get; init; }
}

/// <summary>
/// Represents the status of a role.
/// </summary>
public enum ApplicationRoleStatus
{
/// <summary>
/// Indicates that a role is inactive.
/// </summary>
Inactive = 0,

/// <summary>
/// Indicates that a role is active.
/// </summary>
Active = 1,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Dfe.SignIn.Base.Framework;

namespace Dfe.SignIn.Core.Contracts.Applications;

/// <summary>
/// Request to get the roles that are associated with an application.
/// </summary>
[AssociatedResponse(typeof(GetApplicationRolesResponse))]
public sealed record GetApplicationRolesRequest
{
/// <summary>
/// The unique identifier of the application.
/// </summary>
public required Guid ApplicationId { get; init; }
}

/// <summary>
/// Response model for request <see cref="GetApplicationRolesRequest"/>.
/// </summary>
public sealed record GetApplicationRolesResponse
{
/// <summary>
/// An enumerable collection of application roles.
/// </summary>
public required IEnumerable<ApplicationRole> Roles { get; init; }
}
40 changes: 40 additions & 0 deletions src/Dfe.SignIn.Core.Contracts/Organisations/OrganisationRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Dfe.SignIn.Core.Contracts.Organisations;

/// <summary>
/// Represents an organisation role with an identifier and a display name.
/// </summary>
/// <param name="Id">The unique short identifier for the role.</param>
/// <param name="Name">The display name of the role.</param>
public sealed record OrganisationRole(short Id, string Name);

/// <summary>
/// Provides static references and lookup methods for well-known organisation roles.
/// </summary>
public static class OrganisationRoles
{
/// <summary>
/// The standard end user role (Id = 0, Name = "End user").
/// </summary>
public static readonly OrganisationRole EndUser = new(0, "End user");

/// <summary>
/// The approver role (Id = 10000, Name = "Approver").
/// </summary>
public static readonly OrganisationRole Approver = new(10000, "Approver");

/// <summary>
/// Internal lookup dictionary for roles by their short Id.
/// </summary>
private static readonly IReadOnlyDictionary<short, OrganisationRole> ById =

Check warning on line 28 in src/Dfe.SignIn.Core.Contracts/Organisations/OrganisationRole.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change type of field 'ById' from 'System.Collections.Generic.IReadOnlyDictionary<short, Dfe.SignIn.Core.Contracts.Organisations.OrganisationRole>' to 'System.Collections.Generic.Dictionary<short, Dfe.SignIn.Core.Contracts.Organisations.OrganisationRole>' for improved performance

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_dsi-platform&issues=AZ34f2AP25ty236Rpv1Z&open=AZ34f2AP25ty236Rpv1Z&pullRequest=175
new Dictionary<short, OrganisationRole> {
[EndUser.Id] = EndUser,
[Approver.Id] = Approver
};

/// <summary>
/// Gets a known <see cref="OrganisationRole"/> by its Id, or null if not found.
/// </summary>
/// <param name="roleId">The short Id of the role.</param>
/// <returns>The matching <see cref="OrganisationRole"/>, or null if not found.</returns>
public static OrganisationRole? FromId(short roleId) => ById.TryGetValue(roleId, out var role) ? role : null;
}
Loading
Loading