diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index d0912e89c..b9f1b33eb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -38,7 +38,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Dependency Review ("Dependabot on PR")
if: ${{ github.event_name == 'pull_request' && !github.event.repository.fork }}
diff --git a/.github/workflows/on-cli-generator-pullrequest.yml b/.github/workflows/on-cli-generator-pullrequest.yml
index 06329e8cd..9cc2a8d0b 100644
--- a/.github/workflows/on-cli-generator-pullrequest.yml
+++ b/.github/workflows/on-cli-generator-pullrequest.yml
@@ -56,7 +56,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-cli-generator.ps1 Build -Configuration ${{ env.CONFIGURATION }}
diff --git a/.github/workflows/on-config-pullrequest.yml b/.github/workflows/on-config-pullrequest.yml
index afe307184..f016c3baf 100644
--- a/.github/workflows/on-config-pullrequest.yml
+++ b/.github/workflows/on-config-pullrequest.yml
@@ -54,7 +54,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Build
run: ./build-config.ps1 Build -Configuration ${{ env.CONFIGURATION }}
@@ -121,7 +121,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Build
run: ./build-config.ps1 Build -Configuration ${{ env.CONFIGURATION }}
@@ -163,7 +163,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Build
run: ./build-config.ps1 Build -Configuration ${{ env.CONFIGURATION }} -IdentityProvider ${{matrix.identityprovider}}
@@ -178,7 +178,7 @@ jobs:
with:
name: test-logs
path: |
- ./src/services/EdFi.DmsConfigurationService.Api.Tests.E2E/bin/Release/net8.0/logs
+ ./src/services/EdFi.DmsConfigurationService.Api.Tests.E2E/bin/Release/net10.0/logs
retention-days: 10
- name: Upload Test Results
diff --git a/.github/workflows/on-dms-pullrequest.yml b/.github/workflows/on-dms-pullrequest.yml
index c089f779f..e613cc3c0 100644
--- a/.github/workflows/on-dms-pullrequest.yml
+++ b/.github/workflows/on-dms-pullrequest.yml
@@ -56,7 +56,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }}
@@ -125,7 +125,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }}
@@ -194,7 +194,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }} -IdentityProvider ${{matrix.identityprovider}}
@@ -287,7 +287,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }}
@@ -395,7 +395,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }}
diff --git a/.github/workflows/on-prerelease.yml b/.github/workflows/on-prerelease.yml
index 28b4067a7..a45f4bd9d 100644
--- a/.github/workflows/on-prerelease.yml
+++ b/.github/workflows/on-prerelease.yml
@@ -45,7 +45,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Set DMS Version Numbers
id: versions
@@ -344,7 +344,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Set Config Version Numbers
id: versions
diff --git a/.github/workflows/scheduled-build.yml b/.github/workflows/scheduled-build.yml
index 92e34c222..0d832f43f 100644
--- a/.github/workflows/scheduled-build.yml
+++ b/.github/workflows/scheduled-build.yml
@@ -56,7 +56,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Cache Nuget packages
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57
@@ -132,7 +132,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Build
run: ./build-dms.ps1 Build -Configuration ${{ env.CONFIGURATION }}
diff --git a/.github/workflows/scheduled-pre-image-test.yml b/.github/workflows/scheduled-pre-image-test.yml
index c9ac17c9d..0eaff3974 100644
--- a/.github/workflows/scheduled-pre-image-test.yml
+++ b/.github/workflows/scheduled-pre-image-test.yml
@@ -29,7 +29,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Cache Nuget packages
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2
diff --git a/.github/workflows/scheduled-smoke-test.yml b/.github/workflows/scheduled-smoke-test.yml
index f4c9a7791..d1339ba60 100644
--- a/.github/workflows/scheduled-smoke-test.yml
+++ b/.github/workflows/scheduled-smoke-test.yml
@@ -48,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Cache NuGet packages
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 420a2a974..fd6be6cf5 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -16,7 +16,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "DMS",
- "program": "${workspaceFolder}\\src\\dms\\frontend\\EdFi.DataManagementService.Frontend.AspNetCore\\bin\\Debug\\net8.0\\EdFi.DataManagementService.Frontend.AspNetCore.dll",
+ "program": "${workspaceFolder}\\src\\dms\\frontend\\EdFi.DataManagementService.Frontend.AspNetCore\\bin\\Debug\\net10.0\\EdFi.DataManagementService.Frontend.AspNetCore.dll",
"args": [],
"cwd": "${workspaceFolder}\\src\\dms\\frontend\\EdFi.DataManagementService.Frontend.AspNetCore",
"stopAtEntry": false,
@@ -28,7 +28,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Config",
- "program": "${workspaceFolder}\\src\\config\\frontend\\EdFi.DmsConfigurationService.Frontend.AspNetCore\\bin\\Debug\\net8.0\\EdFi.DmsConfigurationService.Frontend.AspNetCore.dll",
+ "program": "${workspaceFolder}\\src\\config\\frontend\\EdFi.DmsConfigurationService.Frontend.AspNetCore\\bin\\Debug\\net10.0\\EdFi.DmsConfigurationService.Frontend.AspNetCore.dll",
"args": [],
"cwd": "${workspaceFolder}\\src\\config\\frontend\\EdFi.DmsConfigurationService.Frontend.AspNetCore",
"stopAtEntry": false,
diff --git a/build-cli-generator.ps1 b/build-cli-generator.ps1
index 9ac6f4ea1..f865a7f27 100644
--- a/build-cli-generator.ps1
+++ b/build-cli-generator.ps1
@@ -184,7 +184,7 @@ function RunTests {
$Filter
)
- $testAssemblyPath = "$solutionRoot/$Filter/bin/$Configuration/net8.0/"
+ $testAssemblyPath = "$solutionRoot/$Filter/bin/$Configuration/net10.0/"
$testAssemblies = Get-ChildItem -Path $testAssemblyPath -Filter "$Filter.dll" -ErrorAction SilentlyContinue |
Sort-Object -Property { $_.Name.Length }
@@ -263,7 +263,7 @@ function RunSchemaGenerator {
$Arguments
)
- $exePath = "$cliProjectRoot/bin/$Configuration/net8.0/$projectName.exe"
+ $exePath = "$cliProjectRoot/bin/$Configuration/net10.0/$projectName.exe"
if (!(Test-Path $exePath)) {
Write-Warning "Schema Generator CLI executable not found at $exePath"
diff --git a/eng/CmsHierarchy/CmsHierarchy.csproj b/eng/CmsHierarchy/CmsHierarchy.csproj
index 1cdb93412..8c8b30e07 100644
--- a/eng/CmsHierarchy/CmsHierarchy.csproj
+++ b/eng/CmsHierarchy/CmsHierarchy.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/eng/sdkGen/EdFi.DmsApi.Sdk.nuspec b/eng/sdkGen/EdFi.DmsApi.Sdk.nuspec
index 72bfa53eb..70b9108ad 100644
--- a/eng/sdkGen/EdFi.DmsApi.Sdk.nuspec
+++ b/eng/sdkGen/EdFi.DmsApi.Sdk.nuspec
@@ -13,7 +13,7 @@
Copyright @ $year$ Ed-Fi Alliance, LLC and Contributors
Ed-Fi Data Management Service SDK
-
+
@@ -23,6 +23,6 @@
-
+
diff --git a/eng/sdkGen/EdFi.DmsApi.TestSdk.nuspec b/eng/sdkGen/EdFi.DmsApi.TestSdk.nuspec
index ea2815e72..4d850833e 100644
--- a/eng/sdkGen/EdFi.DmsApi.TestSdk.nuspec
+++ b/eng/sdkGen/EdFi.DmsApi.TestSdk.nuspec
@@ -13,7 +13,7 @@
Copyright @ $year$ Ed-Fi Alliance, LLC and Contributors
Ed-Fi Data Management Service TestSdk
-
+
@@ -23,6 +23,6 @@
-
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 0fc1c452e..07cc69203 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -4,7 +4,7 @@
-
+
@@ -17,10 +17,11 @@
-
+
-
+
+
@@ -35,18 +36,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -55,7 +55,7 @@
-
+
@@ -78,10 +78,10 @@
-
-
-
-
+
+
+
+
diff --git a/src/config/Dockerfile b/src/config/Dockerfile
index e2fe325c0..78663f118 100644
--- a/src/config/Dockerfile
+++ b/src/config/Dockerfile
@@ -3,7 +3,7 @@
# The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
# See the LICENSE and NOTICES files in the project root for more information.
-FROM mcr.microsoft.com/dotnet/sdk:8.0.401-alpine3.20@sha256:658c93223111638f9bb54746679e554b2cf0453d8fb7b9fed32c3c0726c210fe AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0.101-alpine3.23@sha256:b6e895da9d359739e8cff0c296c28eaac24697d44af63f3a7099a6fd0dfe3be7 AS build
WORKDIR /source
# Named context support https://github.com/hadolint/hadolint/issues/830
@@ -31,7 +31,7 @@ COPY datamodel/EdFi.DmsConfigurationService.DataModel/ ./datamodel/EdFi.DmsConfi
RUN dotnet publish frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/EdFi.DmsConfigurationService.Frontend.AspNetCore.csproj \
-c Release --no-restore --self-contained false -o /app/Frontend
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.12-alpine3.21@sha256:accc7352721d44ef6246e91704f4efb1954f69912af2d2bd54d117fa09922a53 AS runtimebase
+FROM mcr.microsoft.com/dotnet/aspnet:10.0.1-alpine3.23@sha256:2273b24ea865e253d5e3b66d0eae23ce53529946089819489410849ba62db12c AS runtimebase
# bash: used in startup script and debugging
# postgresql: used to test for PostgreSQL readiness
diff --git a/src/config/Nuget.Dockerfile b/src/config/Nuget.Dockerfile
index e4c124eac..d52787987 100644
--- a/src/config/Nuget.Dockerfile
+++ b/src/config/Nuget.Dockerfile
@@ -7,7 +7,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0.12-alpine3.21@sha256:accc7352721d44ef62
LABEL maintainer="Ed-Fi Alliance, LLC and Contributors "
-RUN apk --no-cache add postgresql16-client=~16
+RUN apk --no-cache add postgresql16-client
FROM runtimebase AS setup
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Installer/EdFi.DmsConfigurationService.Backend.Installer.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Installer/EdFi.DmsConfigurationService.Backend.Installer.csproj
index 6794ed042..87214ecb0 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Installer/EdFi.DmsConfigurationService.Backend.Installer.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Installer/EdFi.DmsConfigurationService.Backend.Installer.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/EdFi.DmsConfigurationService.Backend.Keycloak.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/EdFi.DmsConfigurationService.Backend.Keycloak.csproj
index 61d2bfae7..e654e7357 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/EdFi.DmsConfigurationService.Backend.Keycloak.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/EdFi.DmsConfigurationService.Backend.Keycloak.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/KeycloakClientRepository.cs b/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/KeycloakClientRepository.cs
index eabe7ff48..71c88d90c 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/KeycloakClientRepository.cs
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Keycloak/KeycloakClientRepository.cs
@@ -11,6 +11,7 @@
using Keycloak.Net.Models.ProtocolMappers;
using Keycloak.Net.Models.Roles;
using Microsoft.Extensions.Logging;
+using static EdFi.DmsConfigurationService.DataModel.LoggingUtility;
namespace EdFi.DmsConfigurationService.Backend.Keycloak;
@@ -105,7 +106,8 @@ public async Task CreateClientAsync(
}
logger.LogError(
- $"Error while creating the client: {clientId}. CreateClientAndRetrieveClientIdAsync returned empty string with no exception."
+ "Error while creating the client {ClientId}. CreateClientAndRetrieveClientIdAsync returned empty string with no exception.",
+ SanitizeForLog(clientId)
);
return new ClientCreateResult.FailureUnknown($"Error while creating the client: {clientId}");
}
@@ -319,7 +321,7 @@ public async Task UpdateClientAsync(
else
{
var scopeNotFound = $"Scope {scope} not found";
- logger.LogError(message: scopeNotFound);
+ logger.LogError("Specified scope {Scope} not found", SanitizeForLog(scope));
return new ClientUpdateResult.FailureIdentityProvider(
new IdentityProviderError(scopeNotFound)
);
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Mssql/EdFi.DmsConfigurationService.Backend.Mssql.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Mssql/EdFi.DmsConfigurationService.Backend.Mssql.csproj
index b296670ed..525320c35 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Mssql/EdFi.DmsConfigurationService.Backend.Mssql.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Mssql/EdFi.DmsConfigurationService.Backend.Mssql.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/EdFi.DmsConfigurationService.Backend.OpenIddict.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/EdFi.DmsConfigurationService.Backend.OpenIddict.csproj
index af8e9ea9b..ddd308a9f 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/EdFi.DmsConfigurationService.Backend.OpenIddict.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/EdFi.DmsConfigurationService.Backend.OpenIddict.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/Services/OpenIddictTokenManager.cs b/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/Services/OpenIddictTokenManager.cs
index 59205fdc6..969087aab 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/Services/OpenIddictTokenManager.cs
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.OpenIddict/Services/OpenIddictTokenManager.cs
@@ -109,7 +109,7 @@ private async Task LoadActiveSigningKeyFromCertificatesAsync()
}
else
{
- cert = new X509Certificate2(certPath, certPassword);
+ cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword);
}
var signingKey = new X509SecurityKey(cert);
return await Task.FromResult(
@@ -128,11 +128,8 @@ private async Task LoadActiveSigningKeyFromCertificatesAsync()
);
}
var cert = string.IsNullOrEmpty(certPassword)
- ? new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath)
- : new System.Security.Cryptography.X509Certificates.X509Certificate2(
- certPath,
- certPassword
- );
+ ? X509CertificateLoader.LoadCertificateFromFile(certPath)
+ : X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword);
var signingKey = new X509SecurityKey(cert);
return await Task.FromResult(
new SigningKeyResult { SecurityKey = signingKey, KeyId = cert.Thumbprint }
@@ -464,7 +461,7 @@ private async Task<
}
else
{
- cert = new X509Certificate2(certPath, certPassword);
+ cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword);
}
using var pubRsa = cert.GetRSAPublicKey();
return await Task.FromResult(new[] { (pubRsa!.ExportParameters(false), cert.Thumbprint) });
@@ -480,11 +477,8 @@ private async Task<
);
}
var cert = string.IsNullOrEmpty(certPassword)
- ? new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath)
- : new System.Security.Cryptography.X509Certificates.X509Certificate2(
- certPath,
- certPassword
- );
+ ? X509CertificateLoader.LoadCertificateFromFile(certPath)
+ : X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword);
using var pubRsa = cert.GetRSAPublicKey();
return await Task.FromResult(new[] { (pubRsa!.ExportParameters(false), cert.Thumbprint) });
}
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration.csproj
index b03e5216c..5fd6ed5c8 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration/EdFi.DmsConfigurationService.Backend.Postgresql.Tests.Integration.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql/EdFi.DmsConfigurationService.Backend.Postgresql.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql/EdFi.DmsConfigurationService.Backend.Postgresql.csproj
index aeb92449d..4fa168d07 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql/EdFi.DmsConfigurationService.Backend.Postgresql.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Postgresql/EdFi.DmsConfigurationService.Backend.Postgresql.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend.Tests.Unit/EdFi.DmsConfigurationService.Backend.Tests.Unit.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend.Tests.Unit/EdFi.DmsConfigurationService.Backend.Tests.Unit.csproj
index 449bde648..f0a6506ff 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend.Tests.Unit/EdFi.DmsConfigurationService.Backend.Tests.Unit.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend.Tests.Unit/EdFi.DmsConfigurationService.Backend.Tests.Unit.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/config/backend/EdFi.DmsConfigurationService.Backend/EdFi.DmsConfigurationService.Backend.csproj b/src/config/backend/EdFi.DmsConfigurationService.Backend/EdFi.DmsConfigurationService.Backend.csproj
index b172317dc..11d06f628 100644
--- a/src/config/backend/EdFi.DmsConfigurationService.Backend/EdFi.DmsConfigurationService.Backend.csproj
+++ b/src/config/backend/EdFi.DmsConfigurationService.Backend/EdFi.DmsConfigurationService.Backend.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/EdFi.DmsConfigurationService.DataModel.csproj b/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/EdFi.DmsConfigurationService.DataModel.csproj
index 567936fa2..f972e437d 100644
--- a/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/EdFi.DmsConfigurationService.DataModel.csproj
+++ b/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/EdFi.DmsConfigurationService.DataModel.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/LoggingUtility.cs b/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/LoggingUtility.cs
index 1a2d92e38..46ffacb01 100644
--- a/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/LoggingUtility.cs
+++ b/src/config/datamodel/EdFi.DmsConfigurationService.DataModel/LoggingUtility.cs
@@ -14,6 +14,7 @@ public static class LoggingUtility
/// Sanitizes a string for safe logging by allowing only safe characters.
/// Uses a whitelist approach to prevent log injection and log forging attacks.
/// Allows: letters, digits, spaces, and safe punctuation (_-.:/)
+ /// Explicitly excludes all control characters (ASCII < 32, including \r, \n, \t, etc.)
///
/// The input string to sanitize
/// A sanitized string containing only safe characters
@@ -24,16 +25,20 @@ public static string SanitizeForLog(string? input)
return string.Empty;
}
// Whitelist approach: only allow alphanumeric characters and specific safe symbols
+ // Explicitly reject control characters for defense in depth
return new string(
input
.Where(c =>
- char.IsLetterOrDigit(c)
- || c == ' '
- || c == '_'
- || c == '-'
- || c == '.'
- || c == ':'
- || c == '/'
+ !char.IsControl(c)
+ && (
+ char.IsLetterOrDigit(c)
+ || c == ' '
+ || c == '_'
+ || c == '-'
+ || c == '.'
+ || c == ':'
+ || c == '/'
+ )
)
.ToArray()
);
diff --git a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit.csproj b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit.csproj
index 6f68501ed..0b2d6a7c6 100644
--- a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit.csproj
+++ b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit/EdFi.DmsConfigurationService.Frontend.AspNetCore.Tests.Unit.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/EdFi.DmsConfigurationService.Frontend.AspNetCore.csproj b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/EdFi.DmsConfigurationService.Frontend.AspNetCore.csproj
index d70b2f300..3d2292480 100644
--- a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/EdFi.DmsConfigurationService.Frontend.AspNetCore.csproj
+++ b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/EdFi.DmsConfigurationService.Frontend.AspNetCore.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
@@ -23,7 +23,6 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Modules/IdentityModule.cs b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Modules/IdentityModule.cs
index e0c16981f..a1420a100 100644
--- a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Modules/IdentityModule.cs
+++ b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Modules/IdentityModule.cs
@@ -38,12 +38,25 @@ public void MapEndpoints(IEndpointRouteBuilder endpoints)
private async Task RegisterClient(
RegisterRequest.Validator validator,
- [FromForm] RegisterRequest model,
IIdentityProviderRepository clientRepository,
IOptions identitySettings,
HttpContext httpContext
)
{
+ // Manually read form data to handle empty form bodies in .NET 10
+ // (Minimal API [FromForm] binding returns 400 with empty body before handler is invoked)
+ RegisterRequest model = new();
+ if (httpContext.Request.HasFormContentType)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
+ model = new RegisterRequest
+ {
+ ClientId = form["ClientId"].ToString(),
+ ClientSecret = form["ClientSecret"].ToString(),
+ DisplayName = form["DisplayName"].ToString(),
+ };
+ }
+
bool allowRegistration = identitySettings.Value.AllowRegistration;
if (allowRegistration)
{
@@ -117,7 +130,6 @@ bool IsUnique(ClientClientsResult.Success clientSuccess)
private static async Task GetClientAccessToken(
TokenRequest.Validator validator,
- [FromForm] TokenRequest model,
[FromServices] ITokenManager tokenManager,
[FromServices] IConfiguration configuration,
[FromServices] ILogger logger,
@@ -128,15 +140,16 @@ HttpContext httpContext
configuration.GetValue("AppSettings:IdentityProvider")?.ToLowerInvariant()
?? "self-contained";
- // For self-contained mode, support both form and HTTP Basic authentication
+ // Manually read form data to handle empty form bodies in .NET 10
+ // (Minimal API [FromForm] binding returns 400 with empty body before handler is invoked)
+ string clientId = string.Empty;
+ string clientSecret = string.Empty;
+ string grantType = string.Empty;
+ string scope = string.Empty;
+
+ // For self-contained mode, support HTTP Basic authentication
if (string.Equals(identityProvider, "self-contained", StringComparison.OrdinalIgnoreCase))
{
- // Extract client credentials from either form body or Authorization header
- string clientId = model.client_id ?? string.Empty;
- string clientSecret = model.client_secret ?? string.Empty;
- string grantType = model.grant_type ?? string.Empty;
- string scope = model.scope ?? string.Empty;
-
// Check for Authorization header (HTTP Basic auth) - only for self-contained
httpContext.Request.Headers.TryGetValue("Authorization", out var authHeader);
if (
@@ -162,42 +175,41 @@ HttpContext httpContext
logger.LogWarning("Failed to parse Basic Auth credentials: {Exception}", ex);
}
}
+ }
- // Read form data for other parameters (and as fallback for credentials)
- if (httpContext.Request.HasFormContentType)
- {
- var form = await httpContext.Request.ReadFormAsync();
-
- // Use form credentials if Basic auth didn't provide them
- if (string.IsNullOrEmpty(clientId))
- {
- clientId = form["client_id"].ToString();
- }
- if (string.IsNullOrEmpty(clientSecret))
- {
- clientSecret = form["client_secret"].ToString();
- }
+ // Read form data for all parameters (and as fallback for credentials in self-contained mode)
+ if (httpContext.Request.HasFormContentType)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
- if (string.IsNullOrEmpty(grantType))
- {
- grantType = form["grant_type"].ToString();
- }
- if (string.IsNullOrEmpty(scope))
- {
- scope = form["scope"].ToString();
- }
+ // Use form credentials if Basic auth didn't provide them
+ if (string.IsNullOrEmpty(clientId))
+ {
+ clientId = form["client_id"].ToString();
+ }
+ if (string.IsNullOrEmpty(clientSecret))
+ {
+ clientSecret = form["client_secret"].ToString();
}
- // Create updated model for self-contained validation
- model = new TokenRequest
+ if (string.IsNullOrEmpty(grantType))
{
- client_id = clientId,
- client_secret = clientSecret,
- grant_type = grantType,
- scope = scope,
- };
+ grantType = form["grant_type"].ToString();
+ }
+ if (string.IsNullOrEmpty(scope))
+ {
+ scope = form["scope"].ToString();
+ }
}
+ var model = new TokenRequest
+ {
+ client_id = clientId,
+ client_secret = clientSecret,
+ grant_type = grantType,
+ scope = scope,
+ };
+
await validator.GuardAsync(model);
// Validate grant type (OAuth 2.0 compliance)
@@ -258,11 +270,22 @@ HttpContext httpContext
}
private static async Task IntrospectToken(
- [FromForm] IntrospectionRequest model,
[FromServices] IEnhancedTokenValidator? tokenValidator,
HttpContext httpContext
)
{
+ // Manually read form data to handle empty form bodies in .NET 10
+ IntrospectionRequest model = new();
+ if (httpContext.Request.HasFormContentType)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
+ model = new IntrospectionRequest
+ {
+ Token = form["token"].ToString(),
+ Token_Type_Hint = form["token_type_hint"].ToString(),
+ };
+ }
+
if (string.IsNullOrEmpty(model.Token))
{
return Results.Json(
@@ -305,11 +328,22 @@ HttpContext httpContext
}
private static async Task RevokeToken(
- [FromForm] RevocationRequest model,
[FromServices] ITokenManager tokenManager,
HttpContext httpContext
)
{
+ // Manually read form data to handle empty form bodies in .NET 10
+ RevocationRequest model = new();
+ if (httpContext.Request.HasFormContentType)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
+ model = new RevocationRequest
+ {
+ Token = form["token"].ToString(),
+ Token_Type_Hint = form["token_type_hint"].ToString(),
+ };
+ }
+
if (string.IsNullOrEmpty(model.Token))
{
return Results.Json(
diff --git a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Program.cs b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Program.cs
index 1955ad403..a0b913db6 100644
--- a/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Program.cs
+++ b/src/config/frontend/EdFi.DmsConfigurationService.Frontend.AspNetCore/Program.cs
@@ -40,7 +40,7 @@
| ForwardedHeaders.XForwardedProto;
// Accept forwarded headers from any network and proxy
- options.KnownNetworks.Clear();
+ options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});
}
diff --git a/src/config/tests/EdFi.DmsConfigurationService.Tests.E2E/EdFi.DmsConfigurationService.Tests.E2E.csproj b/src/config/tests/EdFi.DmsConfigurationService.Tests.E2E/EdFi.DmsConfigurationService.Tests.E2E.csproj
index fc9ede7f4..e916fcead 100644
--- a/src/config/tests/EdFi.DmsConfigurationService.Tests.E2E/EdFi.DmsConfigurationService.Tests.E2E.csproj
+++ b/src/config/tests/EdFi.DmsConfigurationService.Tests.E2E/EdFi.DmsConfigurationService.Tests.E2E.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/Dockerfile b/src/dms/Dockerfile
index 038031377..150d2bf0b 100644
--- a/src/dms/Dockerfile
+++ b/src/dms/Dockerfile
@@ -3,7 +3,7 @@
# The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
# See the LICENSE and NOTICES files in the project root for more information.
-FROM mcr.microsoft.com/dotnet/sdk:8.0.401-alpine3.20@sha256:658c93223111638f9bb54746679e554b2cf0453d8fb7b9fed32c3c0726c210fe AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0.101-alpine3.23@sha256:b6e895da9d359739e8cff0c296c28eaac24697d44af63f3a7099a6fd0dfe3be7 AS build
WORKDIR /source
@@ -40,12 +40,12 @@ RUN dotnet publish frontend/EdFi.DataManagementService.Frontend.AspNetCore/EdFi.
dotnet publish clis/EdFi.DataManagementService.ApiSchemaDownloader/EdFi.DataManagementService.ApiSchemaDownloader.csproj \
-c Release --no-restore --self-contained false -o /app/ApiSchemaDownloader
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.12-alpine3.21@sha256:accc7352721d44ef6246e91704f4efb1954f69912af2d2bd54d117fa09922a53 AS runtimebase
+FROM mcr.microsoft.com/dotnet/aspnet:10.0.1-alpine3.23@sha256:2273b24ea865e253d5e3b66d0eae23ce53529946089819489410849ba62db12c AS runtimebase
# bash: used in startup script and debugging
# postgresql: used to test for PostgreSQL readiness
# jq: used to parse plugins defined in JSON env variable
-RUN apk --no-cache add bash=~5 postgresql16-client=~16 jq=~1.7
+RUN apk --no-cache add bash=~5 postgresql16-client=~16 jq=~1.8
FROM runtimebase AS setup
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Installer/EdFi.DataManagementService.Backend.Installer.csproj b/src/dms/backend/EdFi.DataManagementService.Backend.Installer/EdFi.DataManagementService.Backend.Installer.csproj
index 798d0d2eb..0a991a256 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Installer/EdFi.DataManagementService.Backend.Installer.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Installer/EdFi.DataManagementService.Backend.Installer.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Mssql/EdFi.DataManagementService.Backend.Mssql.csproj b/src/dms/backend/EdFi.DataManagementService.Backend.Mssql/EdFi.DataManagementService.Backend.Mssql.csproj
index 3196fd2e0..9bacb56a7 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Mssql/EdFi.DataManagementService.Backend.Mssql.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Mssql/EdFi.DataManagementService.Backend.Mssql.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration.csproj b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration.csproj
index de907f4ce..cf0b953b0 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/EdFi.DataManagementService.Backend.Postgresql.csproj b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/EdFi.DataManagementService.Backend.Postgresql.csproj
index 57dd5a5e9..9c72f7da5 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/EdFi.DataManagementService.Backend.Postgresql.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/EdFi.DataManagementService.Backend.Postgresql.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/Operation/SqlAction.cs b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/Operation/SqlAction.cs
index 9cc2a66c0..2b2b6c8df 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/Operation/SqlAction.cs
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Postgresql/Operation/SqlAction.cs
@@ -703,7 +703,8 @@ TraceId traceId
{
Trace.Assert(
bulkReferences.ReferentialIds.Length == bulkReferences.ReferentialPartitionKeys.Length,
- "Arrays of ReferentialIds and ReferentialPartitionKeys must be the same length"
+ "Arrays of ReferentialIds and ReferentialPartitionKeys must be the same length",
+ ""
);
// Ensure we do not send redundant referential rows to the database.
@@ -757,7 +758,8 @@ TraceId traceId
{
Trace.Assert(
referentialIds.Length == referentialPartitionKeys.Length,
- "Arrays of ReferentialIds and ReferentialPartitionKeys must be the same length"
+ "Arrays of ReferentialIds and ReferentialPartitionKeys must be the same length",
+ ""
);
if (referentialIds.Length == 0)
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/EdFi.DataManagementService.Backend.Tests.Unit.csproj b/src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/EdFi.DataManagementService.Backend.Tests.Unit.csproj
index e152e32f8..60b615398 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/EdFi.DataManagementService.Backend.Tests.Unit.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/EdFi.DataManagementService.Backend.Tests.Unit.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/backend/EdFi.DataManagementService.Backend/EdFi.DataManagementService.Backend.csproj b/src/dms/backend/EdFi.DataManagementService.Backend/EdFi.DataManagementService.Backend.csproj
index 02eadee0d..7f053739d 100644
--- a/src/dms/backend/EdFi.DataManagementService.Backend/EdFi.DataManagementService.Backend.csproj
+++ b/src/dms/backend/EdFi.DataManagementService.Backend/EdFi.DataManagementService.Backend.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
diff --git a/src/dms/clis/Dockerfile.SchemaGenerator b/src/dms/clis/Dockerfile.SchemaGenerator
index b159ae247..c329994d4 100644
--- a/src/dms/clis/Dockerfile.SchemaGenerator
+++ b/src/dms/clis/Dockerfile.SchemaGenerator
@@ -3,7 +3,7 @@
# The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
# See the LICENSE and NOTICES files in the project root for more information.
-FROM mcr.microsoft.com/dotnet/sdk:8.0.401-alpine3.20@sha256:658c93223111638f9bb54746679e554b2cf0453d8fb7b9fed32c3c0726c210fe AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0.101-alpine3.23@sha256:b6e895da9d359739e8cff0c296c28eaac24697d44af63f3a7099a6fd0dfe3be7 AS build
WORKDIR /source
@@ -34,7 +34,7 @@ RUN export NUGET_FALLBACK_PACKAGES="" && \
dotnet publish EdFi.DataManagementService.SchemaGenerator.Cli/EdFi.DataManagementService.SchemaGenerator.Cli.csproj \
-c Release --self-contained false -o /app/cli --disable-parallel --verbosity normal
-FROM mcr.microsoft.com/dotnet/runtime:8.0.12-alpine3.21 AS runtime
+FROM mcr.microsoft.com/dotnet/runtime:10.0.1-alpine3.23@sha256:2206e344229c441d5b083a023f0f22e0a0e3491c0fa134a8f63d7b3ece80341b AS runtime
WORKDIR /app
diff --git a/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration.csproj b/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration.csproj
index ce1d89dec..166f7d79e 100644
--- a/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration/EdFi.DataManagementService.ApiSchemaDownloader.Tests.Integration.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader/EdFi.DataManagementService.ApiSchemaDownloader.csproj b/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader/EdFi.DataManagementService.ApiSchemaDownloader.csproj
index b3591e05e..84ad7bd0b 100644
--- a/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader/EdFi.DataManagementService.ApiSchemaDownloader.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.ApiSchemaDownloader/EdFi.DataManagementService.ApiSchemaDownloader.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration.csproj b/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration.csproj
index 2b4ad388e..4991be0dc 100644
--- a/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration/EdFi.DataManagementService.OpenApiGenerator.Tests.Integration.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator/EdFi.DataManagementService.OpenApiGenerator.csproj b/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator/EdFi.DataManagementService.OpenApiGenerator.csproj
index 8f17e7dc7..6d96daf86 100644
--- a/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator/EdFi.DataManagementService.OpenApiGenerator.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.OpenApiGenerator/EdFi.DataManagementService.OpenApiGenerator.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net10.0
enable
enable
true
diff --git a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Abstractions/EdFi.DataManagementService.SchemaGenerator.Abstractions.csproj b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Abstractions/EdFi.DataManagementService.SchemaGenerator.Abstractions.csproj
index fa71b7ae6..b76014470 100644
--- a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Abstractions/EdFi.DataManagementService.SchemaGenerator.Abstractions.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Abstractions/EdFi.DataManagementService.SchemaGenerator.Abstractions.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Cli/EdFi.DataManagementService.SchemaGenerator.Cli.csproj b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Cli/EdFi.DataManagementService.SchemaGenerator.Cli.csproj
index ca8f5cd7f..39a905381 100644
--- a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Cli/EdFi.DataManagementService.SchemaGenerator.Cli.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Cli/EdFi.DataManagementService.SchemaGenerator.Cli.csproj
@@ -28,7 +28,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Mssql/EdFi.DataManagementService.SchemaGenerator.Mssql.csproj b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Mssql/EdFi.DataManagementService.SchemaGenerator.Mssql.csproj
index c94c9bd2f..0097f96ba 100644
--- a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Mssql/EdFi.DataManagementService.SchemaGenerator.Mssql.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Mssql/EdFi.DataManagementService.SchemaGenerator.Mssql.csproj
@@ -14,7 +14,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Pgsql/EdFi.DataManagementService.SchemaGenerator.Pgsql.csproj b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Pgsql/EdFi.DataManagementService.SchemaGenerator.Pgsql.csproj
index c94c9bd2f..0097f96ba 100644
--- a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Pgsql/EdFi.DataManagementService.SchemaGenerator.Pgsql.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Pgsql/EdFi.DataManagementService.SchemaGenerator.Pgsql.csproj
@@ -14,7 +14,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Tests.Unit/EdFi.DataManagementService.SchemaGenerator.Tests.Unit.csproj b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Tests.Unit/EdFi.DataManagementService.SchemaGenerator.Tests.Unit.csproj
index 35367430e..e9a33c554 100644
--- a/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Tests.Unit/EdFi.DataManagementService.SchemaGenerator.Tests.Unit.csproj
+++ b/src/dms/clis/EdFi.DataManagementService.SchemaGenerator.Tests.Unit/EdFi.DataManagementService.SchemaGenerator.Tests.Unit.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/src/dms/core/EdFi.DataManagementService.Core.External/EdFi.DataManagementService.Core.External.csproj b/src/dms/core/EdFi.DataManagementService.Core.External/EdFi.DataManagementService.Core.External.csproj
index 1b5d8e24d..cfb311567 100644
--- a/src/dms/core/EdFi.DataManagementService.Core.External/EdFi.DataManagementService.Core.External.csproj
+++ b/src/dms/core/EdFi.DataManagementService.Core.External/EdFi.DataManagementService.Core.External.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
diff --git a/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/EdFi.DataManagementService.Core.Tests.Unit.csproj b/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/EdFi.DataManagementService.Core.Tests.Unit.csproj
index 0734a8020..66df57e71 100644
--- a/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/EdFi.DataManagementService.Core.Tests.Unit.csproj
+++ b/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/EdFi.DataManagementService.Core.Tests.Unit.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/OAuth/OAuthManagerTests.cs b/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/OAuth/OAuthManagerTests.cs
index a2066b602..9b4728b63 100644
--- a/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/OAuth/OAuthManagerTests.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/OAuth/OAuthManagerTests.cs
@@ -122,8 +122,8 @@ public void Then_The_Content_Type_Must_Have_Been_UrlEncoded()
A.That.Matches(m =>
m.Content != null
&& m.Content.Headers.ContentType != null
- && m.Content!.Headers.ContentType!.ToString()
- == "application/x-www-form-urlencoded; charset=utf-8"
+ && m.Content!.Headers.ContentType!.MediaType
+ == "application/x-www-form-urlencoded"
)
)
)
diff --git a/src/dms/core/EdFi.DataManagementService.Core/EdFi.DataManagementService.Core.csproj b/src/dms/core/EdFi.DataManagementService.Core/EdFi.DataManagementService.Core.csproj
index 02a202ee2..93dee0fea 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/EdFi.DataManagementService.Core.csproj
+++ b/src/dms/core/EdFi.DataManagementService.Core/EdFi.DataManagementService.Core.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Extraction/ReferenceExtractor.cs b/src/dms/core/EdFi.DataManagementService.Core/Extraction/ReferenceExtractor.cs
index e46caeccb..315c9fbdf 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Extraction/ReferenceExtractor.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Extraction/ReferenceExtractor.cs
@@ -64,7 +64,8 @@ ILogger logger
// Number of document values from resolved JsonPaths should all be the same, otherwise something is very wrong
Trace.Assert(
Array.TrueForAll(intermediateReferenceElements, x => x.ValueSlice.Length == valueSliceLength),
- "Length of document value slices are not equal"
+ "Length of document value slices are not equal",
+ ""
);
// If a JsonPath selection had no results, we can assume an optional reference wasn't there
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Handler/UpdateByIdHandler.cs b/src/dms/core/EdFi.DataManagementService.Core/Handler/UpdateByIdHandler.cs
index b1bda8e62..d93c99586 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Handler/UpdateByIdHandler.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Handler/UpdateByIdHandler.cs
@@ -34,7 +34,7 @@ IAuthorizationServiceFactory authorizationServiceFactory
public async Task Execute(RequestInfo requestInfo, Func next)
{
_logger.LogDebug("Entering UpdateByIdHandler - {TraceId}", requestInfo.FrontendRequest.TraceId.Value);
- Trace.Assert(requestInfo.ParsedBody != null, "Unexpected null Body on Frontend Request from PUT");
+ Trace.Assert(requestInfo.ParsedBody != null, "Unexpected null Body on Frontend Request from PUT", "");
// Resolve repository from service provider within request scope
var documentStoreRepository = _serviceProvider.GetRequiredService();
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Middleware/DuplicatePropertiesMiddleware.cs b/src/dms/core/EdFi.DataManagementService.Core/Middleware/DuplicatePropertiesMiddleware.cs
index 399fdca08..dc1b3bfff 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Middleware/DuplicatePropertiesMiddleware.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Middleware/DuplicatePropertiesMiddleware.cs
@@ -3,8 +3,8 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.
-using System.Text.Json.Nodes;
-using System.Text.RegularExpressions;
+using System.Text;
+using System.Text.Json;
using EdFi.DataManagementService.Core.Model;
using EdFi.DataManagementService.Core.Pipeline;
using Microsoft.Extensions.Logging;
@@ -12,14 +12,13 @@
namespace EdFi.DataManagementService.Core.Middleware;
-/// Parse did not find errors in repeated values, it is identified until an attempt is made to use the JsonNode
-/// Please see https://github.com/dotnet/runtime/issues/70604 for information on the JsonNode bug this code is working-around.
+///
+/// Middleware that detects duplicate property names in JSON request bodies.
+/// This uses Utf8JsonReader to scan the raw JSON and detect duplicates with their exact paths,
+/// which is necessary because System.Text.Json's JsonNode silently overwrites duplicate properties.
+///
internal class DuplicatePropertiesMiddleware(ILogger logger) : IPipelineStep
{
- // This key should never exist in the document
- private const string TestForDuplicateObjectKeyWorkaround = "x";
- private const string Pattern = @"Key: (.*?) \((.*?)\)\.(.*?)$";
-
public async Task Execute(RequestInfo requestInfo, Func next)
{
logger.LogDebug(
@@ -27,51 +26,20 @@ public async Task Execute(RequestInfo requestInfo, Func next)
requestInfo.FrontendRequest.TraceId.Value
);
- if (requestInfo.ParsedBody != null)
+ if (!string.IsNullOrEmpty(requestInfo.FrontendRequest.Body))
{
- try
- {
- if (requestInfo.ParsedBody is JsonObject jsonObject)
- {
- // This validation will identify if the problem is at the first level. It does not identify if it is at a second or third level.
- _ = requestInfo.ParsedBody[TestForDuplicateObjectKeyWorkaround];
+ string? duplicatePath = FindDuplicatePropertyPath(requestInfo.FrontendRequest.Body);
- // If you are in this line there are no First level exceptions, recursively check the rest of the body to find the first exception
- CheckForDuplicateProperties(jsonObject, "$");
- }
- }
- catch (ArgumentException ae)
+ if (duplicatePath != null)
{
- // This match works when the error is after the first level of node
- // e.g. An item with the same key has already been added. Key: classPeriodName (Parameter 'propertyName').$.classPeriods[0].classPeriodReference
- Match match = Regex.Match(ae.Message, Pattern);
-
- string propertyName;
- if (match.Success)
- {
- string keyName = match.Groups[1].Value;
- string errorPath = match.Groups[3].Value;
- propertyName = $"{errorPath}.{keyName}";
- }
- else
- {
- var propertyNameMatch = Regex.Match(
- ae.Message,
- @"Key: (\w+) \(Parameter 'propertyName'\)"
- );
- propertyName = propertyNameMatch.Success
- ? "$." + propertyNameMatch.Groups[1].Value
- : "unknown";
- }
-
var validationErrors = new Dictionary
{
- { $"{propertyName}", new[] { "An item with the same key has already been added." } },
+ { duplicatePath, ["An item with the same key has already been added."] },
};
logger.LogDebug(
- ae,
- "Duplicate key found - {TraceId}",
+ "Duplicate key found at {DuplicatePath} - {TraceId}",
+ duplicatePath,
requestInfo.FrontendRequest.TraceId.Value
);
@@ -87,57 +55,148 @@ public async Task Execute(RequestInfo requestInfo, Func next)
);
return;
}
- catch (Exception e)
- {
- logger.LogDebug(
- e,
- "Unable to evaluate the request body - {TraceId}",
- requestInfo.FrontendRequest.TraceId.Value
- );
- return;
- }
}
await next();
}
- private void CheckForDuplicateProperties(JsonNode node, string path)
+ ///
+ /// Scans the raw JSON string using Utf8JsonReader to find duplicate property names.
+ /// Returns the JSON path of the first duplicate found, or null if no duplicates exist.
+ ///
+ private static string? FindDuplicatePropertyPath(string json)
{
- if (node is JsonObject jsonObject)
+ var bytes = Encoding.UTF8.GetBytes(json);
+ var reader = new Utf8JsonReader(
+ bytes,
+ new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip }
+ );
+
+ // Stack to track the path to current location in the JSON
+ var pathStack = new Stack();
+ // Stack to track property names seen at each object nesting level (for duplicate detection)
+ var propertyNamesStack = new Stack>();
+
+ while (reader.Read())
{
- foreach (var property in jsonObject)
+ switch (reader.TokenType)
{
- string propertyPath = $"{path}.{property.Key}";
- try
- {
- if (property.Value is JsonObject nestedObject)
+ case JsonTokenType.StartObject:
+ propertyNamesStack.Push([]);
+ break;
+
+ case JsonTokenType.EndObject:
+ if (propertyNamesStack.Count > 0)
{
- CheckForDuplicateProperties(nestedObject, propertyPath);
+ propertyNamesStack.Pop();
}
- else if (property.Value is JsonArray jsonArray)
+ // Pop the property segment that led to this object (if any)
+ if (pathStack.Count > 0 && !pathStack.Peek().IsArray)
{
- for (int i = 0; i < jsonArray.Count; i++)
+ pathStack.Pop();
+ }
+ break;
+
+ case JsonTokenType.StartArray:
+ // Push an array segment to track indices
+ pathStack.Push(new PathSegment(IsArray: true, PropertyName: null, ArrayIndex: 0));
+ break;
+
+ case JsonTokenType.EndArray:
+ // Pop the array segment
+ if (pathStack.Count > 0 && pathStack.Peek().IsArray)
+ {
+ pathStack.Pop();
+ }
+ // Pop the property that led to this array (if any)
+ if (pathStack.Count > 0 && !pathStack.Peek().IsArray)
+ {
+ pathStack.Pop();
+ }
+ break;
+
+ case JsonTokenType.PropertyName:
+ string propertyName = reader.GetString()!;
+
+ // Check for duplicate in current object BEFORE adding to path
+ if (propertyNamesStack.Count > 0)
+ {
+ var currentProperties = propertyNamesStack.Peek();
+ if (!currentProperties.Add(propertyName))
{
- var itemPath = $"{propertyPath}[{i}]";
- var item = jsonArray[i];
- if (item is JsonObject)
- {
- CheckForDuplicateProperties(item, itemPath);
- }
+ // Duplicate found! Build the path (don't include the duplicate itself in the stack)
+ return BuildJsonPath(pathStack, propertyName);
}
}
- }
- catch (ArgumentException ex)
- {
- string detailedPath = string.Empty;
- // To avoid when it enters more than one time and the path is modified
- if (!ex.Message.Contains(propertyPath))
+
+ // Push this property onto the path stack (will be popped when its value ends)
+ pathStack.Push(
+ new PathSegment(IsArray: false, PropertyName: propertyName, ArrayIndex: 0)
+ );
+ break;
+
+ case JsonTokenType.String:
+ case JsonTokenType.Number:
+ case JsonTokenType.True:
+ case JsonTokenType.False:
+ case JsonTokenType.Null:
+ // Primitive value encountered - pop the property that led here
+ if (pathStack.Count > 0 && !pathStack.Peek().IsArray)
{
- detailedPath = "." + propertyPath;
+ pathStack.Pop();
}
- throw new ArgumentException(ex.Message + detailedPath);
- }
+ // If we're in an array, increment the index for the next element
+ if (pathStack.Count > 0 && pathStack.Peek().IsArray)
+ {
+ var arraySegment = pathStack.Pop();
+ pathStack.Push(arraySegment with { ArrayIndex = arraySegment.ArrayIndex + 1 });
+ }
+ break;
+ }
+
+ // After completing an object or array that was an array element, increment the array index
+ if (
+ (reader.TokenType == JsonTokenType.EndObject || reader.TokenType == JsonTokenType.EndArray)
+ && pathStack.Count > 0
+ && pathStack.Peek().IsArray
+ )
+ {
+ var arraySegment = pathStack.Pop();
+ pathStack.Push(arraySegment with { ArrayIndex = arraySegment.ArrayIndex + 1 });
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Builds a JSON path string from the path stack and the duplicate property name.
+ ///
+ private static string BuildJsonPath(Stack pathStack, string duplicatePropertyName)
+ {
+ var segments = pathStack.ToArray();
+ Array.Reverse(segments);
+
+ var pathBuilder = new StringBuilder("$");
+
+ foreach (var segment in segments)
+ {
+ if (segment.IsArray)
+ {
+ pathBuilder.Append($"[{segment.ArrayIndex}]");
+ }
+ else if (segment.PropertyName != null)
+ {
+ pathBuilder.Append($".{segment.PropertyName}");
}
}
+
+ pathBuilder.Append($".{duplicatePropertyName}");
+ return pathBuilder.ToString();
}
+
+ ///
+ /// Represents a segment in the JSON path being tracked.
+ ///
+ private sealed record PathSegment(bool IsArray, string? PropertyName, int ArrayIndex);
}
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentInfoMiddleware.cs b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentInfoMiddleware.cs
index 30f9a1c98..5dae017dc 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentInfoMiddleware.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentInfoMiddleware.cs
@@ -27,7 +27,7 @@ public async Task Execute(RequestInfo requestInfo, Func next)
requestInfo.FrontendRequest.TraceId.Value
);
- Trace.Assert(requestInfo.ParsedBody != null, "Body was null, pipeline config invalid");
+ Trace.Assert(requestInfo.ParsedBody != null, "Body was null, pipeline config invalid", "");
var (documentIdentity, superclassIdentity) = requestInfo.ResourceSchema.ExtractIdentities(
requestInfo.ParsedBody,
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentSecurityElementsMiddleware.cs b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentSecurityElementsMiddleware.cs
index 8afb7b7d1..4603454fb 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentSecurityElementsMiddleware.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ExtractDocumentSecurityElementsMiddleware.cs
@@ -25,7 +25,7 @@ public async Task Execute(RequestInfo requestInfo, Func next)
requestInfo.FrontendRequest.TraceId.Value
);
- Trace.Assert(requestInfo.ParsedBody != null, "Body was null, pipeline config invalid");
+ Trace.Assert(requestInfo.ParsedBody != null, "Body was null, pipeline config invalid", "");
requestInfo.DocumentSecurityElements = requestInfo.ResourceSchema.ExtractSecurityElements(
requestInfo.ParsedBody,
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ParseBodyMiddleware.cs b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ParseBodyMiddleware.cs
index 6b584dc51..11a1ff0b4 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ParseBodyMiddleware.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ParseBodyMiddleware.cs
@@ -70,7 +70,7 @@ public async Task Execute(RequestInfo requestInfo, Func next)
JsonNode? body = JsonNode.Parse(requestInfo.FrontendRequest.Body);
- Trace.Assert(body != null, "Unable to parse JSON");
+ Trace.Assert(body != null, "Unable to parse JSON", "");
requestInfo.ParsedBody = body;
}
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ResourceActionAuthorizationMiddleware.cs b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ResourceActionAuthorizationMiddleware.cs
index b83d03737..cb559cda0 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Middleware/ResourceActionAuthorizationMiddleware.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Middleware/ResourceActionAuthorizationMiddleware.cs
@@ -55,7 +55,8 @@ public async Task Execute(RequestInfo requestInfo, Func next)
Debug.Assert(
requestInfo.PathComponents != null,
- "ResourceActionAuthorizationMiddleware: There should be PathComponents"
+ "ResourceActionAuthorizationMiddleware: There should be PathComponents",
+ ""
);
if (!ValidateResourceClaims(requestInfo, claimSet))
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Model/DescriptorDocument.cs b/src/dms/core/EdFi.DataManagementService.Core/Model/DescriptorDocument.cs
index b2ac2d5c9..0935e76ff 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Model/DescriptorDocument.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Model/DescriptorDocument.cs
@@ -26,13 +26,15 @@ public DocumentIdentity ToDocumentIdentity()
string? namespaceName = _document["namespace"]?.GetValue();
Debug.Assert(
namespaceName != null,
- "Failed getting namespace field, JSON schema validation not in pipeline?"
+ "Failed getting namespace field, JSON schema validation not in pipeline?",
+ ""
);
string? codeValue = _document["codeValue"]?.GetValue();
Debug.Assert(
codeValue != null,
- "Failed getting codeValue field, JSON schema validation not in pipeline?"
+ "Failed getting codeValue field, JSON schema validation not in pipeline?",
+ ""
);
DocumentIdentityElement[] descriptorElement =
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Model/SchoolYearEnumerationDocument.cs b/src/dms/core/EdFi.DataManagementService.Core/Model/SchoolYearEnumerationDocument.cs
index 5a7bdb3f8..856dd0712 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Model/SchoolYearEnumerationDocument.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Model/SchoolYearEnumerationDocument.cs
@@ -26,7 +26,8 @@ public DocumentIdentity ToDocumentIdentity()
Debug.Assert(
schoolYearNode != null,
- "Failed getting schoolYear field, JSON schema validation not in pipeline?"
+ "Failed getting schoolYear field, JSON schema validation not in pipeline?",
+ ""
);
DocumentIdentityElement[] schoolYearEnumerationElement =
diff --git a/src/dms/core/EdFi.DataManagementService.Core/OAuth/OAuthManager.cs b/src/dms/core/EdFi.DataManagementService.Core/OAuth/OAuthManager.cs
index a42abd796..dafddccec 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/OAuth/OAuthManager.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/OAuth/OAuthManager.cs
@@ -47,10 +47,9 @@ TraceId traceId
HttpRequestMessage upstreamRequest = new(HttpMethod.Post, upstreamUri);
upstreamRequest.Headers.Add("Authorization", authHeaderString);
- upstreamRequest.Content = new StringContent(
- $"grant_type={grantType}",
- Encoding.UTF8,
- "application/x-www-form-urlencoded"
+ // Use FormUrlEncodedContent for proper URL encoding of user-provided values
+ upstreamRequest.Content = new FormUrlEncodedContent(
+ [new KeyValuePair("grant_type", grantType)]
);
// In case of 5xx Error, pass 503 Service unavailable to client, otherwise forward response directly to client.
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Utilities/LoggingSanitizer.cs b/src/dms/core/EdFi.DataManagementService.Core/Utilities/LoggingSanitizer.cs
index 7e5ffe19a..cb5730708 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Utilities/LoggingSanitizer.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Utilities/LoggingSanitizer.cs
@@ -13,6 +13,7 @@ public static class LoggingSanitizer
///
/// Sanitizes input strings to prevent log injection attacks using a whitelist approach.
/// Only allows alphanumeric characters, spaces, and safe punctuation (_-.:/).
+ /// Explicitly excludes all control characters (ASCII < 32, including \r, \n, \t, etc.)
/// This prevents log forging, template injection, and other log-based attacks.
///
/// The input string to sanitize
@@ -71,6 +72,7 @@ public static string SanitizeForLogging(string? input)
#pragma warning restore S3267
}
+ // Explicitly reject control characters for defense in depth
private static bool IsAllowedChar(char c) =>
- char.IsLetterOrDigit(c) || c is ' ' or '_' or '-' or '.' or ':' or '/';
+ !char.IsControl(c) && (char.IsLetterOrDigit(c) || c is ' ' or '_' or '-' or '.' or ':' or '/');
}
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Validation/DecimalValidator.cs b/src/dms/core/EdFi.DataManagementService.Core/Validation/DecimalValidator.cs
index 7803c116b..6e77a903d 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Validation/DecimalValidator.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Validation/DecimalValidator.cs
@@ -33,7 +33,8 @@ IEnumerable decimalValidationInfos
Trace.Assert(
result.Matches != null,
- "Evaluation of decimalValidationInfos.Path.Matches resulted in unexpected null"
+ "Evaluation of decimalValidationInfos.Path.Matches resulted in unexpected null",
+ ""
);
foreach (var match in result.Matches)
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Validation/DocumentValidator.cs b/src/dms/core/EdFi.DataManagementService.Core/Validation/DocumentValidator.cs
index 716f51ae1..6d82b3c93 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Validation/DocumentValidator.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Validation/DocumentValidator.cs
@@ -349,7 +349,7 @@ internal static JsonObject RemoveProperty(this JsonObject jsonObject, string[] s
var remainingSegments = segments.Skip(1).ToArray();
var node = jsonObject[currentSegment];
- Trace.Assert(node != null, $"PointerSegment '{currentSegment}' not found on JsonObject");
+ Trace.Assert(node != null, $"PointerSegment '{currentSegment}' not found on JsonObject", "");
if (node is JsonObject nodeObj)
{
@@ -367,14 +367,15 @@ internal static JsonObject RemoveProperty(this JsonObject jsonObject, string[] s
}
else
{
- Trace.Assert(false, $"Index '{index}' out of bounds for JsonArray");
+ Trace.Assert(false, $"Index '{index}' out of bounds for JsonArray", "");
}
}
else
{
Trace.Assert(
false,
- $"Node is not a JsonObject or JsonArray or invalid index for array: {currentSegment}"
+ $"Node is not a JsonObject or JsonArray or invalid index for array: {currentSegment}",
+ ""
);
}
diff --git a/src/dms/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs b/src/dms/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs
index 9d0963101..cad0335b8 100644
--- a/src/dms/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs
+++ b/src/dms/core/EdFi.DataManagementService.Core/Validation/EqualityConstraintValidator.cs
@@ -43,11 +43,13 @@ IEnumerable equalityConstraints
Trace.Assert(
sourcePathResult.Matches != null,
- "Evaluation of sourcePathResult.Matches resulted in unexpected null"
+ "Evaluation of sourcePathResult.Matches resulted in unexpected null",
+ ""
);
Trace.Assert(
targetPathResult.Matches != null,
- "Evaluation of targetPathResult.Matches resulted in unexpected null"
+ "Evaluation of targetPathResult.Matches resulted in unexpected null",
+ ""
);
var combinedValues = new HashSet(
diff --git a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit.csproj b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit.csproj
index 0613b4ea7..d50f8c401 100644
--- a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit.csproj
+++ b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit/EdFi.DataManagementService.Frontend.AspNetCore.Tests.Unit.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/EdFi.DataManagementService.Frontend.AspNetCore.csproj b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/EdFi.DataManagementService.Frontend.AspNetCore.csproj
index 2fab46e82..bccc414d6 100644
--- a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/EdFi.DataManagementService.Frontend.AspNetCore.csproj
+++ b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/EdFi.DataManagementService.Frontend.AspNetCore.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
true
true
@@ -21,7 +21,6 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
diff --git a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Modules/TokenEndpointModule.cs b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Modules/TokenEndpointModule.cs
index ab4eb2a89..884d230b3 100644
--- a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Modules/TokenEndpointModule.cs
+++ b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Modules/TokenEndpointModule.cs
@@ -7,7 +7,6 @@
using EdFi.DataManagementService.Core.OAuth;
using EdFi.DataManagementService.Frontend.AspNetCore.Configuration;
using EdFi.DataManagementService.Frontend.AspNetCore.Infrastructure.Extensions;
-using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace EdFi.DataManagementService.Frontend.AspNetCore.Modules;
@@ -28,13 +27,21 @@ public void MapEndpoints(IEndpointRouteBuilder endpoints)
internal static async Task HandleFormData(
HttpContext httpContext,
- [FromForm] TokenRequest tokenRequest,
IOptions appSettings,
IOAuthManager oAuthManager,
ILogger logger,
IHttpClientFactory httpClientFactory
)
{
+ // Manually read form data to handle empty form bodies in .NET 10
+ // (Minimal API [FromForm] binding returns 400 with empty body before handler is invoked)
+ TokenRequest tokenRequest = new();
+ if (httpContext.Request.HasFormContentType)
+ {
+ var form = await httpContext.Request.ReadFormAsync();
+ tokenRequest = new TokenRequest { grant_type = form["grant_type"].ToString() };
+ }
+
await GenerateToken(httpContext, tokenRequest, appSettings, oAuthManager, logger, httpClientFactory);
}
diff --git a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Program.cs b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Program.cs
index 88de05ae3..9099faa00 100644
--- a/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Program.cs
+++ b/src/dms/frontend/EdFi.DataManagementService.Frontend.AspNetCore/Program.cs
@@ -79,7 +79,7 @@
| ForwardedHeaders.XForwardedProto;
// Accept forwarded headers from any network and proxy
- options.KnownNetworks.Clear();
+ options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});
}
diff --git a/src/dms/tests/EdFi.DataManagementService.Tests.E2E/EdFi.DataManagementService.Tests.E2E.csproj b/src/dms/tests/EdFi.DataManagementService.Tests.E2E/EdFi.DataManagementService.Tests.E2E.csproj
index b5812ac37..e13a173a0 100644
--- a/src/dms/tests/EdFi.DataManagementService.Tests.E2E/EdFi.DataManagementService.Tests.E2E.csproj
+++ b/src/dms/tests/EdFi.DataManagementService.Tests.E2E/EdFi.DataManagementService.Tests.E2E.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/ScenarioVariables.cs b/src/dms/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/ScenarioVariables.cs
index 6fd462629..6ac77ecd7 100644
--- a/src/dms/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/ScenarioVariables.cs
+++ b/src/dms/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/ScenarioVariables.cs
@@ -8,7 +8,7 @@ namespace EdFi.DataManagementService.Tests.E2E.StepDefinitions
///
/// Provides means of storing string variables in a Test Scenario.
///
- class ScenarioVariables
+ internal class ScenarioVariables
{
private readonly Dictionary _variableByName = [];
diff --git a/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/EdFi.InstanceManagement.Tests.E2E.csproj b/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/EdFi.InstanceManagement.Tests.E2E.csproj
index deab517cb..6bf614955 100644
--- a/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/EdFi.InstanceManagement.Tests.E2E.csproj
+++ b/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/EdFi.InstanceManagement.Tests.E2E.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
false
diff --git a/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/StepDefinitions/InstanceKafkaStepDefinitions.cs b/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/StepDefinitions/InstanceKafkaStepDefinitions.cs
index c62b0381e..55397684d 100644
--- a/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/StepDefinitions/InstanceKafkaStepDefinitions.cs
+++ b/src/dms/tests/EdFi.InstanceManagement.Tests.E2E/StepDefinitions/InstanceKafkaStepDefinitions.cs
@@ -229,7 +229,9 @@ public void ThenTheMessageShouldHaveDeletedFlag(string expectedDeletedFlag)
var messagesWithDeletedFlag = allMessages.Where(m =>
{
if (m.ValueAsJson == null)
+ {
return false;
+ }
var deletedField = m.ValueAsJson["__deleted"];
string expectedValue = shouldBeDeleted ? "true" : "false";