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";