Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bf74a82
init current user workspace
LanThuyNguyen Mar 24, 2026
44cef0f
Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS into v1…
LanThuyNguyen Mar 24, 2026
ed961ee
Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS into v1…
LanThuyNguyen Mar 25, 2026
e690615
adding current user workspace and their apis
LanThuyNguyen Mar 26, 2026
d0d9541
Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS into v1…
LanThuyNguyen Mar 26, 2026
b14f852
add new controllers
LanThuyNguyen Mar 26, 2026
4bc18b5
Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS into v1…
LanThuyNguyen Mar 30, 2026
225816f
add default implementation
LanThuyNguyen Mar 30, 2026
ed8dae1
Update src/Umbraco.Core/Services/UserService.cs
NguyenThuyLan Mar 31, 2026
5978c6d
Update src/Umbraco.Cms.Api.Management/ViewModels/User/UpdateCurrentUs…
NguyenThuyLan Mar 31, 2026
e85054c
Update tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServ…
NguyenThuyLan Mar 31, 2026
1524945
Update src/Umbraco.Core/Models/CurrentUserUpdateModel.cs
NguyenThuyLan Mar 31, 2026
2dbf9cf
update openApi.json, remove userKey from model, remove redundant auth…
LanThuyNguyen Mar 31, 2026
8f31319
Merge branch 'main' of https://github.com/umbraco/Umbraco-CMS into v1…
LanThuyNguyen Mar 31, 2026
ae454c0
update localize
LanThuyNguyen Mar 31, 2026
d809d00
allow blob: URLs in img-src CSP for avatar
LanThuyNguyen Mar 31, 2026
d0634a3
save image change later
LanThuyNguyen Mar 31, 2026
c650395
Remove references to "current" user from service layer.
AndyButland Mar 31, 2026
98d58a7
Add missing controller from last commit.
AndyButland Mar 31, 2026
434e4ea
resolve conflicts
LanThuyNguyen Apr 14, 2026
a8a8f66
Merge branch 'v17/feature/current-user-workspace' of https://github.c…
LanThuyNguyen Apr 14, 2026
9f6f224
resolve conflicts 2
LanThuyNguyen Apr 14, 2026
7a9376a
Renamed/relocated "current-user-workspace" to "profile/edit"
leekelleher Apr 22, 2026
7332af4
Removed the "Section User No Permission" condition
leekelleher Apr 22, 2026
1bd75d4
UI tweaks + streamlining
leekelleher Apr 22, 2026
4e77066
Profile edit: surface save errors and avoid blob URL leak
leekelleher Apr 22, 2026
c45d665
Merge branch 'main' into v17/feature/current-user-workspace
leekelleher Apr 22, 2026
b754b4f
Refactored to use `asPromise()`
leekelleher Apr 22, 2026
a241d45
Current User: Adapt edit-profile modal into a workspace extension
leekelleher Apr 22, 2026
7a7409c
Current User workspace: Address review findings
leekelleher Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;

namespace Umbraco.Cms.Api.Management.Controllers.User.Current;

/// <summary>
/// Controller responsible for handling requests to clear the avatar of the currently authenticated user.
/// </summary>
[ApiVersion("1.0")]
public class ClearAvatarCurrentUserController : CurrentUserControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IUserService _userService;

/// <summary>
/// Initializes a new instance of the <see cref="ClearAvatarCurrentUserController"/> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Provides access to back office security features for the current user.</param>
/// <param name="userService">Service for managing user-related operations.</param>
public ClearAvatarCurrentUserController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IUserService userService)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_userService = userService;
}

/// <summary>
/// Removes the avatar image for the currently authenticated user.
/// </summary>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>An <see cref="IActionResult"/> indicating the result of the operation.</returns>
[MapToApiVersion("1.0")]
[HttpDelete("avatar")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Clears the current user's avatar.")]
[EndpointDescription("Removes the avatar image for the currently authenticated user.")]
public async Task<IActionResult> ClearAvatar(CancellationToken cancellationToken)
{
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);

UserOperationStatus result = await _userService.ClearAvatarAsync(userKey);

return result is UserOperationStatus.Success
? Ok()
: UserOperationStatusResult(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
public class SetAvatarCurrentUserController : CurrentUserControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly IUserService _userService;

/// <summary>
Expand All @@ -29,13 +28,15 @@ public class SetAvatarCurrentUserController : CurrentUserControllerBase
/// <param name="backOfficeSecurityAccessor">Provides access to back office security features for the current user.</param>
/// <param name="authorizationService">Service used to authorize user actions.</param>
/// <param name="userService">Service for managing user-related operations.</param>
// TODO (V18): Remove the IAuthorizationService parameter from the constructor and the class, as it is not used in the current implementation.
public SetAvatarCurrentUserController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
#pragma warning disable IDE0060 // Remove unused parameter
IAuthorizationService authorizationService,
#pragma warning restore IDE0060 // Remove unused parameter
IUserService userService)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_authorizationService = authorizationService;
_userService = userService;
}

Expand All @@ -55,16 +56,6 @@ public async Task<IActionResult> SetAvatar(CancellationToken cancellationToken,
{
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);

AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
UserPermissionResource.WithKeys(userKey),
AuthorizationPolicies.UserPermissionByResource);

if (!authorizationResult.Succeeded)
{
return Forbidden();
}

UserOperationStatus result = await _userService.SetAvatarAsync(userKey, model.File.Id);

return result is UserOperationStatus.Success
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;

namespace Umbraco.Cms.Api.Management.Controllers.User.Current;

/// <summary>
/// Controller responsible for update information about the currently authenticated user.
/// </summary>
[ApiVersion("1.0")]
public class UpdateCurrentUserProfileController : CurrentUserControllerBase
{
private readonly IUserService _userService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IUserPresentationFactory _userPresentationFactory;

/// <summary>
/// Initializes a new instance of the <see cref="UpdateCurrentUserProfileController"/> class, which manages user update operations in the Umbraco backoffice API.
/// </summary>
/// <param name="userService">Service for managing user data and operations.</param>
/// <param name="userPresentationFactory">Factory for creating user presentation models.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public UpdateCurrentUserProfileController(
IUserService userService,
IUserPresentationFactory userPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_userService = userService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_userPresentationFactory = userPresentationFactory;
}

/// <summary>
/// Updates the current user with new details provided in the request model.
/// </summary>
/// <param name="model">The request model containing updated current user information.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the update operation.</returns>
[HttpPut("profile")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Updates current user profile.")]
[EndpointDescription("Updates current user profile with the details from the request model.")]
public async Task<IActionResult> UpdateCurrentUser(UpdateCurrentUserRequestModel model)
{
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);

UserUpdateProfileModel updateModel = await _userPresentationFactory.CreateUpdateProfileModelAsync(model);
Attempt<IUser?, UserOperationStatus> result = await _userService.UpdateProfileAsync(userKey, updateModel);

return result.Success
? Ok()
: UserOperationStatusResult(result.Status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public interface IUserPresentationFactory
/// </summary>
Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel);

/// <summary>
/// Creates an update model for a current user based on the provided request model.
/// </summary>
// TODO V19: Remove default implementation
Task<UserUpdateProfileModel> CreateUpdateProfileModelAsync(UpdateCurrentUserRequestModel updateModel) => throw new NotImplementedException();

/// <summary>
/// Creates a response model for the current user based on the provided user.
/// </summary>
Expand All @@ -56,10 +62,10 @@ public interface IUserPresentationFactory
/// </summary>
UserItemResponseModel CreateItemResponseModel(IUser user);

/// <summary>
/// Asynchronously creates a response model containing the calculated start nodes for the specified user.
/// </summary>
/// <param name="user">The user for whom to calculate start nodes.</param>
/// <returns>A task representing the asynchronous operation. The task result contains the calculated user start nodes response model.</returns>
/// <summary>
/// Asynchronously creates a response model containing the calculated start nodes for the specified user.
/// </summary>
/// <param name="user">The user for whom to calculate start nodes.</param>
/// <returns>A task representing the asynchronous operation. The task result contains the calculated user start nodes response model.</returns>
Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,15 @@ private ISet<ReferenceByIdModel> GetKeysFromIds(IEnumerable<int>? ids, UmbracoOb

private static bool HasRootAccess(IEnumerable<int>? startNodeIds)
=> startNodeIds?.Contains(Constants.System.Root) is true;

/// <inheritdoc/>
public Task<UserUpdateProfileModel> CreateUpdateProfileModelAsync(UpdateCurrentUserRequestModel updateModel)
{
var model = new UserUpdateProfileModel
{
LanguageIsoCode = updateModel.LanguageIsoCode
};

return Task.FromResult(model);
}
}
Loading
Loading