Skip to content

Commit

Permalink
Updated the TestDataProvider to ensure we remove duplicate routes fro…
Browse files Browse the repository at this point in the history
…m the EndpointRoute collection

(%release-note:

- Updated the EndpointTestDataProvider to ensure we remove duplicate routes from the EndpointRoute collection
- Updated the readme.md file

%)
  • Loading branch information
Farshad DASHTI authored and Farshad DASHTI committed Nov 5, 2024
1 parent 6df5add commit 904e6c0
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private static IEnumerable<object[]> ParseExpectedPageSecurityData(
foreach (var entry in expectedSecurityConfig)
{
var matchingRoute = globalProtectionRoutes
.FirstOrDefault(route => route[0]?.ToString() == entry.Key);
.FirstOrDefault(route => route[0].ToString()!.Equals(entry.Key, StringComparison.OrdinalIgnoreCase));

if (matchingRoute != null)
{
Expand Down
176 changes: 150 additions & 26 deletions src/DfE.CoreLibs.Testing/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,15 @@ You can create custom factory customizations and use them like the following exa
This demonstrates how you can test your queries and database context interactions using a custom web application factory and test claims.


### Authorization and Endpoint Security Testing Framework
## Authorization and Endpoint and Page Security Testing Framework

The **Endpoint Security Testing Framework** is a library designed to help you verify that all your API endpoints have the expected security configurations.
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.
It ensures that each controller and action has the appropriate authorization attributes and that your application's security policies are consistently enforced.

## Endpoint Security Validator

**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.

## Usage

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

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.

```json

```json
{
"Endpoints": [
{
"Endpoints": [
{
"Controller": "SchoolsController",
"Action": "GetPrincipalBySchoolAsync",
"ExpectedSecurity": "Authorize: Policy=API.Read"
},
{
"Controller": "SchoolsController",
"Action": "GetPrincipalsBySchoolsAsync",
"ExpectedSecurity": "Authorize: Policy=API.Read"
},
{
"Controller": "SchoolsController",
"Action": "CreateSchoolAsync",
"ExpectedSecurity": "Authorize: Policy=API.Write"
},
{
"Controller": "SchoolsController",
"Action": "CreateReportAsync",
"ExpectedSecurity": "AllowAnonymous"
}
]
"Controller": "SchoolsController",
"Action": "GetPrincipalBySchoolAsync",
"ExpectedSecurity": "Authorize: Policy=API.Read"
},
{
"Controller": "SchoolsController",
"Action": "GetPrincipalsBySchoolsAsync",
"ExpectedSecurity": "Authorize: Policy=API.Read"
},
{
"Controller": "SchoolsController",
"Action": "CreateSchoolAsync",
"ExpectedSecurity": "Authorize: Policy=API.Write"
},
{
"Controller": "SchoolsController",
"Action": "CreateReportAsync",
"ExpectedSecurity": "AllowAnonymous"
}
```
]
}
```


### 2\. Write the Test Class

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

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

## Page Security Validator

**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.

## Usage

### 1\. Create the Security Configuration File

```json
{
"Endpoints": [
{
"Route": "/public/accessibility",
"ExpectedSecurity": "AllowAnonymous"
},
{
"Route": "/admin/dashboard",
"ExpectedSecurity": "Authorize: Policy=AdminOnly"
},
{
"Route": "/user/profile",
"ExpectedSecurity": "Authorize: Roles=User,Manager"
}
]
}
```

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.


### Understanding `_globalAuthorizationEnabled`


* **When `_globalAuthorizationEnabled` is `true`:**
* This setting assumes **global security enforcement** is applied in `Startup.cs` (e.g., `AuthorizeFolder("/")`).
* By default, **all pages are expected to have the `Authorize` attribute**.
* The configuration file can specify exceptions to global authorization, such as `AllowAnonymous` or specific authorization policies or roles.
* **When `_globalAuthorizationEnabled` is `false`:**
* Only the routes explicitly listed in the configuration file are validated.
* No global assumptions are made about other pages.


### 2\. Test Setup

The test setup includes:

* Instantiating the `AuthorizationTester`.
* Using `InitializeEndpoints` to retrieve all relevant endpoints.
* Loading security expectations from the JSON configuration file.

### Test Class Structure

```csharp
public class PageSecurityTests
{
private readonly AuthorizationTester _validator;
private static readonly Lazy<IEnumerable<RouteEndpoint>> _endpoints = new(InitializeEndpoints);
private const bool _globalAuthorizationEnabled = true;

public PageSecurityTests()
{
_validator = new AuthorizationTester(_globalAuthorizationEnabled);
}

[Theory]
[MemberData(nameof(GetPageSecurityTestData))]
public void ValidatePageSecurity(string route, string expectedSecurity)
{
var result = _validator.ValidatePageSecurity(route, expectedSecurity, _endpoints.Value);
Assert.Null(result.Message);
}

public static IEnumerable<object[]> GetPageSecurityTestData()
{
var configFilePath = "ExpectedSecurityConfig.json";
return EndpointTestDataProvider.GetPageSecurityTestDataFromFile(configFilePath, _endpoints.Value, _globalAuthorizationEnabled);
}

private static IEnumerable<RouteEndpoint> InitializeEndpoints()
{
// Using a temporary factory to access the EndpointDataSource for lazy initialization
var factory = new CustomWebApplicationFactory<Startup>();
var endpointDataSource = factory.Services.GetRequiredService<EndpointDataSource>();

return endpointDataSource.Endpoints
.OfType<RouteEndpoint>()
.Where(x => x.DisplayName!.Contains("Public/"));
}
}
```

### Explanation of Key Components

* **`AuthorizationTester` Instance:** Instantiates the validator with `_globalAuthorizationEnabled`.
* **Lazy Initialization of Endpoints:** `_endpoints` defers endpoint retrieval until needed, using `InitializeEndpoints`.
* **Test Method (`ValidatePageSecurity`):** Checks each route against its expected security settings.
* **`GetPageSecurityTestData`:** Loads security settings from the configuration file.
* **`InitializeEndpoints`:** Retrieves all relevant `RouteEndpoint` instances.

### Expected Security Configuration (JSON)

Each route entry in the JSON file specifies:

* **Route:** The URL pattern or path of the page.
* **ExpectedSecurity:** The required security setting:
* `AllowAnonymous` for public pages.
* `Authorize` with optional `Policy` or `Roles` for restricted pages.


### 3\. Running the Tests

Run the `PageSecurityTests` test suite to verify that each page’s security matches the specified expectations.

If a route is missing the expected security setting, `Assert.Null(result.Message);` will fail and show the error message, such as:

Page /admin/dashboard should be protected with Policy 'AdminOnly' but was not found.


For detailed examples, please refer to the [GitHub DDD-CA-Template repository](https://github.com/DFE-Digital/rsd-ddd-clean-architecture).

* * *

0 comments on commit 904e6c0

Please sign in to comment.