diff --git a/.github/BUILD.md b/.github/BUILD.md
index d0143bfbe2..012afcae86 100644
--- a/.github/BUILD.md
+++ b/.github/BUILD.md
@@ -79,13 +79,12 @@ Conversely, if you are working on front-end only, you want to build the back-end
"AuthorizeCallbackLogoutPathName": "/logout",
"AuthorizeCallbackErrorPathName": "/error",
"BackOfficeTokenCookie": {
- "Enabled": true,
"SameSite": "None"
}
```
> [!NOTE]
-> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `BackOfficeTokenCookie` settings are correct. Namely, that `SameSite` should be set to `None` when running the front-end server separately.
+> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `Umbraco:Cms:Security:BackOfficeTokenCookie:SameSite` setting is set to `None`.
Then run Umbraco from the command line.
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 858984a9d3..fba0c7a6bf 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -6,91 +6,101 @@ Always reference these instructions first and fallback to search or bash command
Bootstrap, build, and test the repository:
-- Install .NET SDK (version specified in global.json):
- - `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version $(jq -r '.sdk.version' global.json)`
- - `export PATH="/home/runner/.dotnet:$PATH"`
-- Install Node.js (version specified in src/Umbraco.Web.UI.Client/.nvmrc):
- - `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash`
- - `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`
- - `nvm install $(cat src/Umbraco.Web.UI.Client/.nvmrc) && nvm use $(cat src/Umbraco.Web.UI.Client/.nvmrc)`
-- Fix shallow clone issue (required for GitVersioning):
- - `git fetch --unshallow`
-- Restore packages:
- - `dotnet restore` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
-- Build the solution:
- - `dotnet build` -- takes 4.5 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
-- Install and build frontend:
- - `cd src/Umbraco.Web.UI.Client`
- - `npm ci --no-fund --no-audit --prefer-offline` -- takes 11 seconds.
- - `npm run build:for:cms` -- takes 1.25 minutes. NEVER CANCEL. Set timeout to 5+ minutes.
-- Install and build Login
- - `cd src/Umbraco.Web.UI.Login`
- - `npm ci --no-fund --no-audit --prefer-offline`
- - `npm run build`
-- Run the application:
- - `cd src/Umbraco.Web.UI`
- - `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
+- Install .NET SDK (version specified in global.json):
+ - `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version $(jq -r '.sdk.version' global.json)`
+ - `export PATH="/home/runner/.dotnet:$PATH"`
+- Install Node.js (version specified in src/Umbraco.Web.UI.Client/.nvmrc):
+ - `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash`
+ - `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`
+ - `nvm install $(cat src/Umbraco.Web.UI.Client/.nvmrc) && nvm use $(cat src/Umbraco.Web.UI.Client/.nvmrc)`
+- Fix shallow clone issue (required for GitVersioning):
+ - `git fetch --unshallow`
+- Restore packages:
+ - `dotnet restore` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
+- Build the solution:
+ - `dotnet build` -- takes 4.5 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
+- Install and build frontend:
+ - `cd src/Umbraco.Web.UI.Client`
+ - `npm ci --no-fund --no-audit --prefer-offline` -- takes 11 seconds.
+ - `npm run build:for:cms` -- takes 1.25 minutes. NEVER CANCEL. Set timeout to 5+ minutes.
+- Install and build Login
+ - `cd src/Umbraco.Web.UI.Login`
+ - `npm ci --no-fund --no-audit --prefer-offline`
+ - `npm run build`
+- Run the application:
+ - `cd src/Umbraco.Web.UI`
+ - `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
+
+Check out [BUILD.md](./BUILD.md) for more detailed instructions.
## Validation
-- ALWAYS run through at least one complete end-to-end scenario after making changes.
-- Build and unit tests must pass before committing changes.
-- Frontend build produces output in src/Umbraco.Web.UI.Client/dist-cms/ which gets copied to src/Umbraco.Web.UI/wwwroot/umbraco/backoffice/
-- Always run `dotnet build` and `npm run build:for:cms` before running the application to see your changes.
-- For login-only changes, you can run `npm run build` from src/Umbraco.Web.UI.Login and then `dotnet run --no-build` from src/Umbraco.Web.UI.
-- For frontend-only changes, you can run `npm run dev:server` from src/Umbraco.Web.UI.Client for hot reloading.
-- Frontend changes should be linted using `npm run lint:fix` which uses Eslint.
+- ALWAYS run through at least one complete end-to-end scenario after making changes.
+- Build and unit tests must pass before committing changes.
+- Frontend build produces output in src/Umbraco.Web.UI.Client/dist-cms/ which gets copied to src/Umbraco.Web.UI/wwwroot/umbraco/backoffice/
+- Always run `dotnet build` and `npm run build:for:cms` before running the application to see your changes.
+- For login-only changes, you can run `npm run build` from src/Umbraco.Web.UI.Login and then `dotnet run --no-build` from src/Umbraco.Web.UI.
+- For frontend-only changes, you can run `npm run dev:server` from src/Umbraco.Web.UI.Client for hot reloading.
+- Frontend changes should be linted using `npm run lint:fix` which uses Eslint.
## Testing
### Unit Tests (.NET)
-- Location: tests/Umbraco.Tests.UnitTests/
-- Run: `dotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj --configuration Release --verbosity minimal`
-- Duration: ~1 minute with 3,343 tests
-- NEVER CANCEL: Set timeout to 5+ minutes
+
+- Location: tests/Umbraco.Tests.UnitTests/
+- Run: `dotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj --configuration Release --verbosity minimal`
+- Duration: ~1 minute with 3,343 tests
+- NEVER CANCEL: Set timeout to 5+ minutes
### Integration Tests (.NET)
-- Location: tests/Umbraco.Tests.Integration/
-- Run: `dotnet test tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj --configuration Release --verbosity minimal`
-- NEVER CANCEL: Set timeout to 10+ minutes
+
+- Location: tests/Umbraco.Tests.Integration/
+- Run: `dotnet test tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj --configuration Release --verbosity minimal`
+- NEVER CANCEL: Set timeout to 10+ minutes
### Frontend Tests
-- Location: src/Umbraco.Web.UI.Client/
-- Run: `npm test` (requires `npx playwright install` first)
-- Frontend tests use Web Test Runner with Playwright
+
+- Location: src/Umbraco.Web.UI.Client/
+- Run: `npm test` (requires `npx playwright install` first)
+- Frontend tests use Web Test Runner with Playwright
### Acceptance Tests (E2E)
-- Location: tests/Umbraco.Tests.AcceptanceTest/
-- Requires running Umbraco application and configuration
-- See tests/Umbraco.Tests.AcceptanceTest/README.md for detailed setup (requires `npx playwright install` first)
+
+- Location: tests/Umbraco.Tests.AcceptanceTest/
+- Requires running Umbraco application and configuration
+- See tests/Umbraco.Tests.AcceptanceTest/README.md for detailed setup (requires `npx playwright install` first)
## Project Structure
The solution contains 30 C# projects organized as follows:
### Main Application Projects
-- **Umbraco.Web.UI**: Main web application project (startup project)
-- **Umbraco.Web.UI.Client**: TypeScript frontend (backoffice)
-- **Umbraco.Web.UI.Login**: Separate login screen frontend
-- **Umbraco.Core**: Core domain models and interfaces
-- **Umbraco.Infrastructure**: Data access and infrastructure
-- **Umbraco.Cms**: Main CMS package
+
+- **Umbraco.Web.UI**: Main web application project (startup project)
+- **Umbraco.Web.UI.Client**: TypeScript frontend (backoffice)
+- **Umbraco.Web.UI.Login**: Separate login screen frontend
+- **Umbraco.Core**: Core domain models and interfaces
+- **Umbraco.Infrastructure**: Data access and infrastructure
+- **Umbraco.Cms**: Main CMS package
### API Projects
-- **Umbraco.Cms.Api.Management**: Management API
-- **Umbraco.Cms.Api.Delivery**: Content Delivery API
-- **Umbraco.Cms.Api.Common**: Shared API components
+
+- **Umbraco.Cms.Api.Management**: Management API
+- **Umbraco.Cms.Api.Delivery**: Content Delivery API
+- **Umbraco.Cms.Api.Common**: Shared API components
### Persistence Projects
-- **Umbraco.Cms.Persistence.SqlServer**: SQL Server support
-- **Umbraco.Cms.Persistence.Sqlite**: SQLite support
-- **Umbraco.Cms.Persistence.EFCore**: Entity Framework Core abstractions
+
+- **Umbraco.Cms.Persistence.SqlServer**: SQL Server support
+- **Umbraco.Cms.Persistence.Sqlite**: SQLite support
+- **Umbraco.Cms.Persistence.EFCore**: Entity Framework Core abstractions
### Test Projects
-- **Umbraco.Tests.UnitTests**: Unit tests
-- **Umbraco.Tests.Integration**: Integration tests
-- **Umbraco.Tests.AcceptanceTest**: End-to-end tests with Playwright
-- **Umbraco.Tests.Common**: Shared test utilities
+
+- **Umbraco.Tests.UnitTests**: Unit tests
+- **Umbraco.Tests.Integration**: Integration tests
+- **Umbraco.Tests.AcceptanceTest**: End-to-end tests with Playwright
+- **Umbraco.Tests.Common**: Shared test utilities
## Common Tasks
@@ -98,6 +108,7 @@ The solution contains 30 C# projects organized as follows:
**Production Mode (Standard Development)**
Use this for backend development, testing full builds, or when you don't need hot reloading:
+
1. Build frontend assets: `cd src/Umbraco.Web.UI.Client && npm run build:for:cms`
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Access backoffice: `https://localhost:44339/umbraco`
@@ -105,17 +116,17 @@ Use this for backend development, testing full builds, or when you don't need ho
**Vite Dev Server Mode (Frontend Development with Hot Reload)**
Use this for frontend-only development with hot module reloading:
+
1. Configure backend for frontend development - Add to `src/Umbraco.Web.UI/appsettings.json` under `Umbraco:CMS:Security`:
- ```json
- "BackOfficeHost": "http://localhost:5173",
- "AuthorizeCallbackPathName": "/oauth_complete",
- "AuthorizeCallbackLogoutPathName": "/logout",
- "AuthorizeCallbackErrorPathName": "/error",
- "BackOfficeTokenCookie": {
- "Enabled": true,
- "SameSite": "None"
- }
- ```
+ ```json
+ "BackOfficeHost": "http://localhost:5173",
+ "AuthorizeCallbackPathName": "/oauth_complete",
+ "AuthorizeCallbackLogoutPathName": "/logout",
+ "AuthorizeCallbackErrorPathName": "/error",
+ "BackOfficeTokenCookie": {
+ "SameSite": "None"
+ }
+ ```
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
3. Run frontend dev server: `cd src/Umbraco.Web.UI.Client && npm run dev:server`
4. Access backoffice: `http://localhost:5173/` (no `/umbraco` prefix)
@@ -124,39 +135,48 @@ Use this for frontend-only development with hot module reloading:
**Important:** Remove the `BackOfficeHost` configuration before committing or switching back to production mode.
### Backend-Only Development
+
For backend-only changes, disable frontend builds:
-- Comment out the target named "BuildStaticAssetsPreconditions" in src/Umbraco.Cms.StaticAssets.csproj:
- ```
-
- ```
-- Remember to uncomment before committing
+
+- Comment out the target named "BuildStaticAssetsPreconditions" in src/Umbraco.Cms.StaticAssets.csproj:
+ ```
+
+ ```
+- Remember to uncomment before committing
### Building NuGet Packages
+
To build custom NuGet packages for testing:
+
```bash
dotnet pack -c Release -o Build.Out
dotnet nuget add source [Path to Build.Out folder] -n MyLocalFeed
```
### Regenerating Frontend API Types
+
When changing Management API:
+
```bash
cd src/Umbraco.Web.UI.Client
npm run generate:server-api-dev
```
+
Also update OpenApi.json from /umbraco/swagger/management/swagger.json
## Database Setup
Default configuration supports SQLite for development. For production-like testing:
-- Use SQL Server/LocalDb for better performance
-- Configure connection string in src/Umbraco.Web.UI/appsettings.json
+
+- Use SQL Server/LocalDb for better performance
+- Configure connection string in src/Umbraco.Web.UI/appsettings.json
## Clean Up / Reset
To reset development environment:
+
```bash
# Remove configuration and database
rm src/Umbraco.Web.UI/appsettings.json
@@ -168,31 +188,31 @@ git clean -xdf .
## Version Information
-- Target Framework: .NET (version specified in global.json)
-- Current Version: (specified in version.json)
-- Node.js Requirement: (specified in src/Umbraco.Web.UI.Client/.nvmrc)
-- npm Requirement: Latest compatible version
+- Target Framework: .NET (version specified in global.json)
+- Current Version: (specified in version.json)
+- Node.js Requirement: (specified in src/Umbraco.Web.UI.Client/.nvmrc)
+- npm Requirement: Latest compatible version
## Known Issues
-- Build requires full git history (not shallow clone) due to GitVersioning
-- Some NuGet package security warnings are expected (SixLabors.ImageSharp vulnerabilities)
-- Frontend tests require Playwright browser installation: `npx playwright install`
-- Older Node.js versions may show engine compatibility warnings (check .nvmrc for current requirement)
+- Build requires full git history (not shallow clone) due to GitVersioning
+- Some NuGet package security warnings are expected (SixLabors.ImageSharp vulnerabilities)
+- Frontend tests require Playwright browser installation: `npx playwright install`
+- Older Node.js versions may show engine compatibility warnings (check .nvmrc for current requirement)
## Timing Expectations
**NEVER CANCEL** these operations - they are expected to take time:
-| Operation | Expected Time | Timeout Setting |
-|-----------|--------------|-----------------|
-| `dotnet restore` | 50 seconds | 90+ seconds |
-| `dotnet build` | 4.5 minutes | 10+ minutes |
-| `npm ci` | 11 seconds | 30+ seconds |
-| `npm run build:for:cms` | 1.25 minutes | 5+ minutes |
-| `npm test` | 2 minutes | 5+ minutes |
-| `npm run lint` | 1 minute | 5+ minutes |
-| Unit tests | 1 minute | 5+ minutes |
-| Integration tests | Variable | 10+ minutes |
+| Operation | Expected Time | Timeout Setting |
+| ----------------------- | ------------- | --------------- |
+| `dotnet restore` | 50 seconds | 90+ seconds |
+| `dotnet build` | 4.5 minutes | 10+ minutes |
+| `npm ci` | 11 seconds | 30+ seconds |
+| `npm run build:for:cms` | 1.25 minutes | 5+ minutes |
+| `npm test` | 2 minutes | 5+ minutes |
+| `npm run lint` | 1 minute | 5+ minutes |
+| Unit tests | 1 minute | 5+ minutes |
+| Integration tests | Variable | 10+ minutes |
-Always wait for commands to complete rather than canceling and retrying.
\ No newline at end of file
+Always wait for commands to complete rather than canceling and retrying.
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c56f06dc2f..89409b0d61 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -107,7 +107,6 @@
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKLOGOUTPATHNAME": "/logout",
"UMBRACO__CMS__SECURITY__AUTHORIZECALLBACKERRORPATHNAME": "/error",
"UMBRACO__CMS__SECURITY__KEEPUSERLOGGEDIN": "true",
- "UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__ENABLED": "true",
"UMBRACO__CMS__SECURITY__BACKOFFICETOKENCOOKIE__SAMESITE": "None"
},
"sourceFileMap": {
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d07032a3ff..416a91cec3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,27 +12,27 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/HideBackOfficeTokensHandler.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/HideBackOfficeTokensHandler.cs
index 8d1dbd040e..725d613183 100644
--- a/src/Umbraco.Cms.Api.Common/DependencyInjection/HideBackOfficeTokensHandler.cs
+++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/HideBackOfficeTokensHandler.cs
@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Api.Common.DependencyInjection;
internal sealed class HideBackOfficeTokensHandler
: IOpenIddictServerHandler,
+ IOpenIddictServerHandler,
IOpenIddictServerHandler,
IOpenIddictValidationHandler,
INotificationHandler
@@ -22,6 +23,7 @@ internal sealed class HideBackOfficeTokensHandler
private const string RedactedTokenValue = "[redacted]";
private const string AccessTokenCookieKey = "__Host-umbAccessToken";
private const string RefreshTokenCookieKey = "__Host-umbRefreshToken";
+ private const string PkceCodeCookieKey = "__Host-umbPkceCode";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IDataProtectionProvider _dataProtectionProvider;
@@ -70,6 +72,28 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
+ ///
+ /// This is invoked when a PKCE code is issued to the client. For the back-office client, we will intercept the
+ /// response, write the PKCE code from the response into a HTTP-only cookie, and redact the code from the response,
+ /// so it's not exposed to the client.
+ ///
+ public ValueTask HandleAsync(OpenIddictServerEvents.ApplyAuthorizationResponseContext context)
+ {
+ if (context.Request?.ClientId is not Constants.OAuthClientIds.BackOffice)
+ {
+ // Only ever handle the back-office client.
+ return ValueTask.CompletedTask;
+ }
+
+ if (context.Response.Code is not null)
+ {
+ SetCookie(GetHttpContext(), PkceCodeCookieKey, context.Response.Code);
+ context.Response.Code = RedactedTokenValue;
+ }
+
+ return ValueTask.CompletedTask;
+ }
+
///
/// This is invoked when requesting new tokens.
///
@@ -81,7 +105,23 @@ internal sealed class HideBackOfficeTokensHandler
return ValueTask.CompletedTask;
}
- // For the back-office client, this only happens when a refresh token is being exchanged for a new access token.
+ // Handle when the PKCE code is being exchanged for an access token.
+ if (context.Request.Code == RedactedTokenValue
+ && TryGetCookie(PkceCodeCookieKey, out var code))
+ {
+ context.Request.Code = code;
+
+ // We won't need the PKCE cookie after this, let's remove it.
+ RemoveCookie(GetHttpContext(), PkceCodeCookieKey);
+ }
+ else
+ {
+ // PCKE codes should always be redacted. If we got here, someone might be trying to pass another PKCE
+ // code. For security reasons, explicitly discard the code (if any) to be on the safe side.
+ context.Request.Code = null;
+ }
+
+ // Handle when a refresh token is being exchanged for a new access token.
if (context.Request.RefreshToken == RedactedTokenValue
&& TryGetCookie(RefreshTokenCookieKey, out var refreshToken))
{
@@ -95,7 +135,6 @@ internal sealed class HideBackOfficeTokensHandler
context.Request.RefreshToken = null;
}
-
return ValueTask.CompletedTask;
}
@@ -140,7 +179,15 @@ internal sealed class HideBackOfficeTokensHandler
{
var cookieValue = EncryptionHelper.Encrypt(value, _dataProtectionProvider);
- var cookieOptions = new CookieOptions
+ RemoveCookie(httpContext, key);
+ httpContext.Response.Cookies.Append(key, cookieValue, GetCookieOptions(httpContext));
+ }
+
+ private void RemoveCookie(HttpContext httpContext, string key)
+ => httpContext.Response.Cookies.Delete(key, GetCookieOptions(httpContext));
+
+ private CookieOptions GetCookieOptions(HttpContext httpContext) =>
+ new()
{
// Prevent the client-side scripts from accessing the cookie.
HttpOnly = true,
@@ -164,10 +211,6 @@ internal sealed class HideBackOfficeTokensHandler
SameSite = ParseSameSiteMode(_backOfficeTokenCookieSettings.SameSite),
};
- httpContext.Response.Cookies.Delete(key, cookieOptions);
- httpContext.Response.Cookies.Append(key, cookieValue, cookieOptions);
- }
-
private bool TryGetCookie(string key, [NotNullWhen(true)] out string? value)
{
if (GetHttpContext().Request.Cookies.TryGetValue(key, out var cookieValue))
diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
index 487de19908..75a15813da 100644
--- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
+++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderAuthExtensions.cs
@@ -120,21 +120,24 @@ public static class UmbracoBuilderAuthExtensions
configuration.UseSingletonHandler().SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
});
- if (hideBackOfficeTokens)
+ options.AddEventHandler(configuration =>
{
- options.AddEventHandler(configuration =>
- {
- configuration
- .UseSingletonHandler()
- .SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ProcessJsonResponse.Descriptor.Order - 1);
- });
- options.AddEventHandler(configuration =>
- {
- configuration
- .UseSingletonHandler()
- .SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest.Descriptor.Order + 1);
- });
- }
+ configuration
+ .UseSingletonHandler()
+ .SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ProcessJsonResponse.Descriptor.Order - 1);
+ });
+ options.AddEventHandler(configuration =>
+ {
+ configuration
+ .UseSingletonHandler()
+ .SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.Authentication.ProcessQueryResponse.Descriptor.Order - 1);
+ });
+ options.AddEventHandler(configuration =>
+ {
+ configuration
+ .UseSingletonHandler()
+ .SetOrder(OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers.ExtractPostRequest.Descriptor.Order + 1);
+ });
})
// Register the OpenIddict validation components.
@@ -160,24 +163,18 @@ public static class UmbracoBuilderAuthExtensions
configuration.UseSingletonHandler().SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ResolveRequestUri.Descriptor.Order - 1);
});
- if (hideBackOfficeTokens)
+ options.AddEventHandler(configuration =>
{
- options.AddEventHandler(configuration =>
- {
- configuration
- .UseSingletonHandler()
- // IMPORTANT: the handler must be AFTER the built-in query string handler, because the client-side SignalR library sometimes appends access tokens to the query string.
- .SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ExtractAccessTokenFromQueryString.Descriptor.Order + 1);
- });
- }
+ configuration
+ .UseSingletonHandler()
+ // IMPORTANT: the handler must be AFTER the built-in query string handler, because the client-side SignalR library sometimes appends access tokens to the query string.
+ .SetOrder(OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers.ExtractAccessTokenFromQueryString.Descriptor.Order + 1);
+ });
});
builder.Services.AddSingleton();
builder.Services.ConfigureOptions();
- if (hideBackOfficeTokens)
- {
- builder.AddNotificationHandler();
- }
+ builder.AddNotificationHandler();
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Services/NewsDashboard/NewsDashboardService.cs b/src/Umbraco.Cms.Api.Management/Services/NewsDashboard/NewsDashboardService.cs
index 2d05e20df3..6dfe7fde54 100644
--- a/src/Umbraco.Cms.Api.Management/Services/NewsDashboard/NewsDashboardService.cs
+++ b/src/Umbraco.Cms.Api.Management/Services/NewsDashboard/NewsDashboardService.cs
@@ -46,7 +46,7 @@ public class NewsDashboardService : INewsDashboardService
///
public async Task GetItemsAsync()
{
- const string BaseUrl = "https://umbraco-dashboard-news.euwest01.umbraco.io";
+ const string BaseUrl = "https://news-dashboard.umbraco.com";
const string Path = "/api/News";
var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
diff --git a/src/Umbraco.Core/Cache/IRepositoryCacheVersionAccessor.cs b/src/Umbraco.Core/Cache/IRepositoryCacheVersionAccessor.cs
index 61ae733fe1..af304dc19e 100644
--- a/src/Umbraco.Core/Cache/IRepositoryCacheVersionAccessor.cs
+++ b/src/Umbraco.Core/Cache/IRepositoryCacheVersionAccessor.cs
@@ -20,7 +20,14 @@ public interface IRepositoryCacheVersionAccessor
///
/// The cache version if found, or if the version doesn't exist or the request is a client-side request.
///
- public Task GetAsync(string cacheKey);
+ Task GetAsync(string cacheKey);
+
+ ///
+ /// Notifies of a version change on a given cache key.
+ ///
+ /// Key of the changed version.
+ void VersionChanged(string cacheKey)
+ { }
///
/// Notifies the accessor that caches have been synchronized.
@@ -29,5 +36,5 @@ public interface IRepositoryCacheVersionAccessor
/// This method is called after cache synchronization to temporarily bypass version checking,
/// preventing recursive sync attempts while repositories reload data from the database.
///
- public void CachesSynced();
+ void CachesSynced();
}
diff --git a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs
index 541e9f032e..09269f92e1 100644
--- a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs
+++ b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs
@@ -88,6 +88,7 @@ internal class RepositoryCacheVersionService : IRepositoryCacheVersionService
_logger.LogDebug("Setting cache for {EntityType} to version {Version}", typeof(TEntity).Name, newVersion);
await _repositoryCacheVersionRepository.SaveAsync(new RepositoryCacheVersion { Identifier = cacheKey, Version = newVersion.ToString() });
_cacheVersions[cacheKey] = newVersion;
+ _repositoryCacheVersionAccessor.VersionChanged(cacheKey);
scope.Complete();
}
diff --git a/src/Umbraco.Core/Configuration/Models/BackOfficeTokenCookieSettings.cs b/src/Umbraco.Core/Configuration/Models/BackOfficeTokenCookieSettings.cs
index 4019c42547..e7632d4270 100644
--- a/src/Umbraco.Core/Configuration/Models/BackOfficeTokenCookieSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/BackOfficeTokenCookieSettings.cs
@@ -9,17 +9,8 @@ namespace Umbraco.Cms.Core.Configuration.Models;
[Obsolete("This will be replaced with a different authentication scheme. Scheduled for removal in Umbraco 18.")]
public class BackOfficeTokenCookieSettings
{
- private const bool StaticEnabled = false;
-
private const string StaticSameSite = "Strict";
- ///
- /// Gets or sets a value indicating whether to enable access and refresh tokens in cookies.
- ///
- [DefaultValue(StaticEnabled)]
- [Obsolete("This is only configurable in Umbraco 16. Scheduled for removal in Umbraco 17.")]
- public bool Enabled { get; set; } = StaticEnabled;
-
///
/// Gets or sets a value indicating whether the cookie SameSite configuration.
///
diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
index a5adc0de2a..763dde8410 100644
--- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
+++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs
@@ -64,6 +64,10 @@ public static class UdiGetterExtensions
{
entityType = Constants.UdiEntityType.DataTypeContainer;
}
+ else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint)
+ {
+ entityType = Constants.UdiEntityType.DocumentBlueprintContainer;
+ }
else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType)
{
entityType = Constants.UdiEntityType.DocumentTypeContainer;
@@ -72,9 +76,9 @@ public static class UdiGetterExtensions
{
entityType = Constants.UdiEntityType.MediaTypeContainer;
}
- else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint)
+ else if (entity.ContainedObjectType == Constants.ObjectTypes.MemberType)
{
- entityType = Constants.UdiEntityType.DocumentBlueprintContainer;
+ entityType = Constants.UdiEntityType.MemberTypeContainer;
}
else
{
diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs
index b6770913a6..02cefdc795 100644
--- a/src/Umbraco.Core/Models/EntityContainer.cs
+++ b/src/Umbraco.Core/Models/EntityContainer.cs
@@ -10,10 +10,10 @@ public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity
private static readonly Dictionary ObjectTypeMap = new()
{
{ Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer },
+ { Constants.ObjectTypes.DocumentBlueprint, Constants.ObjectTypes.DocumentBlueprintContainer },
{ Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer },
{ Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer },
{ Constants.ObjectTypes.MemberType, Constants.ObjectTypes.MemberTypeContainer },
- { Constants.ObjectTypes.DocumentBlueprint, Constants.ObjectTypes.DocumentBlueprintContainer },
};
///
@@ -83,7 +83,7 @@ public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity
public static Guid GetContainedObjectType(Guid containerObjectType)
{
Guid contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key;
- if (contained == null)
+ if (contained == default)
{
throw new ArgumentException("Not a container object type.", nameof(containerObjectType));
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index 8bdddc0a55..3e0ecb7f94 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -139,6 +139,7 @@ public class UmbracoPlan : MigrationPlan
To("{263075BF-F18A-480D-92B4-4947D2EAB772}");
To("26179D88-58CE-4C92-B4A4-3CBA6E7188AC");
To("{8B2C830A-4FFB-4433-8337-8649B0BF52C8}");
+ To("{1C38D589-26BB-4A46-9ABE-E4A0DF548A87}");
// To 18.0.0
// TODO (V18): Enable on 18 branch
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_0_0/InvalidateBackofficeUserAccess.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_0_0/InvalidateBackofficeUserAccess.cs
new file mode 100644
index 0000000000..fdb111df1a
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_0_0/InvalidateBackofficeUserAccess.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_17_0_0;
+
+public class InvalidateBackofficeUserAccess : AsyncMigrationBase
+{
+ public InvalidateBackofficeUserAccess(IMigrationContext context)
+ : base(context)
+ {
+ }
+
+ protected override Task MigrateAsync()
+ {
+ InvalidateBackofficeUserAccess = true;
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/Umbraco.Web.Common/Cache/RepositoryCacheVersionAccessor.cs b/src/Umbraco.Web.Common/Cache/RepositoryCacheVersionAccessor.cs
index b96e2546e1..4485c80834 100644
--- a/src/Umbraco.Web.Common/Cache/RepositoryCacheVersionAccessor.cs
+++ b/src/Umbraco.Web.Common/Cache/RepositoryCacheVersionAccessor.cs
@@ -77,5 +77,17 @@ public class RepositoryCacheVersionAccessor : IRepositoryCacheVersionAccessor
return databaseVersion;
}
+
+ ///
+ public void VersionChanged(string cacheKey)
+ {
+ var removed = _requestCache.Remove(cacheKey);
+ if (removed is false)
+ {
+ _logger.LogDebug("Cache version for key {CacheKey} wasn't removed from request cache, possibly missing HTTP context", cacheKey);
+ }
+ }
+
+ ///
public void CachesSynced() => _requestCache.ClearOfType();
}
diff --git a/src/Umbraco.Web.UI.Client/.github/README.md b/src/Umbraco.Web.UI.Client/.github/README.md
index d677ad9e0d..fc94ba5835 100644
--- a/src/Umbraco.Web.UI.Client/.github/README.md
+++ b/src/Umbraco.Web.UI.Client/.github/README.md
@@ -41,7 +41,6 @@ Open this file in an editor: `/src/Umbraco.Web.UI/appsettings.Development.json`
"AuthorizeCallbackLogoutPathName": "/logout",
"AuthorizeCallbackErrorPathName": "/error",,
"BackOfficeTokenCookie": {
- "Enabled": true,
"SameSite": "None"
}
},
@@ -52,7 +51,7 @@ Open this file in an editor: `/src/Umbraco.Web.UI/appsettings.Development.json`
This will override the backoffice host URL, enabling the Client to run from a different origin.
> [!NOTE]
-> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `BackOfficeTokenCookie` settings are correct. Namely, that `SameSite` should be set to `None` when running the front-end server separately.
+> If you get stuck in a login loop, try clearing your browser cookies for localhost, and make sure that the `Umbraco:Cms:Security:BackOfficeTokenCookie:SameSite` setting is set to `None`.
#### 2. Start Umbraco
diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts
index 34328f20b7..e0b5b84d7a 100644
--- a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts
+++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts
@@ -53,35 +53,35 @@ const customItems: Array = [
unique: '1',
entityType: 'example',
name: 'Example 1',
- icon: 'icon-shape-triangle',
+ icon: 'icon-shape-triangle yellow',
isPickable: true,
},
{
unique: '2',
entityType: 'example',
name: 'Example 2',
- icon: 'icon-shape-triangle',
+ icon: 'icon-shape-triangle yellow',
isPickable: true,
},
{
unique: '3',
entityType: 'example',
name: 'Example 3',
- icon: 'icon-shape-triangle',
+ icon: 'icon-shape-triangle yellow',
isPickable: true,
},
{
unique: '4',
entityType: 'example',
name: 'Example 4',
- icon: 'icon-shape-triangle',
+ icon: 'icon-shape-triangle yellow',
isPickable: false,
},
{
unique: '5',
entityType: 'example',
name: 'Example 5',
- icon: 'icon-shape-triangle',
+ icon: 'icon-shape-triangle yellow',
isPickable: true,
},
];
diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts
index 47cfbdc043..3ba6c2d2ef 100644
--- a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts
+++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts
@@ -19,7 +19,9 @@ import type {
UmbTreeChildrenOfRequestArgs,
UmbTreeRootItemsRequestArgs,
} from '@umbraco-cms/backoffice/tree';
-import { getConfigValue, type UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils';
+import { getConfigValue } from '@umbraco-cms/backoffice/utils';
+
+type ExampleDocumentPickerConfigCollectionModel = Array<{ alias: 'filter'; value: string }>;
export class ExampleDocumentPickerPropertyEditorDataSource
extends UmbControllerBase
@@ -30,17 +32,17 @@ export class ExampleDocumentPickerPropertyEditorDataSource
#tree = new UmbDocumentTreeRepository(this);
#item = new UmbDocumentItemRepository(this);
#search = new UmbDocumentSearchRepository(this);
- #config: UmbConfigCollectionModel = [];
+ #config: ExampleDocumentPickerConfigCollectionModel = [];
treePickableFilter: (treeItem: UmbDocumentTreeItemModel) => boolean = (treeItem) => !!treeItem.unique;
- setConfig(config: UmbConfigCollectionModel) {
+ setConfig(config: ExampleDocumentPickerConfigCollectionModel) {
// TODO: add examples for all config options
this.#config = config;
this.#applyPickableFilterFromConfig();
}
- getConfig(): UmbConfigCollectionModel {
+ getConfig(): ExampleDocumentPickerConfigCollectionModel {
return this.#config;
}
@@ -72,7 +74,7 @@ export class ExampleDocumentPickerPropertyEditorDataSource
}
#getAllowedDocumentTypesConfig() {
- const filterString = getConfigValue(this.#config, 'filter');
+ const filterString = getConfigValue(this.#config, 'filter');
const filterArray = filterString ? filterString.split(',') : [];
const allowedContentTypes: UmbDocumentSearchRequestArgs['allowedContentTypes'] = filterArray.map(
(unique: string) => ({
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/menu-item/default/default-collection-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/menu-item/default/default-collection-menu-item.element.ts
index 3dfed63ced..fc30554826 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/menu-item/default/default-collection-menu-item.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/menu-item/default/default-collection-menu-item.element.ts
@@ -61,9 +61,7 @@ export class UmbDefaultCollectionMenuItemElement extends UmbLitElement {
?selected=${this._isSelected}
@selected=${() => this.#api?.select()}
@deselected=${() => this.#api?.deselect()}>
- ${item.icon
- ? html``
- : html``}
+
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
index 9c90d216c0..31c4649aa5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
@@ -82,6 +82,12 @@ export class UmbPickerInputContext<
getSelection() {
return this.#itemManager.getUniques();
}
+ getSelectedItems() {
+ return this.#itemManager.getItems();
+ }
+ getSelectedItemByUnique(unique: string) {
+ return this.#itemManager.getItems().find((item) => item.unique === unique);
+ }
setSelection(selection: Array) {
// Note: Currently we do not support picking root item. So we filter out null values:
@@ -111,21 +117,12 @@ export class UmbPickerInputContext<
this.getHostElement().dispatchEvent(new UmbChangeEvent());
}
- /**
- * Get the display name for an item to show in the remove confirmation dialog.
- * Subclasses can override this to provide custom formatting for missing items.
- * @param item - The item to get the display name for, or undefined if not found
- * @param unique - The unique identifier of the item
- * @returns The display name to show in the dialog
- */
- protected getItemDisplayName(item: PickedItemType | undefined, unique: string): string {
- return item?.name ?? unique;
+ protected async _requestItemName(unique: string) {
+ return this.getSelectedItemByUnique(unique)?.name ?? '#general_notFound';
}
async requestRemoveItem(unique: string) {
- const item = this.#itemManager.getItems().find((item) => item.unique === unique);
- const name = this.getItemDisplayName(item, unique);
-
+ const name = await this._requestItemName(unique);
await umbConfirmModal(this, {
color: 'danger',
headline: `#actions_remove?`,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/get-config-value.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/get-config-value.test.ts
new file mode 100644
index 0000000000..9c510c09b3
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/get-config-value.test.ts
@@ -0,0 +1,38 @@
+import { expect } from '@open-wc/testing';
+import { getConfigValue } from './index.js';
+
+describe('getConfigValue', () => {
+ it('should return the value for a matching alias', () => {
+ const config = [
+ { alias: 'foo', value: 123 },
+ { alias: 'bar', value: 'hello' },
+ ];
+ const result = getConfigValue(config, 'foo');
+ expect(result).to.equal(123);
+ });
+
+ it('should return undefined if alias is not found', () => {
+ const config = [
+ { alias: 'foo', value: 123 },
+ { alias: 'bar', value: 'hello' },
+ ];
+ const result = getConfigValue(config, 'baz');
+ expect(result).to.be.undefined;
+ });
+
+ it('should return undefined if config is undefined', () => {
+ const result = getConfigValue(undefined, 'foo');
+ expect(result).to.be.undefined;
+ });
+
+ it('should work with different value types', () => {
+ const config = [
+ { alias: 'num', value: 42 },
+ { alias: 'str', value: 'test' },
+ { alias: 'obj', value: { a: 1 } },
+ ];
+ expect(getConfigValue(config, 'num')).to.equal(42);
+ expect(getConfigValue(config, 'str')).to.equal('test');
+ expect(getConfigValue(config, 'obj')).to.deep.equal({ a: 1 });
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts
index b4f03f1155..a2de3f4899 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts
@@ -1,12 +1,14 @@
-import type { UmbConfigCollectionModel } from './types.js';
+import type { UmbConfigCollectionEntryModel } from './types.js';
/**
* Get a value from a config collection by its alias.
- * @param {UmbConfigCollectionModel | undefined} config - The config collection to get the value from.
- * @param {string} alias - The alias of the value to get.
- * @returns {T | undefined} The value with the specified alias, or undefined if not found or if the config is undefined.
+ * @param config - The config collection to get the value from.
+ * @param alias - The alias of the config entry to get the value for.
+ * @returns The value of the config entry with the specified alias, or undefined if not found.
*/
-export function getConfigValue(config: UmbConfigCollectionModel | undefined, alias: string): T | undefined {
- const entry = config?.find((entry) => entry.alias === alias);
- return entry?.value as T | undefined;
+export function getConfigValue(
+ config: T[] | undefined,
+ alias: K,
+) {
+ return config?.find((entry) => entry.alias === alias)?.value as Extract['value'] | undefined;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts
index 1d815ed740..43ac95bd8b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts
@@ -7,6 +7,7 @@ import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbDocumentTypeEntityType } from '@umbraco-cms/backoffice/document-type';
import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant';
+import { UmbDocumentItemDataResolver } from '../../item/index.js';
interface UmbDocumentPickerInputContextOpenArgs {
allowedContentTypes?: Array<{ unique: string; entityType: UmbDocumentTypeEntityType }>;
@@ -56,6 +57,15 @@ export class UmbDocumentPickerInputContext extends UmbPickerInputContext<
await super.openPicker(combinedPickerData);
}
+ protected override async _requestItemName(unique: string): Promise {
+ const item = this.getSelectedItemByUnique(unique);
+ const resolver = new UmbDocumentItemDataResolver(this);
+ resolver.setData(item);
+ const name = await resolver.getName();
+ this.removeUmbController(resolver);
+ return name ?? '#general_notFound';
+ }
+
#pickableFilter = (
item: UmbDocumentItemModel,
allowedContentTypes?: Array<{ unique: string; entityType: UmbDocumentTypeEntityType }>,
diff --git a/templates/UmbracoExtension/Umbraco.Extension.csproj b/templates/UmbracoExtension/Umbraco.Extension.csproj
index 4bc359eaa8..1e515aebbf 100644
--- a/templates/UmbracoExtension/Umbraco.Extension.csproj
+++ b/templates/UmbracoExtension/Umbraco.Extension.csproj
@@ -26,8 +26,6 @@
-
-
@@ -39,19 +37,4 @@
-
-
-
-
-
-
-
-
-
-
- <_ClientAssetsBuildOutput Include="wwwroot\App_Plugins\**" />
-
-
-
-
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
index 509985c5b1..55ac1a3b7e 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
+++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
@@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.42",
- "@umbraco/playwright-testhelpers": "^17.0.6",
+ "@umbraco/playwright-testhelpers": "^17.0.8",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -67,9 +67,9 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
- "version": "17.0.6",
- "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.6.tgz",
- "integrity": "sha512-M0e5HJCqSTDxORFhebaNNGzBB4v6+77MerK6ctG1f+bU3JHfmbGZr4A4HDkD9eAeU7WGu5q7xoASdI0J1wqb1w==",
+ "version": "17.0.8",
+ "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.8.tgz",
+ "integrity": "sha512-LUVBdsweiS0WpE1F9YTQejmSxdtgEvbcmLHX57e2S2AbNkdVuR8cJ0rYd9TqSKtNU8ckwnk6YRtVikegU0D64w==",
"license": "MIT",
"dependencies": {
"@umbraco/json-models-builders": "2.0.42",
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json
index 7528914b7d..9cbb0c6fc7 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/package.json
+++ b/tests/Umbraco.Tests.AcceptanceTest/package.json
@@ -23,7 +23,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.42",
- "@umbraco/playwright-testhelpers": "^17.0.6",
+ "@umbraco/playwright-testhelpers": "^17.0.8",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts
index 0ede3be26b..c8024ba19c 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts
@@ -194,7 +194,8 @@ test('can add multiple content start nodes for a user', async ({umbracoApi, umbr
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
-test('can remove a content start node from a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+// TODO: Look into flaky test
+test.fixme('can remove a content start node from a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName);
const userId = await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]);
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs
index 476fefec66..0dd3568175 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs
@@ -16,9 +16,10 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions;
public class UdiGetterExtensionsTests
{
[TestCase(Constants.ObjectTypes.Strings.DataType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type-container/6ad82c70685c4e049b36d81bd779d16f")]
+ [TestCase(Constants.ObjectTypes.Strings.DocumentBlueprint, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-blueprint-container/6ad82c70685c4e049b36d81bd779d16f")]
[TestCase(Constants.ObjectTypes.Strings.DocumentType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type-container/6ad82c70685c4e049b36d81bd779d16f")]
[TestCase(Constants.ObjectTypes.Strings.MediaType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type-container/6ad82c70685c4e049b36d81bd779d16f")]
- [TestCase(Constants.ObjectTypes.Strings.DocumentBlueprint, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-blueprint-container/6ad82c70685c4e049b36d81bd779d16f")]
+ [TestCase(Constants.ObjectTypes.Strings.MemberType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-type-container/6ad82c70685c4e049b36d81bd779d16f")]
public void GetUdiForEntityContainer(Guid containedObjectType, Guid key, string expected)
{
EntityContainer entity = new EntityContainer(containedObjectType)