Skip to content

Commit 904e6c0

Browse files
Farshad DASHTIFarshad DASHTI
authored andcommitted
Updated the TestDataProvider to ensure we remove duplicate routes from the EndpointRoute collection
(%release-note: - Updated the EndpointTestDataProvider to ensure we remove duplicate routes from the EndpointRoute collection - Updated the readme.md file %)
1 parent 6df5add commit 904e6c0

File tree

2 files changed

+151
-27
lines changed

2 files changed

+151
-27
lines changed

src/DfE.CoreLibs.Testing/Authorization/Helpers/EndpointTestDataProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private static IEnumerable<object[]> ParseExpectedPageSecurityData(
133133
foreach (var entry in expectedSecurityConfig)
134134
{
135135
var matchingRoute = globalProtectionRoutes
136-
.FirstOrDefault(route => route[0]?.ToString() == entry.Key);
136+
.FirstOrDefault(route => route[0].ToString()!.Equals(entry.Key, StringComparison.OrdinalIgnoreCase));
137137

138138
if (matchingRoute != null)
139139
{

src/DfE.CoreLibs.Testing/readme.md

Lines changed: 150 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@ You can create custom factory customizations and use them like the following exa
8282
This demonstrates how you can test your queries and database context interactions using a custom web application factory and test claims.
8383

8484

85-
### Authorization and Endpoint Security Testing Framework
85+
## Authorization and Endpoint and Page Security Testing Framework
8686

87-
The **Endpoint Security Testing Framework** is a library designed to help you verify that all your API endpoints have the expected security configurations.
87+
The **Endpoint and Page Security Testing Framework** is a library designed to help you verify that all your API endpoints have the expected security configurations.
8888
It ensures that each controller and action has the appropriate authorization attributes and that your application's security policies are consistently enforced.
8989

90+
## Endpoint Security Validator
91+
92+
**Endpoint Security Validator** allows you to validate that endpoint in your .NET API has the correct security settings. The validator uses reflection along with a configuration file to enforce expected security requirements.
93+
9094
## Usage
9195

9296
To utilize the framework, follow these steps:
@@ -95,32 +99,34 @@ To utilize the framework, follow these steps:
9599

96100
Create a JSON file (e.g., `ExpectedSecurity.json`) that defines the expected security for each endpoint in your application. This file should include all controllers and actions.
97101

98-
```json
102+
103+
```json
104+
{
105+
"Endpoints": [
99106
{
100-
"Endpoints": [
101-
{
102-
"Controller": "SchoolsController",
103-
"Action": "GetPrincipalBySchoolAsync",
104-
"ExpectedSecurity": "Authorize: Policy=API.Read"
105-
},
106-
{
107-
"Controller": "SchoolsController",
108-
"Action": "GetPrincipalsBySchoolsAsync",
109-
"ExpectedSecurity": "Authorize: Policy=API.Read"
110-
},
111-
{
112-
"Controller": "SchoolsController",
113-
"Action": "CreateSchoolAsync",
114-
"ExpectedSecurity": "Authorize: Policy=API.Write"
115-
},
116-
{
117-
"Controller": "SchoolsController",
118-
"Action": "CreateReportAsync",
119-
"ExpectedSecurity": "AllowAnonymous"
120-
}
121-
]
107+
"Controller": "SchoolsController",
108+
"Action": "GetPrincipalBySchoolAsync",
109+
"ExpectedSecurity": "Authorize: Policy=API.Read"
110+
},
111+
{
112+
"Controller": "SchoolsController",
113+
"Action": "GetPrincipalsBySchoolsAsync",
114+
"ExpectedSecurity": "Authorize: Policy=API.Read"
115+
},
116+
{
117+
"Controller": "SchoolsController",
118+
"Action": "CreateSchoolAsync",
119+
"ExpectedSecurity": "Authorize: Policy=API.Write"
120+
},
121+
{
122+
"Controller": "SchoolsController",
123+
"Action": "CreateReportAsync",
124+
"ExpectedSecurity": "AllowAnonymous"
122125
}
123-
```
126+
]
127+
}
128+
```
129+
124130

125131
### 2\. Write the Test Class
126132

@@ -148,6 +154,124 @@ Create a test class in your test project that uses the framework to validate you
148154

149155
The above test will run a test per endpoint and ensures the expected security policy is applied to thje endpoint or the controller.
150156

157+
## Page Security Validator
158+
159+
**Page Security Validator** allows you to validate that each page in your ASP.NET Core application has the correct security settings. The validator uses route metadata along with a configuration file to enforce expected security requirements, including global authorization settings or route-specific configurations.
160+
161+
## Usage
162+
163+
### 1\. Create the Security Configuration File
164+
165+
```json
166+
{
167+
"Endpoints": [
168+
{
169+
"Route": "/public/accessibility",
170+
"ExpectedSecurity": "AllowAnonymous"
171+
},
172+
{
173+
"Route": "/admin/dashboard",
174+
"ExpectedSecurity": "Authorize: Policy=AdminOnly"
175+
},
176+
{
177+
"Route": "/user/profile",
178+
"ExpectedSecurity": "Authorize: Roles=User,Manager"
179+
}
180+
]
181+
}
182+
```
183+
184+
This configuration file should be set to always copy to the output directory by setting `Copy to Output Directory` to `Copy always` in your project settings.
185+
186+
187+
### Understanding `_globalAuthorizationEnabled`
188+
189+
190+
* **When `_globalAuthorizationEnabled` is `true`:**
191+
* This setting assumes **global security enforcement** is applied in `Startup.cs` (e.g., `AuthorizeFolder("/")`).
192+
* By default, **all pages are expected to have the `Authorize` attribute**.
193+
* The configuration file can specify exceptions to global authorization, such as `AllowAnonymous` or specific authorization policies or roles.
194+
* **When `_globalAuthorizationEnabled` is `false`:**
195+
* Only the routes explicitly listed in the configuration file are validated.
196+
* No global assumptions are made about other pages.
197+
198+
199+
### 2\. Test Setup
200+
201+
The test setup includes:
202+
203+
* Instantiating the `AuthorizationTester`.
204+
* Using `InitializeEndpoints` to retrieve all relevant endpoints.
205+
* Loading security expectations from the JSON configuration file.
206+
207+
### Test Class Structure
208+
209+
```csharp
210+
public class PageSecurityTests
211+
{
212+
private readonly AuthorizationTester _validator;
213+
private static readonly Lazy<IEnumerable<RouteEndpoint>> _endpoints = new(InitializeEndpoints);
214+
private const bool _globalAuthorizationEnabled = true;
215+
216+
public PageSecurityTests()
217+
{
218+
_validator = new AuthorizationTester(_globalAuthorizationEnabled);
219+
}
220+
221+
[Theory]
222+
[MemberData(nameof(GetPageSecurityTestData))]
223+
public void ValidatePageSecurity(string route, string expectedSecurity)
224+
{
225+
var result = _validator.ValidatePageSecurity(route, expectedSecurity, _endpoints.Value);
226+
Assert.Null(result.Message);
227+
}
228+
229+
public static IEnumerable<object[]> GetPageSecurityTestData()
230+
{
231+
var configFilePath = "ExpectedSecurityConfig.json";
232+
return EndpointTestDataProvider.GetPageSecurityTestDataFromFile(configFilePath, _endpoints.Value, _globalAuthorizationEnabled);
233+
}
234+
235+
private static IEnumerable<RouteEndpoint> InitializeEndpoints()
236+
{
237+
// Using a temporary factory to access the EndpointDataSource for lazy initialization
238+
var factory = new CustomWebApplicationFactory<Startup>();
239+
var endpointDataSource = factory.Services.GetRequiredService<EndpointDataSource>();
240+
241+
return endpointDataSource.Endpoints
242+
.OfType<RouteEndpoint>()
243+
.Where(x => x.DisplayName!.Contains("Public/"));
244+
}
245+
}
246+
```
247+
248+
### Explanation of Key Components
249+
250+
* **`AuthorizationTester` Instance:** Instantiates the validator with `_globalAuthorizationEnabled`.
251+
* **Lazy Initialization of Endpoints:** `_endpoints` defers endpoint retrieval until needed, using `InitializeEndpoints`.
252+
* **Test Method (`ValidatePageSecurity`):** Checks each route against its expected security settings.
253+
* **`GetPageSecurityTestData`:** Loads security settings from the configuration file.
254+
* **`InitializeEndpoints`:** Retrieves all relevant `RouteEndpoint` instances.
255+
256+
### Expected Security Configuration (JSON)
257+
258+
Each route entry in the JSON file specifies:
259+
260+
* **Route:** The URL pattern or path of the page.
261+
* **ExpectedSecurity:** The required security setting:
262+
* `AllowAnonymous` for public pages.
263+
* `Authorize` with optional `Policy` or `Roles` for restricted pages.
264+
265+
266+
### 3\. Running the Tests
267+
268+
Run the `PageSecurityTests` test suite to verify that each page’s security matches the specified expectations.
269+
270+
If a route is missing the expected security setting, `Assert.Null(result.Message);` will fail and show the error message, such as:
271+
272+
Page /admin/dashboard should be protected with Policy 'AdminOnly' but was not found.
273+
274+
151275
For detailed examples, please refer to the [GitHub DDD-CA-Template repository](https://github.com/DFE-Digital/rsd-ddd-clean-architecture).
152276

153277
* * *

0 commit comments

Comments
 (0)