Skip to content

Conversation

@bjorntore
Copy link
Contributor

@bjorntore bjorntore commented Sep 17, 2025

Add a standard validator that validates that the signature hashes are still valid when leaving a signature task.

Related Issue(s)

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • New Features

    • Streaming support for large binary files with cancellation-aware transfers.
    • Cancellation support added to HTTP operations.
    • Automatic signature-hash validation for signing tasks to detect tampered data.
    • Richer, sanitized HTTP error snapshots for clearer failure information.
  • Bug Fixes

    • Improved diagnostics and handling for platform HTTP failures, yielding clearer error responses.
  • Tests

    • Comprehensive unit tests added for streaming, validation, and HTTP snapshot behaviors.
  • Localization

    • Backend fallback text for invalid signature-hash validation added.

@bjorntore bjorntore self-assigned this Sep 17, 2025
@bjorntore bjorntore added the backport-ignore This PR is a new feature and should not be cherry-picked onto release branches label Sep 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

📝 Walkthrough

Walkthrough

Adds cancellation-aware HTTP helpers and a streaming GET, implements streaming binary retrieval in storage, introduces SignatureHashValidator with SHA‑256 checks and new InvalidSignatureHash code, introduces PlatformHttpResponseSnapshotException and related tests and mocks.

Changes

Cohort / File(s) Summary
HTTP client extensions
src/Altinn.App.Core/Extensions/HttpClientExtension.cs
Added CancellationToken (default) to PostAsync, PutAsync, GetAsync, DeleteAsync; added GetStreamingAsync using HttpCompletionOption.ResponseHeadersRead; consistent Authorization and optional platform token handling; updated XML docs.
Signature validation
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs
New internal SignatureHashValidator implementing IValidator that computes SHA‑256 of data streams, compares to stored Sha256Hash, returns ValidationIssue InvalidSignatureHash on mismatch; added ValidationIssueCodes.DataElementCodes.InvalidSignatureHash.
DI registration
src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
Registered SignatureHashValidator in the service collection alongside existing validators.
Storage streaming support
src/Altinn.App.Core/Internal/Data/IDataClient.cs
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs
Added GetBinaryDataStream to IDataClient and DataClient; DataClient gains a dedicated streaming HttpClient and a 30‑minute streaming timeout; ResponseWrapperStream wraps an inner stream and disposes the originating HttpResponseMessage; added telemetry and cancellation handling for streaming.
Platform HTTP error snapshot
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs
src/Altinn.App.Api/Controllers/ProcessController.cs
New PlatformHttpResponseSnapshotException that snapshots status, headers (with sensitive header redaction), and up to 16KB of content, disposes original response; ProcessController.ExceptionResponse now handles this exception and returns ProblemDetails with the snapshot status.
Mocks and tests
test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs
test/Altinn.App.Core.Tests/Helpers/PlatformHttpResponseSnapshotExceptionTests.cs
Added GetBinaryDataStream to DataClientMock; extensive unit tests for SignatureHashValidator, DataClient.GetBinaryDataStream, and PlatformHttpResponseSnapshotException; test fixtures updated to provide streaming HttpClient instances.
Verified snapshots / public API
test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_FailingValidator_ReturnsValidationErrors.verified.txt
test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_PdfFails_DataIsUnlocked.verified.txt
test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
Updated verified activity logs and public API surface to reflect CancellationToken additions to HttpClientExtension methods, new GetStreamingAsync, new IDataClient.GetBinaryDataStream, and new ValidationIssueCodes entry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Validate signature hashes" directly aligns with the primary objective stated in the PR description: adding a standard validator that validates signature hashes when leaving a signature task. The title is concise, clear, and specific — it accurately describes the main feature being introduced (SignatureHashValidator) without being vague or generic. While the PR includes supporting infrastructure changes such as CancellationToken additions to HTTP methods and streaming data support, these are enablers for the core feature and do not diminish the title's accuracy in capturing the main change. The title would allow a teammate scanning the repository history to quickly understand that this change introduces signature hash validation functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/signature-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bjorntore bjorntore added the feature Label Pull requests with new features. Used when generation releasenotes label Sep 17, 2025
@bjorntore
Copy link
Contributor Author

Ser jeg glemte å ta i bruk GetStreamingAsync.... WIP

@bjorntore bjorntore changed the title Validate signature hashes WIP: Validate signature hashes Sep 17, 2025
@bjorntore bjorntore marked this pull request as draft September 17, 2025 10:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (15)
.claude/settings.local.json (1)

1-11: Local Claude settings checked in — confirm intent; add trailing newline.

If this is meant for local dev only, consider ignoring it via .gitignore or documenting why it’s versioned. Also add a trailing newline to appease some linters.

src/Altinn.App.Core/Extensions/HttpClientExtension.cs (1)

106-135: Expose CancellationToken on GetStreamingAsync.

Streaming calls often need cancellation; currently hard-wired to CancellationToken.None.

Apply this diff:

-    public static async Task<HttpResponseMessage> GetStreamingAsync(
+    public static async Task<HttpResponseMessage> GetStreamingAsync(
         this HttpClient httpClient,
         string authorizationToken,
         string requestUri,
-        string? platformAccessToken = null
+        string? platformAccessToken = null,
+        CancellationToken cancellationToken = default
     )
     {
         using HttpRequestMessage request = new(HttpMethod.Get, requestUri);
@@
-        return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None);
+        return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
     }
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (4)

93-97: Avoid FormatException from Guid.Parse — use TryParse and fail gracefully.

A malformed id will currently throw. Prefer a validation issue.

Apply this diff:

-                    Guid.Parse(dataElementSignature.DataElementId),
+                    Guid.TryParse(dataElementSignature.DataElementId, out var dataGuid)
+                        ? dataGuid
+                        : throw new ApplicationConfigException($"Invalid data element id: {dataElementSignature.DataElementId}"),

42-45: Narrow exception catch in ShouldRunForTask.

Catching Exception hides unexpected faults. Consider catching ApplicationConfigException (or the specific type thrown by GetAltinnTaskExtension) instead.


142-143: Tiny cleanup: redundant null-conditional.

dataType is already null-checked above; drop the ?.

Apply this diff:

-        return !string.IsNullOrEmpty(dataType?.ActionRequiredToRead);
+        return !string.IsNullOrEmpty(dataType.ActionRequiredToRead);

152-155: Typo in XML doc: "best best practice".

Minor doc fix.

Apply this diff:

-    /// Formats a SHA digest with common best best practice:<br/>
+    /// Formats a SHA digest with common best practice:<br/>
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (9)

32-38: Prefer NullLogger in tests over mocking ILogger

Slightly simpler and avoids unnecessary Moq setup.

-            new Mock<ILogger<SignatureHashValidator>>().Object
+            NullLogger<SignatureHashValidator>.Instance

Also add this using at the top:

+using Microsoft.Extensions.Logging.Abstractions;

46-55: Avoid magic SHA-256 constants; compute from the arranged data

Computing the expected hash in-test makes intent clear and prevents drift if encoding changes.

-        const string testData = "test data";
-        const string expectedHash = "916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9";
+        const string testData = "test data";
+        string expectedHash = Sha256Hex(testData);

Add this helper to the test class:

private static string Sha256Hex(string s)
{
    using var sha = System.Security.Cryptography.SHA256.Create();
    return Convert.ToHexString(sha.ComputeHash(Encoding.UTF8.GetBytes(s))).ToLowerInvariant();
}

And add at file top if missing:

using System.Security.Cryptography; // optional; not needed if using fully qualified above

65-73: Use a valid (but different) SHA-256 instead of a non-hex placeholder

Better represents real-world data and keeps the test robust if validation tightens.

-        const string testData = "test data";
-        const string storedHash = "different-hash";
+        const string testData = "test data";
+        string storedHash = Sha256Hex("mismatching content");

181-215: LGTM: validates across multiple signee contexts

Accurate call-count check for GetBinaryData. Consider also a test with multiple signatures in a single context to cover that loop.


303-314: Add test for null AltinnTaskExtension

Covers the case where GetAltinnTaskExtension returns null (not just throws).

[Fact]
public void ShouldRunForTask_WithNullExtension_ReturnsFalse()
{
    _processReaderMock.Setup(x => x.GetAltinnTaskExtension("task")).Returns((AltinnTaskExtension?)null);
    Assert.False(_validator.ShouldRunForTask("task"));
}

341-365: Use obviously fake identifiers to avoid realistic PII patterns

Swap the SSN and name to clear dummy values.

-                SocialSecurityNumber = "12345678901",
-                FullName = "Test Person",
+                SocialSecurityNumber = "00000000000",
+                FullName = "Test User",

Apply in both places: Line 348 and Line 230.


383-386: Reduce brittle matching in Moq setup

Match by type rather than exact instance to decouple from the test fixture’s private fields.

-                    It.Is<IInstanceDataAccessor>(d => d == _dataAccessorMock.Object),
-                    It.Is<AltinnSignatureConfiguration>(c => c == signingConfiguration),
+                    It.IsAny<IInstanceDataAccessor>(),
+                    It.IsAny<AltinnSignatureConfiguration>(),

57-60: Structured logging in SUT for the invalid-hash branch

Switch from string interpolation to structured logging placeholders to preserve fields in logs.

In SignatureHashValidator.Validate:

-                    logger.LogError(
-                        $"Found an invalid signature for data element {dataElementSignature.DataElementId} on instance {instance.Id}. Expected hash {dataElementSignature.Sha256Hash}, calculated hash {sha256Hash}."
-                    );
+                    logger.LogError(
+                        "Found an invalid signature for data element {DataElementId} on instance {InstanceId}. Expected hash {ExpectedHash}, calculated hash {CalculatedHash}.",
+                        dataElementSignature.DataElementId,
+                        instance.Id,
+                        dataElementSignature.Sha256Hash,
+                        sha256Hash
+                    );

57-60: Consider aggregating all invalid signatures instead of early-return

Returning all issues improves UX and reduces back-and-forth for users. If kept as-is, document that only the first failure is reported.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5759ca and 0b08baf.

📒 Files selected for processing (6)
  • .claude/settings.local.json (1 hunks)
  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs (1 hunks)
  • src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs (1 hunks)
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1 hunks)
  • src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (1 hunks)
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
  • src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
  • src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs
src/Altinn.App.Core/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

New features should follow the established feature pattern under /src/Altinn.App.Core/Features (feature folder, DI registration, telemetry, and tests)

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
test/Altinn.App.Core.Tests/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧠 Learnings (2)
📚 Learning: 2025-08-29T11:06:37.385Z
Learnt from: CR
PR: Altinn/app-lib-dotnet#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T11:06:37.385Z
Learning: Applies to test/Altinn.App.Core.Tests/Features/**/*.cs : Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Applied to files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
📚 Learning: 2025-08-29T11:06:37.385Z
Learnt from: CR
PR: Altinn/app-lib-dotnet#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T11:06:37.385Z
Learning: Applies to test/Altinn.App.Integration.Tests/**/*.cs : Include integration tests for platform service interactions (using Docker Testcontainers)

Applied to files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧬 Code graph analysis (4)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (5)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)
  • SigneeContext (341-365)
src/Altinn.App.Core/Models/InstanceIdentifier.cs (1)
  • InstanceOwnerPartyId (111-118)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
test/Altinn.App.Integration.Tests/_fixture/NuGetPackaging.cs (1)
  • ToHexString (169-169)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (4)
src/Altinn.App.Core/Internal/Sign/SignatureContext.cs (1)
  • Signee (85-109)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1)
  • SignatureHashValidator (19-162)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs (1)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1)
  • SignatureHashValidator (19-162)
src/Altinn.App.Core/Extensions/HttpClientExtension.cs (3)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (6)
  • Task (61-77)
  • Task (80-125)
  • Task (128-169)
  • Task (172-205)
  • Task (208-247)
  • Task (250-277)
src/Altinn.App.Core/Constants/AuthorizationSchemes.cs (1)
  • AuthorizationSchemes (6-12)
src/Altinn.App.Core/Constants/General.cs (1)
  • General (6-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Static code analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (10)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (1)

74-77: New code added: DataElementCodes.InvalidSignatureHash — ensure i18n text exists.

Looks good. Verify a corresponding localized text exists (frontend expects lookup by this code).

src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs (1)

233-234: Multiple IValidator registrations are safe — no single-IValidator resolution found.

Verified: DI registers multiple IValidator implementations (src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs); validators are collected via IValidatorFactory.GetAll and used as IValidator[]/IEnumerable (src/Altinn.App.Core/Internal/Validation/IValidatorFactory.cs, src/Altinn.App.Core/Internal/Validation/ValidationService.cs). No constructor or GetService/GetRequiredService resolving a lone IValidator in src/; only a test-only singleton exists in test/Altinn.App.Integration.Tests/_testapps/_shared/Tracing.cs.

test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (8)

98-127: LGTM: verifies ServiceOwner auth is used for restricted read

Good assertion on non-null StorageAuthenticationMethod and correct routing params.


129-158: LGTM: verifies no ServiceOwner auth for non-restricted read

Clear, targeted Verify with null auth.


160-179: LGTM: precise exception assertion for missing data type

Strong contract check with exact message.


217-256: LGTM: skips when SignDocument is null

Good negative-path coverage and Verify(Times.Never).


271-279: LGTM: ShouldRunForTask returns true for signing tasks


282-301: LGTM: ShouldRunForTask returns false for non-signing and null task types


316-328: LGTM: HasRelevantChanges throws as NoIncrementalValidation is true


21-41: Test infra verified — no action required.

  • SignatureHashValidatorTests is public (test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs).
  • InternalsVisibleTo grants Altinn.App.Core.Tests (test/Altinn.App.Tests.Common/Altinn.App.Tests.Common.csproj); src/Altinn.App.Core/Altinn.App.Core.csproj also exposes test assemblies. ImplicitUsings is enabled for test projects.

Copy link
Contributor

@martinothamar martinothamar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this matches the corresponding code in Storage: https://github.com/Altinn/altinn-storage/blob/6259f1a741cb0c6fc6b625575af867bc455bcf2b/src/Storage/Services/DataService.cs#L110

Maybe make a comment/note saying how this is coupled together?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (2)

114-127: Avoid reference equality for StorageAuthenticationMethod in Moq matcher

StorageAuthenticationMethod.ServiceOwner() returns a new instance; using == compares references and can be brittle or fail if equality isn’t overridden. Match on intent (e.g., underlying request/type) instead of instance.

Example approaches:

  • Match properties: It.Is<StorageAuthenticationMethod?>(a => a is not null /* and a.Request is ServiceOwner */)
  • Or expose/compare a discriminant on StorageAuthenticationMethod (e.g., MethodKind == ServiceOwner) and assert that.

Please adjust similarly in the non-restricted test below.


315-316: Use Assert.Equal for clarity

Prefer Assert.Equal(expected, actual) over Assert.True(a == b) for message comparison.

-        Assert.True(thrownException.Message == mockedException.Message);
+        Assert.Equal(mockedException.Message, thrownException.Message);
src/Altinn.App.Core/Internal/Data/IDataClient.cs (1)

110-112: Clarify semantics and disposal requirements; avoid hard-coding timeout

  • Document 404 behavior for GetBinaryData (returns null despite non-nullable signature) to prevent surprises.
  • In GetBinaryDataStream docs, explicitly state: “Caller must dispose the returned Stream to release the underlying HTTP response.”
  • Avoid hard-coding “Defaults to 100 seconds”; reference “default operation timeout” to prevent drift from implementation constants.

Also applies to: 130-153

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (3)

219-220: Unify error handling: prefer snapshot exception to avoid retaining HttpResponseMessage

Non-streaming paths still throw PlatformHttpException, which keeps a reference to the response (risking leaks/late ObjectDisposed). For consistency and safety, use PlatformHttpResponseSnapshotException.CreateAndDisposeHttpResponse(...) as in the streaming method.

Example (apply pattern):

-        throw await PlatformHttpException.CreateAsync(response);
+        throw await PlatformHttpResponseSnapshotException.CreateAndDisposeHttpResponse(
+            response,
+            cancellationToken: timeoutCts.Token
+        );

Also applies to: 314-315, 347-348, 390-391, 475-476, 518-519, 616-617, 663-664, 698-699, 741-742, 784-785


28-29: Seal the class

Per guidelines, seal classes by default unless inheritance is required. DataClient appears DI-constructed and not intended for inheritance.

As per coding guidelines


183-221: Document the null-on-404 behavior or plan deprecation

GetBinaryData returns null for 404 while the streaming variant throws. Consider:

  • Add XML doc here mirroring the null return on 404, and
  • Plan to make return type Stream? (breaking change) in next major, or steer consumers to streaming method.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cba78ae and 841451f.

📒 Files selected for processing (5)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (29 hunks)
  • src/Altinn.App.Core/Internal/Data/IDataClient.cs (2 hunks)
  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1 hunks)
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1 hunks)
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs
🧰 Additional context used
📓 Path-based instructions (5)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
  • src/Altinn.App.Core/Internal/Data/IDataClient.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
  • src/Altinn.App.Core/Internal/Data/IDataClient.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
test/Altinn.App.Core.Tests/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Internal/Data/IDataClient.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
🧠 Learnings (1)
📚 Learning: 2025-08-29T11:06:37.385Z
Learnt from: CR
PR: Altinn/app-lib-dotnet#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T11:06:37.385Z
Learning: Applies to test/Altinn.App.Core.Tests/Features/**/*.cs : Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Applied to files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧬 Code graph analysis (3)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (4)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (2)
  • SignatureHashValidator (19-178)
  • ShouldRunForTask (37-41)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (16)
  • Task (64-83)
  • Task (86-134)
  • Task (137-181)
  • Task (184-220)
  • Task (223-270)
  • Task (273-315)
  • Task (318-348)
  • Task (351-391)
  • Task (429-439)
  • Task (442-476)
  • Task (479-519)
  • Task (522-574)
  • Task (577-617)
  • Task (620-664)
  • Task (667-699)
  • Task (702-742)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
src/Altinn.App.Core/Internal/Data/IDataClient.cs (1)
src/Altinn.App.Core/Features/AuthenticationMethod.cs (5)
  • StorageAuthenticationMethod (85-85)
  • StorageAuthenticationMethod (88-88)
  • StorageAuthenticationMethod (91-92)
  • StorageAuthenticationMethod (95-96)
  • StorageAuthenticationMethod (100-103)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Static code analysis
🔇 Additional comments (3)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)

43-61: Good coverage and setup

Tests exercise valid/invalid hash, auth selection (restricted vs non-restricted), multi-signee, and control methods (TaskId/NoIncrementalValidation/ShouldRunForTask/HasRelevantChanges). Nice use of MemoryStream to emulate content.

Also applies to: 183-218

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)

222-270: Streaming path looks correct; wrapper ensures response disposal

Using GetStreamingAsync + ResponseWrapperStream with linked CTS is solid. The try/catch disposing the response on failures is good.


55-61: HttpClientExtension methods don’t support CancellationToken
The PostAsync, PutAsync, GetAsync and DeleteAsync extensions in HttpClientExtension.cs only accept (HttpClient, string authorizationToken, string requestUri[, HttpContent/ platformAccessToken]) and have no CancellationToken parameter. You can’t pass timeoutCts.Token to them—either add overloads (or use HttpClient.SendAsync) that accept a CancellationToken, or reconsider relying on infinite HttpClient.Timeout.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1)

131-140: CA2000 “undisposed FileStream” warning is a false positive here.

The mock returns the FileStream to the caller (SUT disposes it). If code scanning keeps flagging this, suppress locally.

Apply one of these:

-        var fs = new FileStream(
+        // Suppressed: stream ownership is transferred to the caller (SUT disposes).
+#pragma warning disable CA2000 // Dispose objects before losing scope
+        var fs = new FileStream(
             path,
             FileMode.Open,
             FileAccess.Read,
-            FileShare.Read,
+            FileShare.Read, // consider FileShare.ReadWrite | FileShare.Delete if tests write concurrently
             bufferSize: 64 * 1024,
             options: FileOptions.Asynchronous | FileOptions.SequentialScan
         );
+#pragma warning restore CA2000
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)

371-402: Analyzer note: reference equality in SetupMocks is intentional—make it explicit.

The predicate d == _dataAccessorMock.Object triggered a previous analyzer note about System.Object reference equality. Make intent explicit to quiet the rule.

-                    It.Is<IInstanceDataAccessor>(d => d == _dataAccessorMock.Object),
+                    It.Is<IInstanceDataAccessor>(d => ReferenceEquals(d, _dataAccessorMock.Object)),
🧹 Nitpick comments (6)
test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1)

126-131: Align 404/error semantics with production DataClient.GetBinaryDataStream.

Production throws a PlatformHttpResponseSnapshotException on non-success; this mock throws FileNotFoundException. Consider mirroring production to avoid surprise in tests that assert on exception type/status in the streaming path. If you want to keep the mock light, at least document the divergence here.

Would you like a small helper that throws a NotFound snapshot exception for mocks?

test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (2)

114-126: Fragile equality on authentication method in Moq predicate.

auth == StorageAuthenticationMethod.ServiceOwner() may rely on reference equality. Prefer a predicate that asserts “non-null” (or a stable discriminant) to avoid flakiness.

Example:

-                    It.Is<StorageAuthenticationMethod?>(auth => auth == StorageAuthenticationMethod.ServiceOwner()),
+                    It.Is<StorageAuthenticationMethod?>(auth => auth is not null),

If StorageAuthenticationMethod exposes a discriminant, match on that instead.


46-56: Nice, focused tests; consider computing the expected hash to reduce brittleness.

Hard-coding the SHA-256 string is fine; alternatively compute it inline to avoid accidental mismatch if test data changes.

static string Sha256Hex(string s)
{
    using var sha = SHA256.Create();
    return Convert.ToHexString(sha.ComputeHash(Encoding.UTF8.GetBytes(s))).ToLowerInvariant();
}

Then use Sha256Hex("test data") for expectedHash.

Also applies to: 62-75, 160-179, 181-215

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (3)

223-270: Inconsistent 404 behavior between buffered and streaming APIs.

GetBinaryData returns null on 404 (with #nullable disable), while GetBinaryDataStream throws PlatformHttpResponseSnapshotException for any non-success. This divergence complicates callers.

Pick one convention for both (prefer exception + typed status via snapshot for clarity), adjust interface nullability accordingly, and update mocks/tests. Based on coding guidelines (NRTs enabled), avoiding null Stream is cleaner.

Also applies to: 195-218


39-41: Expose timeouts via configuration, not hardcoded constants.

Consider IOptions with defaults (100s standard, 30m streaming) to allow environment tuning and tests to shorten timeouts.


128-135: Unify exception strategy (snapshot vs PlatformHttpException).

Streaming uses PlatformHttpResponseSnapshotException (safe, disposes response); other paths throw PlatformHttpException, which retains HttpResponseMessage. Consider migrating to snapshot for consistency and to avoid accidental use of disposed responses (as discussed in PR).

Do you want a follow-up PR to switch remaining call sites?

Also applies to: 181-182, 220-221, 314-315, 347-348, 390-391, 475-476, 518-519, 573-574, 616-617, 663-664, 698-699, 741-742, 784-785

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 412ad8e and 6370f20.

📒 Files selected for processing (5)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (29 hunks)
  • src/Altinn.App.Core/Internal/Data/IDataClient.cs (2 hunks)
  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1 hunks)
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1 hunks)
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Altinn.App.Core/Internal/Data/IDataClient.cs
🧰 Additional context used
📓 Path-based instructions (5)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
test/Altinn.App.Core.Tests/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧠 Learnings (1)
📚 Learning: 2025-08-29T11:06:37.385Z
Learnt from: CR
PR: Altinn/app-lib-dotnet#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T11:06:37.385Z
Learning: Applies to test/Altinn.App.Core.Tests/Features/**/*.cs : Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Applied to files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧬 Code graph analysis (3)
test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1)
test/Altinn.App.Api.Tests/Data/TestData.cs (3)
  • org (69-90)
  • TestData (9-221)
  • GetDataBlobPath (116-120)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (3)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (2)
  • SignatureHashValidator (19-178)
  • ShouldRunForTask (37-41)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (16)
  • Task (65-84)
  • Task (87-135)
  • Task (138-182)
  • Task (185-221)
  • Task (224-270)
  • Task (273-315)
  • Task (318-348)
  • Task (351-391)
  • Task (429-439)
  • Task (442-476)
  • Task (479-519)
  • Task (522-574)
  • Task (577-617)
  • Task (620-664)
  • Task (667-699)
  • Task (702-742)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Static code analysis
🔇 Additional comments (4)
test/Altinn.App.Api.Tests/Mocks/DataClientMock.cs (1)

121-123: Early-cancel path: good.

Returning Task.FromCanceled when the token is already canceled is correct and prevents opening the file unnecessarily.

test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (2)

145-157: Non-restricted read: assert null auth only.

This is precise and stable. No change requested.


199-215: Good coverage for multiple signee contexts.

Verifies per-context streaming fetch and ensures exactly two calls.

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (1)

237-270: Streaming disposal pattern looks solid.

Wrapping the content stream with ResponseWrapperStream and disposing the HttpResponseMessage on error paths avoids leaks and TDZ-like uses of disposed responses.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6370f20 and 7018d57.

📒 Files selected for processing (3)
  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs (8 hunks)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (26 hunks)
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Extensions/HttpClientExtension.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
🧬 Code graph analysis (2)
src/Altinn.App.Core/Extensions/HttpClientExtension.cs (3)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (16)
  • Task (65-84)
  • Task (87-140)
  • Task (143-192)
  • Task (195-231)
  • Task (234-280)
  • Task (283-325)
  • Task (328-358)
  • Task (361-401)
  • Task (439-449)
  • Task (452-486)
  • Task (489-534)
  • Task (537-594)
  • Task (597-642)
  • Task (645-694)
  • Task (697-734)
  • Task (737-782)
src/Altinn.App.Core/Constants/AuthorizationSchemes.cs (1)
  • AuthorizationSchemes (6-12)
src/Altinn.App.Core/Constants/General.cs (1)
  • General (6-42)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Static code analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (8)
src/Altinn.App.Core/Extensions/HttpClientExtension.cs (5)

18-43: LGTM: CancellationToken support added to PostAsync.

The addition of the cancellationToken parameter and forwarding it to SendAsync enables proper timeout and cancellation control for POST operations.


53-78: LGTM: CancellationToken support added to PutAsync.

Consistent with PostAsync, the cancellation token is properly forwarded to SendAsync.


87-110: LGTM: GetAsync enhanced with explicit completion option and cancellation token.

The explicit use of HttpCompletionOption.ResponseContentRead on line 109 documents the buffering behavior, and the cancellation token is properly forwarded.


112-145: LGTM: GetStreamingAsync correctly implements streaming semantics.

The new method properly uses HttpCompletionOption.ResponseHeadersRead to enable streaming, and the XML documentation (lines 122-123) appropriately warns consumers about timeout configuration for large files.


154-177: LGTM: CancellationToken support added to DeleteAsync.

Consistent with other methods, the cancellation token is properly forwarded.

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (3)

39-40: LGTM: Timeout strategy correctly implements per-operation cancellation.

Setting HttpClient.Timeout = Timeout.InfiniteTimeSpan (line 60) combined with per-operation CancellationTokenSource instances (using the timeout constants on lines 39-40) provides fine-grained timeout control while allowing callers to override via their own cancellation tokens. This addresses the concerns raised in previous reviews.

Also applies to: 60-60


233-280: LGTM: GetBinaryDataStream implements robust streaming with proper resource management.

The new streaming method correctly:

  • Uses the extended _streamingHttpOperationTimeout (30 minutes) for large file operations (line 245)
  • Leverages GetStreamingAsync with HttpCompletionOption.ResponseHeadersRead for true streaming (lines 256-260)
  • Wraps the response and stream in ResponseWrapperStream (line 267), ensuring the response is disposed when the stream is disposed
  • Handles ReadAsStreamAsync failures by disposing the response in the catch block (lines 269-273)
  • On non-success responses, uses PlatformHttpResponseSnapshotException.CreateAndDisposeHttpResponse (lines 276-279) which safely snapshots and disposes the response

78-84: LGTM: Per-operation timeout pattern applied consistently throughout.

All methods correctly implement the pattern of:

  1. Creating a linked CancellationTokenSource with the incoming cancellationToken
  2. Setting the appropriate timeout (_httpOperationTimeout for most operations)
  3. Passing timeoutCts.Token to all async operations: GetAccessToken, HTTP calls, and content reads

This ensures that all operations respect both caller-provided cancellation tokens and per-operation timeouts, preventing indefinite hangs while maintaining flexibility.

Also applies to: 97-127, 156-191, 205-231, 294-325, 338-358, 370-401, 463-486, 500-534, 548-594, 608-642, 655-694, 704-734, 744-782, 792-825

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)

311-314: Prefer Assert.Equal over string == in tests.

This reads clearer and avoids “reference equality” lint noise.

-        Assert.True(thrownException.Message == mockedException.Message);
+        Assert.Equal(mockedException.Message, thrownException.Message);
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1)

126-142: Include DataElementId and compare hash case-insensitively.

Avoid case pitfalls and return the offending element id.

-        if (sha256Hash != dataElementSignature.Sha256Hash)
+        if (!string.Equals(sha256Hash, dataElementSignature.Sha256Hash, StringComparison.OrdinalIgnoreCase))
         {
             logger.LogError(
                 "Found an invalid signature for data element {DataElementId} on instance {InstanceId}. Expected hash {ExpectedHash}, calculated hash {CalculatedHash}",
                 dataElementSignature.DataElementId,
                 instance.Id,
                 dataElementSignature.Sha256Hash,
                 sha256Hash
             );
 
-            return new ValidationIssue
+            return new ValidationIssue
             {
                 Code = ValidationIssueCodes.DataElementCodes.InvalidSignatureHash,
                 Severity = ValidationIssueSeverity.Error,
+                DataElementId = dataElementSignature.DataElementId,
                 Description = ValidationIssueCodes.DataElementCodes.InvalidSignatureHash,
             };
         }
🧹 Nitpick comments (5)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (3)

80-84: Also assert DataElementId on error for stronger contract.

Add an assertion to ensure the validator populates DataElementId.

         Assert.Single(result);
         Assert.Equal(ValidationIssueCodes.DataElementCodes.InvalidSignatureHash, result[0].Code);
         Assert.Equal(ValidationIssueSeverity.Error, result[0].Severity);
         Assert.Equal(ValidationIssueCodes.DataElementCodes.InvalidSignatureHash, result[0].Description);
+        Assert.Equal("550e8400-e29b-41d4-a716-446655440001", result[0].DataElementId);

342-366: Helper name is misleading; consider renaming.

Method sets any provided hash (valid or not). Rename to CreateSigneeContextWithHash; update call sites.


368-404: Add a test to verify RequestAborted is propagated to IDataClient.

Ensure cancellation flows from HttpContext to GetBinaryDataStream.

Add this test (append to the class):

[Fact]
public async Task Validate_PropagatesRequestAbortedToken_ToDataClient()
{
    // Arrange
    const string testData = "test data";
    const string expectedHash = "916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9";
    var signingConfiguration = new AltinnSignatureConfiguration { SignatureDataType = "signature" };
    var applicationMetadata = new ApplicationMetadata("testorg/testapp")
    {
        DataTypes = [ new DataType { Id = "form", ActionRequiredToRead = null } ],
    };
    var signeeContext = CreateSigneeContextWithValidHash(expectedHash);

    var context = new DefaultHttpContext();
    var cts = new CancellationTokenSource();
    context.RequestAborted = cts.Token;

    var httpContextAccessor = new Mock<IHttpContextAccessor>();
    httpContextAccessor.Setup(x => x.HttpContext).Returns(context);

    var validator = new SignatureHashValidator(
        _signingServiceMock.Object,
        _processReaderMock.Object,
        _dataClientMock.Object,
        _appMetadataMock.Object,
        httpContextAccessor.Object,
        new Mock<ILogger<SignatureHashValidator>>().Object
    );

    // Reuse existing setup helpers
    SetupMocks(signingConfiguration, applicationMetadata, [signeeContext], testData);

    // Act
    await validator.Validate(_dataAccessorMock.Object, "signing-task", "en");

    // Assert
    _dataClientMock.Verify(x => x.GetBinaryDataStream(
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<int>(),
            It.IsAny<Guid>(),
            It.IsAny<Guid>(),
            It.IsAny<StorageAuthenticationMethod?>(),
            It.Is<CancellationToken>(t => t == context.RequestAborted)
        ),
        Times.AtLeastOnce);
}
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (2)

124-126: Propagate cancellation and make hashing resilient to non-zero stream position.

Pass the request token into ComputeHashAsync and reset Position when possible.

-        string sha256Hash = await GenerateSha256Hash(dataStream);
+        string sha256Hash = await GenerateSha256Hash(dataStream, cancellationToken);
@@
-    private static async Task<string> GenerateSha256Hash(Stream stream)
+    private static async Task<string> GenerateSha256Hash(Stream stream, CancellationToken cancellationToken)
     {
         using var sha256 = SHA256.Create();
-        byte[] digest = await sha256.ComputeHashAsync(stream);
+        if (stream.CanSeek)
+        {
+            try { stream.Position = 0; } catch { /* ignore */ }
+        }
+        byte[] digest = await sha256.ComputeHashAsync(stream, cancellationToken);
         return FormatShaDigest(digest);
     }

Also applies to: 167-172


91-95: Optional: collect all mismatches before returning.

Today you short-circuit on first mismatch. Consider aggregating all invalid signatures to reduce user roundtrips.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84b31ae and ce1bceb.

📒 Files selected for processing (2)
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1 hunks)
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
src/Altinn.App.Core/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

New features should follow the established feature pattern under /src/Altinn.App.Core/Features (feature folder, DI registration, telemetry, and tests)

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
test/Altinn.App.Core.Tests/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧬 Code graph analysis (2)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (4)
src/Altinn.App.Core/Internal/Data/IDataClient.cs (15)
  • Task (26-37)
  • Task (50-58)
  • Task (74-85)
  • Task (98-107)
  • Task (120-128)
  • Task (142-150)
  • Task (163-171)
  • Task (183-190)
  • Task (214-223)
  • Task (236-245)
  • Task (283-291)
  • Task (304-313)
  • Task (323-328)
  • Task (337-342)
  • Task (351-356)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
src/Altinn.App.Core/Models/InstanceIdentifier.cs (1)
  • InstanceOwnerPartyId (111-118)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (3)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (6)
  • SignatureHashValidator (20-185)
  • Task (48-53)
  • Task (55-101)
  • Task (103-145)
  • Task (167-172)
  • ShouldRunForTask (39-43)
src/Altinn.App.Core/Internal/Data/IDataClient.cs (12)
  • Task (26-37)
  • Task (50-58)
  • Task (74-85)
  • Task (98-107)
  • Task (120-128)
  • Task (142-150)
  • Task (163-171)
  • Task (183-190)
  • Task (214-223)
  • Task (236-245)
  • Task (283-291)
  • Task (304-313)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Static code analysis
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (3)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)

45-62: Good coverage of the valid-hash happy path.

Solid setup/mocks, verifies no issues returned.

src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (2)

20-28: Design and defaults look good.

internal sealed, DI, and await using on the stream are all on point.


112-123: Behavior on 404 from storage: confirm desired semantics.

If GetBinaryDataStream throws (e.g., 404), should validator surface a ValidationIssue (e.g., treat as invalid/missing) or bubble the exception to block task exit? Align with platform guidance and update tests accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs (1)

5-22: LGTM! Correct implementation with clear disposal guidance.

The method correctly creates a linked CancellationTokenSource that combines the input token with a timeout. The documentation clearly states the disposal requirement, and the example demonstrates proper usage with using var.

The implementation is efficient and follows .NET best practices for this pattern.

Optional: Consider documenting edge case behavior.

You could enhance the XML documentation to mention behavior for special timeout values:

 /// <summary>
 /// Creates a linked cancellation token source that will be canceled after the specified timeout or when the original token is canceled.
 /// </summary>
 /// <param name="cancellationToken">The cancellation token to link with the timeout.</param>
-/// <param name="timeout">The timeout duration.</param>
+/// <param name="timeout">The timeout duration. Use <see cref="Timeout.InfiniteTimeSpan"/> to disable the timeout.</param>
 /// <returns>A CancellationTokenSource that must be disposed to clean up resources.</returns>
+/// <exception cref="ArgumentOutOfRangeException">Thrown when timeout is negative (except <see cref="Timeout.InfiniteTimeSpan"/>) or too large.</exception>
 /// <example>
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (1)

39-40: Consider ivarne's suggestion to use IHttpClientFactory for cleaner timeout management.

The current approach of combining tokens in every method works but creates repetitive code throughout the class. As ivarne noted in past comments, using IHttpClientFactory with a custom timeout argument could reduce this noise and improve maintainability.

Based on past review comments.

Also applies to: 60-60

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa8624 and 9fa048d.

📒 Files selected for processing (2)
  • src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs (1 hunks)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (26 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
🧬 Code graph analysis (1)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Static code analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (3)
src/Altinn.App.Core/Extensions/CancellationTokenExtensions.cs (1)

3-4: LGTM! Class declaration follows coding guidelines.

The class correctly uses internal accessibility as required by the coding guidelines and static for an extension class.

src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (2)

229-269: LGTM on GetBinaryDataStream streaming implementation.

The method correctly handles disposal semantics:

  • Success path wraps the stream in ResponseWrapperStream to ensure response disposal when the stream is disposed
  • Exception during stream read properly disposes the response before rethrowing
  • Non-success responses are snapshotted and disposed via PlatformHttpResponseSnapshotException

The streaming timeout covers request establishment and header receipt, while allowing the caller to control the pace of reading the stream content.


78-120: Consistent per-operation timeout implementation across all methods.

The pattern of creating a linked CancellationTokenSource with WithTimeout, then passing cts.Token to all authentication, HTTP operations, and content reads is applied consistently throughout the class. This successfully addresses the previous blocker about infinite HttpClient.Timeout requiring per-operation cancellation tokens.

Also applies to: 154-178, 202-217, 283-297, 326-342, 357-374, 449-460, 485-498, 527-552, 581-598, 622-644, 665-682, 699-725, 746-767

…flicts. Might consider using cts for cancellation later.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (6)

222-257: Streaming path: wrapper + snapshot exception are solid.

  • ResponseWrapperStream correctly ties response lifetime to stream disposal.
  • Snapshot exception ensures details without leaking disposed responses.

LGTM. Consider adding XML remarks on the method that callers must dispose the returned stream (use await using).


206-220: 404 semantics and nullability: unify behavior and avoid #nullable suppression.

GetBinaryData returns null on 404 despite Task return type, forcing a #nullable disable block and making consumer behavior inconsistent with GetBinaryDataStream (which throws on 404).

Options:

  • Preferred: change signature to Task<Stream?> and document null on 404.
  • Or: throw (PlatformHttpException) on 404 to align with streaming.

If back‑compat constrains you now, add XML docs clarifying the nullable behavior and plan a breaking change later.

Would you like a follow‑up PR to introduce TryGetBinaryData(..., out Stream?) and obsolete this method in favor of consistent semantics?


447-451: Use structured logging templates instead of string interpolation.

Interpolate-to-string loses structured fields and allocates unnecessarily. Prefer message templates.

-        _logger.LogError(
-            $"Deleting data element {dataGuid} for instance {instanceIdentifier} failed with status code {response.StatusCode}"
-        );
+        _logger.LogError(
+            "Deleting data element {DataGuid} for instance {InstanceIdentifier} failed with status code {StatusCode}",
+            dataGuid, instanceIdentifier, response.StatusCode);
-        _logger.LogError(
-            $"Storing attachment for instance {instanceId} failed with status code {StatusCode} - content {Content}",
-            response.StatusCode, await response.Content.ReadAsStringAsync(cancellationToken));
+        _logger.LogError(
+            "Storing attachment for instance {InstanceId} failed with status code {StatusCode} - content {Content}",
+            instanceId, response.StatusCode, await response.Content.ReadAsStringAsync(cancellationToken));

As per coding guidelines.

Also applies to: 539-543


506-511: URL building: keep relative paths consistently (BaseAddress is already set).

Some methods build absolute URLs with _platformSettings.ApiStorageEndpoint while others use relative paths. Prefer relative paths everywhere; it reduces duplication and misconfiguration risk.

Example:

-        string apiUrl = $"{_platformSettings.ApiStorageEndpoint}instances/{instanceId}/data?dataType={dataType}";
+        string apiUrl = $"instances/{instanceId}/data?dataType={dataType}";

Apply similarly in other absolute URL spots.

Also applies to: 567-573, 616-647


28-29: Optionally seal the class.

Unless inheritance is intended, mark DataClient as sealed.

As per coding guidelines.


63-73: Use a named HttpClient ("StorageStreaming") for the streaming client to enable centralized configuration and explicit naming.

The parameterless CreateClient() call on line 64 works but is less explicit and harder to configure centrally. Adopting the named client pattern improves clarity and testability.

To implement this suggestion:

  1. Change line 64 from httpClientFactory.CreateClient() to httpClientFactory.CreateClient("StorageStreaming")
  2. Register and configure the named client in DI (e.g., in ServiceCollectionExtensions.cs):
    services.AddHttpClient("StorageStreaming")
        .ConfigureHttpClient(client =>
        {
            client.BaseAddress = new Uri(platformSettings.ApiStorageEndpoint);
            client.DefaultRequestHeaders.Add(General.SubscriptionKeyHeaderName, platformSettings.SubscriptionKey);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
            client.Timeout = TimeSpan.FromMinutes(30);
        });
  3. Update test mocks in DataClientTests.cs to handle named client requests.
test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs (2)

1166-1170: Align factory usage with a named client for streaming.

Tests set up CreateClient(string.Empty). If production moves to a named client (e.g., "StorageStreaming"), mock and fixture should match:

-            mocks.HttpClientFactoryMock.Setup(x => x.CreateClient(string.Empty)).Returns(streamingClient);
+            mocks.HttpClientFactoryMock.Setup(x => x.CreateClient("StorageStreaming")).Returns(streamingClient);
...
-                DataClient = new DataClient(httpClient, serviceProvider),
+                DataClient = new DataClient(httpClient, serviceProvider),
                 BaseHttpClient = httpClient,
                 StreamingHttpClient = streamingClient,

And dispose remains as-is.

If you keep relying on a parameterless CreateClient() extension, add a matching mock setup for that extension path; otherwise, switch to the named client approach above.

Also applies to: 1194-1201, 1213-1219


414-450: Rename for accuracy: method returns null, not an empty stream.

Test name suggests “empty stream” but asserts null. Rename to reflect behavior:

-    public async Task GetBinaryData_returns_empty_stream_when_storage_returns_notfound(...)
+    public async Task GetBinaryData_returns_null_when_storage_returns_notfound(...)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa048d and dbc85c4.

📒 Files selected for processing (2)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (24 hunks)
  • test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
  • test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
  • test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs
🧬 Code graph analysis (2)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (4)
test/Altinn.App.Api.Tests/CustomWebApplicationFactory.cs (3)
  • HttpClient (92-105)
  • HttpClient (107-120)
  • HttpClient (126-177)
src/Altinn.App.Core/Features/Telemetry/Telemetry.DataClient.cs (15)
  • Activity (9-14)
  • Activity (16-22)
  • Activity (24-30)
  • Activity (32-38)
  • Activity (40-46)
  • Activity (48-54)
  • Activity (56-62)
  • Activity (64-69)
  • Activity (71-77)
  • Activity (79-84)
  • Activity (86-91)
  • Activity (93-99)
  • Activity (101-107)
  • Activity (109-115)
  • Activity (117-123)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs (3)
test/Altinn.App.Core.Tests/Internal/Auth/AuthenticationTokenResolverTest.cs (1)
  • Fixture (146-203)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (18)
  • Task (76-92)
  • Task (95-140)
  • Task (143-184)
  • Task (187-220)
  • Task (223-257)
  • Task (260-299)
  • Task (302-329)
  • Task (332-369)
  • Task (407-417)
  • Task (420-451)
  • Task (454-491)
  • Task (494-543)
  • Task (546-583)
  • Task (586-627)
  • Task (630-659)
  • Task (662-705)
  • DataClient (28-746)
  • DataClient (47-73)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Static code analysis
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (1)
test/Altinn.App.Core.Tests/Infrastructure/Clients/Storage/DataClientTests.cs (1)

483-525: Streaming tests look good and dispose correctly.

  • Using await using on the returned stream prevents leaks.
  • Error cases assert PlatformHttpResponseSnapshotException and status codes.

LGTM.

Also applies to: 526-597

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (1)

55-72: Consider ivarne's suggestion: use IHttpClientFactory with per-call timeout instead of dedicated streaming client.

As ivarne noted in the past review, the current approach of maintaining a separate _streamingClient with a fixed 30-minute timeout adds noise and lacks flexibility. Based on past review comments.

Consider refactoring to:

  • Remove the dedicated _streamingClient field
  • Inject IHttpClientFactory and call CreateClient() in methods that need custom timeouts
  • Pass timeout as a parameter to allow callers to override when needed

This would reduce the class's surface area and provide better flexibility for different streaming scenarios (some might need more or less than 30 minutes).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbc85c4 and 8c2aa6c.

📒 Files selected for processing (1)
  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (18 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs
🧬 Code graph analysis (1)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (3)
src/Altinn.App.Core/Features/Telemetry/Telemetry.DataClient.cs (15)
  • Activity (9-14)
  • Activity (16-22)
  • Activity (24-30)
  • Activity (32-38)
  • Activity (40-46)
  • Activity (48-54)
  • Activity (56-62)
  • Activity (64-69)
  • Activity (71-77)
  • Activity (79-84)
  • Activity (86-91)
  • Activity (93-99)
  • Activity (101-107)
  • Activity (109-115)
  • Activity (117-123)
src/Altinn.App.Core/Helpers/ResponseWrapperStream.cs (3)
  • ResponseWrapperStream (6-165)
  • ResponseWrapperStream (16-23)
  • Dispose (29-37)
src/Altinn.App.Core/Helpers/PlatformHttpResponseSnapshotException.cs (2)
  • PlatformHttpResponseSnapshotException (18-300)
  • PlatformHttpResponseSnapshotException (157-175)
🪛 GitHub Actions: Verify dotnet format
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs

[error] 122-122: CSharpier formatting check failed. File was not formatted. Expected: Around Line 122; Actual: Around Line 122. Run 'dotnet csharpier check .' (and 'dotnet csharpier write' to fix) to format the file.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Static code analysis
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (1)
src/Altinn.App.Core/Infrastructure/Clients/Storage/DataClient.cs (1)

247-256: Disposal handling is correct.

The try-catch ensures the response is disposed if ReadAsStreamAsync throws, and ResponseWrapperStream takes ownership on success. This properly addresses the past review concerns about response disposal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (2)

124-124: Consider case-insensitive hash comparison for robustness.

While both hashes should be lowercase hex, using case-insensitive comparison (string.Equals(sha256Hash, dataElementSignature.Sha256Hash, StringComparison.OrdinalIgnoreCase)) would be more defensive against format inconsistencies.

Apply this diff if you prefer defensive comparison:

-        if (sha256Hash != dataElementSignature.Sha256Hash)
+        if (!string.Equals(sha256Hash, dataElementSignature.Sha256Hash, StringComparison.OrdinalIgnoreCase))

134-139: Include DataElementId in ValidationIssue for better error reporting.

Users will benefit from knowing which specific data element has an invalid signature, especially when multiple elements are signed.

Apply this diff:

             return new ValidationIssue
             {
                 Code = ValidationIssueCodes.DataElementCodes.InvalidSignatureHash,
                 Severity = ValidationIssueSeverity.Error,
                 CustomTextKey = "backend.validation_errors.invalid_signature_hash",
+                DataElementId = dataElementSignature.DataElementId,
             };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c32f408 and 37c6082.

📒 Files selected for processing (3)
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (1 hunks)
  • src/Altinn.App.Core/Internal/Texts/TranslationService.cs (1 hunks)
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.cs: Use internal accessibility on types by default
Use sealed for classes unless inheritance is a valid use-case
Dispose IDisposable/IAsyncDisposable instances
Do not use .GetAwaiter().GetResult(), .Result(), .Wait(), or other blocking APIs on Task
Do not use the Async suffix for async methods
Write efficient code; avoid unnecessary allocations (e.g., avoid repeated ToString calls; consider for-loops over LINQ when appropriate)
Do not invoke the same async operation multiple times in the same code path unless necessary
Avoid awaiting async operations inside tight loops; prefer batching with a sensible upper bound on parallelism
Use CSharpier for formatting (required before commits; formatting also runs on build via CSharpier.MSBuild)

Files:

  • src/Altinn.App.Core/Internal/Texts/TranslationService.cs
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Nullable Reference Types

Files:

  • src/Altinn.App.Core/Internal/Texts/TranslationService.cs
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

src/{Altinn.App.Api,Altinn.App.Core}/**/*.cs: Types meant to be implemented by apps should be marked with the ImplementableByApps attribute
For HTTP APIs, define DTOs with ...Request and ...Response naming

Files:

  • src/Altinn.App.Core/Internal/Texts/TranslationService.cs
  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
src/Altinn.App.Core/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

New features should follow the established feature pattern under /src/Altinn.App.Core/Features (feature folder, DI registration, telemetry, and tests)

Files:

  • src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs
test/Altinn.App.Core.Tests/Features/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.cs: Test projects mirror source structure
Prefer xUnit asserts over FluentAssertions
Mock external dependencies with Moq

Files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧠 Learnings (1)
📚 Learning: 2025-08-29T11:06:37.385Z
Learnt from: CR
PR: Altinn/app-lib-dotnet#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T11:06:37.385Z
Learning: Applies to test/Altinn.App.Core.Tests/Features/**/*.cs : Provide corresponding test coverage for each Core feature under /test/Altinn.App.Core.Tests/Features

Applied to files:

  • test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs
🧬 Code graph analysis (3)
src/Altinn.App.Core/Internal/Texts/TranslationService.cs (1)
src/Altinn.App.Core/Internal/Language/LanguageConst.cs (1)
  • LanguageConst (3-8)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (4)
src/Altinn.App.Core/Internal/Data/IDataClient.cs (15)
  • Task (26-37)
  • Task (50-58)
  • Task (74-85)
  • Task (98-107)
  • Task (120-128)
  • Task (140-146)
  • Task (159-167)
  • Task (179-186)
  • Task (210-219)
  • Task (232-241)
  • Task (279-287)
  • Task (300-309)
  • Task (319-324)
  • Task (333-338)
  • Task (347-352)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
src/Altinn.App.Core/Models/InstanceIdentifier.cs (1)
  • InstanceOwnerPartyId (111-118)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (5)
src/Altinn.App.Core/Internal/Sign/SignatureContext.cs (1)
  • Signee (85-109)
src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (6)
  • SignatureHashValidator (20-183)
  • Task (48-53)
  • Task (55-101)
  • Task (103-143)
  • Task (165-170)
  • ShouldRunForTask (39-43)
src/Altinn.App.Core/Internal/Data/IDataClient.cs (12)
  • Task (26-37)
  • Task (50-58)
  • Task (74-85)
  • Task (98-107)
  • Task (120-128)
  • Task (140-146)
  • Task (159-167)
  • Task (179-186)
  • Task (210-219)
  • Task (232-241)
  • Task (279-287)
  • Task (300-309)
src/Altinn.App.Core/Models/Validation/ValidationIssue.cs (1)
  • ValidationIssue (9-105)
src/Altinn.App.Core/Models/Validation/ValidationIssueCodes.cs (2)
  • ValidationIssueCodes (6-79)
  • DataElementCodes (27-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run dotnet build and test (windows-latest)
  • GitHub Check: Run dotnet build and test (ubuntu-latest)
  • GitHub Check: Run dotnet build and test (macos-latest)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Static code analysis
🔇 Additional comments (8)
src/Altinn.App.Core/Internal/Texts/TranslationService.cs (1)

202-212: LGTM! Clean addition of localized fallback text.

The new backend fallback resource follows the established pattern and provides appropriate translations for signature hash validation errors across all supported languages.

test/Altinn.App.Core.Tests/Features/Validators/Default/SignatureHashValidatorTests.cs (1)

22-388: Excellent test coverage!

The test suite comprehensively covers all critical paths including valid/invalid hashes, authentication modes, error handling, and behavioral contracts. The test structure is clean, well-organized, and follows xUnit best practices.

Based on learnings

src/Altinn.App.Core/Features/Validation/Default/SignatureHashValidator.cs (6)

20-43: LGTM! Clean implementation following coding guidelines.

The class properly uses internal sealed modifiers and implements task-specific validation logic correctly by checking the task type in ShouldRunForTask.

As per coding guidelines


45-53: LGTM! Correct NoIncrementalValidation pattern.

The validator correctly signals that incremental validation is not supported and throws UnreachableException in HasRelevantChanges to catch programming errors.


55-101: LGTM! Well-structured validation flow.

The method properly propagates cancellation tokens, loads required configuration and metadata, and uses fail-fast semantics by returning on the first invalid signature. Structured logging on success is a nice touch.


112-120: Excellent! Proper async disposal prevents resource leaks.

The await using pattern ensures the stream and underlying HTTP response are disposed correctly, even when handling large data elements with the streaming API.


145-163: LGTM! Solid error handling and configuration validation.

The method correctly identifies restricted-read data types and throws a clear exception when the data type is misconfigured, helping catch configuration errors early.


165-182: LGTM! Correct SHA-256 hash generation and formatting.

The implementation properly disposes the SHA256 instance and formats the digest consistently with the storage service format (lowercase hex). The documentation note about staying in sync with altinn-storage is valuable.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@bjorntore bjorntore merged commit bdd828d into main Oct 22, 2025
13 of 14 checks passed
@bjorntore bjorntore deleted the feat/signature-validation branch October 22, 2025 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches feature Label Pull requests with new features. Used when generation releasenotes

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

4 participants