Skip to content

9.0.0

Choose a tag to compare

@petrsvihlik petrsvihlik released this 05 Jun 23:36
· 148 commits to master since this release
3224049

9.0.0

Highlights

This is the Minimal-API release. The entire WOPI surface has moved off MVC controllers onto ASP.NET Core Minimal APIs (#438) — wired with a single app.MapWopiEndpoints(), with the override-multiplexed POST {id} routes dispatched through a custom matcher policy so each X-WOPI-Override keeps its own auth policy. Both sample frontends were rebuilt on Razor Components (#470, #479).

Around that core sits a big automation & supply-chain investment — CodeQL scanning, an agentic Claude PR-reviewer, a fork-friendly conditional Cobalt build, grouped Dependabot PRs, and a self-improving codebase-audit skill — plus cross-platform IWopiFile.Owner, a path-traversal hardening, and a Redis lock-eviction fix.

⚠️ Requires .NET 10. v9 drops net8.0/net9.0 multitargeting — the libraries are net10.0-only (#432). The breaking surface is smaller than 8.0 and concentrated in the host wiring (controllers → endpoints), the host-customization seam (no more HttpContext), and PascalCase header/auth constants. See Migration guide below.

✨ New

  • WOPI endpoints as Minimal APIs (#438, closes #430) — controllers retired in favour of app.MapWopiEndpoints(); faster startup, trimmer-friendlier, and each X-WOPI-Override route gets its own authorization policy via WopiOverrideMatcherPolicy. A minimal-API idiom audit followed in #470.
  • Razor Components sample frontends (#470, #479) — WopiHost.Web and WopiHost.Web.Oidc migrated from MVC/Razor Pages to Razor Components, with full OIDC sign-in / sign-out flow coverage (#481).
  • IWopiFile.Owner on every platform (#499) — owner resolution is no longer Windows-only, with a macOS passwd-struct SIGSEGV fixed along the way (#506).
  • Storage-provider conformance suite (#512) — WopiHost.Abstractions.Testing now ships StorageProviderConformanceTests alongside the existing lock-provider suite, so every IWopiStorageProvider runs the same battery. Adding a provider = one sealed subclass.
  • Collabora end-to-end test suite (#435, closes #357 Approach B) — real-browser editing against a live Collabora container via Aspire.Hosting.Testing + Playwright.
  • CI / automation overhaul
    • PR auto-labeler, CodeQL analysis, and a conditional Cobalt build so forks without the private feed build the rest of the solution cleanly (#489).
    • Agentic PR review via the Claude Code action (#486, #524, #525) — internal PRs reviewed as claude[bot], @claude on-demand for forks.
    • Dependabot minor/patch updates grouped into bulk PRs (#523) — far less PR noise.
  • codebase-audit skill (#516, #518, #528) — a repeatable, self-improving health-check sweeping duplication, architectural drift, .NET design-guideline conformance, security and library-hygiene vectors, and WOPI-spec compliance.

🐛 Fixes

  • Path-traversal gap in file/container creation (#530) — CreateWopiChildFile / CreateWopiChildContainer now run the same single-segment name validation as the rename/suggested-name paths, rejecting ../ and separator characters.
  • PutRelativeFile returns 501 on unauthorized overwrite (#472) — spec-correct response when the target exists and overwrite isn't permitted.
  • Redis lock provider proactively evicts expired locks on the CAS read path (#491).
  • BlobIdMap thread-safety race fixed (#521).
  • Collabora domain regex narrowed from .* to host.docker.internal.* (#490).
  • FileSystem storage provider aligned on Singleton DI lifetime (#507); storage providers register with TryAdd for a clean host-override path (#509).
  • Audit-driven correctness batches from #456 / #457#464, #474, #484, with added proof-validation integration coverage.

🧹 Refactors & internals

  • HttpContext dropped from customization seams (#476) — IWopiHostExtensions hooks now receive a typed context record carrying the ClaimsPrincipal and resource, not the raw HttpContext. (See breaking changes.)
  • Shared sample code extracted to WopiHost.Web.Shared (#487) — view models + WopiOptions deduplicated across the frontends.
  • Provider config/options consistency (#510) and AppHost Redis switched to Session lifetime (#478).
  • Spec-helper extractionsEnsureExactlyOneOf for mutex headers (#477), ValidatePutFileLock (#513), bounded-body ReadBytesAsync overload (#492), inlined token-issuance helpers to keep Infer# clean (#483).
  • Codebase-wide comment cleanup + enforced comment-style rules (#508); documented ProblemDetails / UseStatusCodePages strategy (#482).

🔧 Maintenance, CI & dependencies

  • Target frameworks: dropped net8.0/net9.0 multitargeting — libraries are net10.0-only (#432).
  • Package-validation baseline restored to 7.0.0 for the 8.0.0 cut, then bumped to 8.0.0 with the post-release auto-bump hardened against a race (#431, #434).
  • Test-package management centralized into the root Directory.Packages.props (#465); test-package bumps Dependabot wrongly auto-closed were re-applied (#445, #453, #463).
  • src/ line coverage raised 95.7% → 97.7% (#433).
  • Dependency bumps (consolidated): Aspire.Hosting.* 13.3.2 → 13.4.2, StackExchange.Redis 2.9.32 → 2.13.17, Azure.Storage.Blobs 12.28.0 → 12.29.0, Microsoft.IdentityModel.Tokens / System.IdentityModel.Tokens.Jwt 8.18 → 8.19.1, Microsoft.Playwright 1.59 → 1.60, Microsoft.NET.Test.Sdk 18.5.1 → 18.6.0, Scalar.AspNetCore 2.14.11 → 2.14.14, actions/labeler 5 → 6, actions/checkout 4 → 6.

💥 Breaking changes

Before (8.x) After (9.0) PR
Target framework net8.0 / net9.0 / net10.0 net10.0 only #432
WOPI route registration MVC controllers (app.MapControllers()) Minimal APIs (app.MapWopiEndpoints()) #438
Host-customization seam hooks received HttpContext hooks receive a context record (ClaimsPrincipal? User, resource, payload) #476
Header constants WopiHeaders.WOPI_OVERRIDE, ITEM_VERSION, PROOF, TIMESTAMP, … (SCREAMING_SNAKE) WopiOverride, ItemVersion, Proof, Timestamp, … (PascalCase) — values unchanged #529
Auth constants AccessTokenDefaults.AUTHENTICATION_SCHEME, ACCESS_TOKEN_QUERY_NAME AuthenticationScheme, AccessTokenQueryName #529
FileSystem provider DI lifetime as-registered Singleton #507

Migration guide

1. Target framework

WopiHost 9.x runs on .NET 10 only. Multitargeting back to net8.0/net9.0 is gone — stay on the 8.x line if you can't move to net10 yet.

2. Endpoint registration (controllers → Minimal APIs)

The WOPI surface is no longer MVC controllers. Map the endpoints instead, and drop any AddControllers() / AddMvc() you only added for WopiHost:

 var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddControllers();
 builder.Services.AddWopi();

 var app = builder.Build();
 app.UseAuthentication();
 app.UseAuthorization();
-app.MapControllers();
+app.MapWopiEndpoints();

3. Host-customization seam (no more HttpContext)

IWopiHostExtensions hooks now take a typed context record exposing the authenticated ClaimsPrincipal and the resource — not the raw HttpContext:

 public sealed class MyHostExtensions : IWopiHostExtensions
 {
-    public Task<WopiCheckFileInfo> OnCheckFileInfoAsync(WopiCheckFileInfo cfi, HttpContext ctx, CancellationToken ct)
-        => ApplyTenantOverrides(cfi, ctx.User, ct);
+    public Task<WopiCheckFileInfo> OnCheckFileInfoAsync(WopiCheckFileInfoContext context, CancellationToken ct = default)
+        => ApplyTenantOverrides(context.CheckFileInfo, context.User, ct);
 }

WopiCheckFileInfoContext is (ClaimsPrincipal? User, IWopiFile File, WopiCheckFileInfo CheckFileInfo); the container/folder/ecosystem hooks follow the same shape. If you relied on something only reachable from HttpContext, surface it through DI instead.

4. Renamed constants (PascalCase)

Public header/auth constants were renamed to satisfy CA1707. Values are identical — only the symbol names changed, so it's a recompile, not a behavior change:

-context.Request.Headers[WopiHeaders.WOPI_OVERRIDE]
+context.Request.Headers[WopiHeaders.WopiOverride]

-AccessTokenDefaults.AUTHENTICATION_SCHEME
+AccessTokenDefaults.AuthenticationScheme

The same SCREAMING_SNAKE → PascalCase mapping applies across all of WopiHeaders (e.g. ITEM_VERSIONItemVersion, PROOFProof, TIMESTAMPTimestamp) and AccessTokenDefaults (ACCESS_TOKEN_QUERY_NAMEAccessTokenQueryName).

5. FileSystem provider lifetime

AddFileSystemStorageProvider now registers the provider as a Singleton via TryAdd. If you pre-register your own IWopiStorageProvider, yours still wins; if you depended on per-request (scoped) state inside the default provider, move that state out — the instance is now shared process-wide.


Full Changelog: 8.0.0...9.0.0