V10: fix build warnings in Web.BackOffice (#12479)

* Run code cleanup

* Start manual run

* Finish dotnet format + manual cleanup

* Fix up after merge

* Fix substrings changed to [..]

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
Nikolaj Geisle
2022-06-20 08:37:17 +02:00
committed by GitHub
parent 7688c61621
commit e762fa91bc
234 changed files with 28037 additions and 27527 deletions

View File

@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -29,298 +26,326 @@ using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
namespace Umbraco.Cms.Web.BackOffice.Controllers;
/// <summary>
/// Controller to back the User.Resource service, used for fetching user data when already authenticated. user.service
/// is currently used for handling authentication
/// </summary>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class CurrentUserController : UmbracoAuthorizedJsonController
{
/// <summary>
/// Controller to back the User.Resource service, used for fetching user data when already authenticated. user.service is currently used for handling authentication
/// </summary>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class CurrentUserController : UmbracoAuthorizedJsonController
private readonly AppCaches _appCaches;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly ContentSettings _contentSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly ILocalizedTextService _localizedTextService;
private readonly MediaFileManager _mediaFileManager;
private readonly IPasswordChanger<BackOfficeIdentityUser> _passwordChanger;
private readonly IShortStringHelper _shortStringHelper;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IUserDataService _userDataService;
private readonly IUserService _userService;
[ActivatorUtilitiesConstructor]
public CurrentUserController(
MediaFileManager mediaFileManager,
IOptionsSnapshot<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
IImageUrlGenerator imageUrlGenerator,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IUserService userService,
IUmbracoMapper umbracoMapper,
IBackOfficeUserManager backOfficeUserManager,
ILocalizedTextService localizedTextService,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
IPasswordChanger<BackOfficeIdentityUser> passwordChanger,
IUserDataService userDataService)
{
private readonly MediaFileManager _mediaFileManager;
private readonly ContentSettings _contentSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IUserService _userService;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly ILocalizedTextService _localizedTextService;
private readonly AppCaches _appCaches;
private readonly IShortStringHelper _shortStringHelper;
private readonly IPasswordChanger<BackOfficeIdentityUser> _passwordChanger;
private readonly IUserDataService _userDataService;
_mediaFileManager = mediaFileManager;
_contentSettings = contentSettings.Value;
_hostingEnvironment = hostingEnvironment;
_imageUrlGenerator = imageUrlGenerator;
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_userService = userService;
_umbracoMapper = umbracoMapper;
_backOfficeUserManager = backOfficeUserManager;
_localizedTextService = localizedTextService;
_appCaches = appCaches;
_shortStringHelper = shortStringHelper;
_passwordChanger = passwordChanger;
_userDataService = userDataService;
}
[ActivatorUtilitiesConstructor]
public CurrentUserController(
MediaFileManager mediaFileManager,
IOptionsSnapshot<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
IImageUrlGenerator imageUrlGenerator,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IUserService userService,
IUmbracoMapper umbracoMapper,
IBackOfficeUserManager backOfficeUserManager,
ILocalizedTextService localizedTextService,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
IPasswordChanger<BackOfficeIdentityUser> passwordChanger,
IUserDataService userDataService)
[Obsolete("This constructor is obsolete and will be removed in v11, use constructor with all values")]
public CurrentUserController(
MediaFileManager mediaFileManager,
IOptions<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
IImageUrlGenerator imageUrlGenerator,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IUserService userService,
IUmbracoMapper umbracoMapper,
IBackOfficeUserManager backOfficeUserManager,
ILoggerFactory loggerFactory,
ILocalizedTextService localizedTextService,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
IPasswordChanger<BackOfficeIdentityUser> passwordChanger) : this(
mediaFileManager,
StaticServiceProvider.Instance.GetRequiredService<IOptionsSnapshot<ContentSettings>>(),
hostingEnvironment,
imageUrlGenerator,
backofficeSecurityAccessor,
userService,
umbracoMapper,
backOfficeUserManager,
localizedTextService,
appCaches,
shortStringHelper,
passwordChanger,
StaticServiceProvider.Instance.GetRequiredService<IUserDataService>())
{
}
/// <summary>
/// Returns permissions for all nodes passed in for the current user
/// </summary>
/// <param name="nodeIds"></param>
/// <returns></returns>
[HttpPost]
public Dictionary<int, string[]> GetPermissions(int[] nodeIds)
{
EntityPermissionCollection permissions = _userService
.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, nodeIds);
var permissionsDictionary = new Dictionary<int, string[]>();
foreach (var nodeId in nodeIds)
{
_mediaFileManager = mediaFileManager;
_contentSettings = contentSettings.Value;
_hostingEnvironment = hostingEnvironment;
_imageUrlGenerator = imageUrlGenerator;
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_userService = userService;
_umbracoMapper = umbracoMapper;
_backOfficeUserManager = backOfficeUserManager;
_localizedTextService = localizedTextService;
_appCaches = appCaches;
_shortStringHelper = shortStringHelper;
_passwordChanger = passwordChanger;
_userDataService = userDataService;
var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray();
permissionsDictionary.Add(nodeId, aggregatePerms);
}
[Obsolete("This constructor is obsolete and will be removed in v11, use constructor with all values")]
public CurrentUserController(
MediaFileManager mediaFileManager,
IOptions<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
IImageUrlGenerator imageUrlGenerator,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IUserService userService,
IUmbracoMapper umbracoMapper,
IBackOfficeUserManager backOfficeUserManager,
ILoggerFactory loggerFactory,
ILocalizedTextService localizedTextService,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
IPasswordChanger<BackOfficeIdentityUser> passwordChanger) : this(
mediaFileManager,
StaticServiceProvider.Instance.GetRequiredService<IOptionsSnapshot<ContentSettings>>(),
hostingEnvironment,
imageUrlGenerator,
backofficeSecurityAccessor,
userService,
umbracoMapper,
backOfficeUserManager,
localizedTextService,
appCaches,
shortStringHelper,
passwordChanger,
StaticServiceProvider.Instance.GetRequiredService<IUserDataService>())
{
return permissionsDictionary;
}
/// <summary>
/// Checks a nodes permission for the current user
/// </summary>
/// <param name="permissionToCheck"></param>
/// <param name="nodeId"></param>
/// <returns></returns>
[HttpGet]
public bool HasPermission(string permissionToCheck, int nodeId)
{
IEnumerable<string> p = _userService
.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, nodeId).GetAllPermissions();
if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
{
return true;
}
return false;
}
/// <summary>
/// Returns permissions for all nodes passed in for the current user
/// </summary>
/// <param name="nodeIds"></param>
/// <returns></returns>
[HttpPost]
public Dictionary<int, string[]> GetPermissions(int[] nodeIds)
/// <summary>
/// Saves a tour status for the current user
/// </summary>
/// <param name="status"></param>
/// <returns></returns>
public IEnumerable<UserTourStatus> PostSetUserTour(UserTourStatus? status)
{
if (status == null)
{
var permissions = _userService
.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, nodeIds);
var permissionsDictionary = new Dictionary<int, string[]>();
foreach (var nodeId in nodeIds)
{
var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray();
permissionsDictionary.Add(nodeId, aggregatePerms);
}
return permissionsDictionary;
throw new ArgumentNullException(nameof(status));
}
/// <summary>
/// Checks a nodes permission for the current user
/// </summary>
/// <param name="permissionToCheck"></param>
/// <param name="nodeId"></param>
/// <returns></returns>
[HttpGet]
public bool HasPermission(string permissionToCheck, int nodeId)
List<UserTourStatus>? userTours = null;
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
{
var p = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, nodeId).GetAllPermissions();
if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
{
return true;
}
return false;
}
/// <summary>
/// Saves a tour status for the current user
/// </summary>
/// <param name="status"></param>
/// <returns></returns>
public IEnumerable<UserTourStatus> PostSetUserTour(UserTourStatus? status)
{
if (status == null) throw new ArgumentNullException(nameof(status));
List<UserTourStatus>? userTours = null;
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
{
userTours = new List<UserTourStatus> { status };
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null)
{
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
}
return userTours;
}
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData is not null)
{
userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData)?.ToList();
var found = userTours?.FirstOrDefault(x => x.Alias == status.Alias);
if (found != null)
{
//remove it and we'll replace it next
userTours?.Remove(found);
}
userTours?.Add(status);
}
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
/// <summary>
/// Returns the user's tours
/// </summary>
/// <returns></returns>
public IEnumerable<UserTourStatus>? GetUserTours()
{
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
return Enumerable.Empty<UserTourStatus>();
var userTours = JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData!);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
public IEnumerable<UserData> GetUserData() => _userDataService.GetUserData();
/// <summary>
/// When a user is invited and they click on the invitation link, they will be partially logged in
/// where they can set their username/password
/// </summary>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// This only works when the user is logged in (partially)
/// </remarks>
[AllowAnonymous]
public async Task<ActionResult<UserDetail?>> PostSetInvitedUserPassword([FromBody]string newPassword)
{
var user = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString());
if (user == null) throw new InvalidOperationException("Could not find user");
var result = await _backOfficeUserManager.AddPasswordAsync(user, newPassword);
if (result.Succeeded == false)
{
//it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form
// so that is why it is being used here.
ModelState.AddModelError("value", result.Errors.ToErrorMessage());
return ValidationProblem(ModelState);
}
userTours = new List<UserTourStatus> { status };
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null)
{
//They've successfully set their password, we can now update their user account to be approved
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsApproved = true;
//They've successfully set their password, and will now get fully logged into the back office, so the lastlogindate is set so the backoffice shows they have logged in
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.LastLoginDate = DateTime.UtcNow;
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData =
JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
}
//now we can return their full object since they are now really logged into the back office
var userDisplay = _umbracoMapper.Map<UserDetail>(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser);
if (userDisplay is not null)
{
userDisplay.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds();
}
return userDisplay;
return userTours;
}
[AppendUserModifiedHeader]
public IActionResult PostSetAvatar(IList<IFormFile> file)
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData is not null)
{
var userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId();
var result = userId?.ResultOr(0);
//borrow the logic from the user controller
return UsersController.PostSetAvatarInternal(file, _userService, _appCaches.RuntimeCache, _mediaFileManager, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0);
userTours = JsonConvert
.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity
.CurrentUser.TourData)?.ToList();
UserTourStatus? found = userTours?.FirstOrDefault(x => x.Alias == status.Alias);
if (found != null)
{
//remove it and we'll replace it next
userTours?.Remove(found);
}
userTours?.Add(status);
}
/// <summary>
/// Changes the users password
/// </summary>
/// <param name="changingPasswordModel">The changing password model</param>
/// <returns>
/// If the password is being reset it will return the newly reset password, otherwise will return an empty value
/// </returns>
public async Task<ActionResult<ModelWithNotifications<string?>>?> PostChangePassword(ChangingPasswordModel changingPasswordModel)
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
/// <summary>
/// Returns the user's tours
/// </summary>
/// <returns></returns>
public IEnumerable<UserTourStatus>? GetUserTours()
{
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
{
IUser? currentUser = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
if (currentUser is null)
{
return null;
}
return Enumerable.Empty<UserTourStatus>();
}
changingPasswordModel.Id = currentUser.Id;
IEnumerable<UserTourStatus>? userTours =
JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity
.CurrentUser.TourData!);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
// all current users have access to reset/manually change their password
public IEnumerable<UserData> GetUserData() => _userDataService.GetUserData();
Attempt<PasswordChangedModel?> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _backOfficeUserManager);
/// <summary>
/// When a user is invited and they click on the invitation link, they will be partially logged in
/// where they can set their username/password
/// </summary>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// This only works when the user is logged in (partially)
/// </remarks>
[AllowAnonymous]
public async Task<ActionResult<UserDetail?>> PostSetInvitedUserPassword([FromBody] string newPassword)
{
BackOfficeIdentityUser? user = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor
.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString());
if (user == null)
{
throw new InvalidOperationException("Could not find user");
}
if (passwordChangeResult.Success)
{
// even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
var result = new ModelWithNotifications<string?>(passwordChangeResult.Result?.ResetPassword);
result.AddSuccessNotification(_localizedTextService.Localize("user","password"), _localizedTextService.Localize("user","passwordChanged"));
return result;
}
IdentityResult result = await _backOfficeUserManager.AddPasswordAsync(user, newPassword);
if (passwordChangeResult.Result?.ChangeError?.MemberNames is not null)
{
foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames)
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage ?? string.Empty);
}
}
if (result.Succeeded == false)
{
//it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form
// so that is why it is being used here.
ModelState.AddModelError("value", result.Errors.ToErrorMessage());
return ValidationProblem(ModelState);
}
// TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[ValidateAngularAntiForgeryToken]
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null)
{
var identityUser = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString(CultureInfo.InvariantCulture));
//They've successfully set their password, we can now update their user account to be approved
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsApproved = true;
//They've successfully set their password, and will now get fully logged into the back office, so the lastlogindate is set so the backoffice shows they have logged in
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.LastLoginDate = DateTime.UtcNow;
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
}
// deduplicate in case there are duplicates (there shouldn't be now since we have a unique constraint on the external logins
// but there didn't used to be)
var result = new Dictionary<string, string>();
foreach (var l in identityUser.Logins)
{
result[l.LoginProvider] = l.ProviderKey;
}
//now we can return their full object since they are now really logged into the back office
UserDetail? userDisplay =
_umbracoMapper.Map<UserDetail>(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser);
if (userDisplay is not null)
{
userDisplay.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds();
}
return userDisplay;
}
[AppendUserModifiedHeader]
public IActionResult PostSetAvatar(IList<IFormFile> file)
{
Attempt<int>? userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId();
var result = userId?.ResultOr(0);
//borrow the logic from the user controller
return UsersController.PostSetAvatarInternal(
file,
_userService,
_appCaches.RuntimeCache,
_mediaFileManager,
_shortStringHelper,
_contentSettings,
_hostingEnvironment,
_imageUrlGenerator,
_backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0);
}
/// <summary>
/// Changes the users password
/// </summary>
/// <param name="changingPasswordModel">The changing password model</param>
/// <returns>
/// If the password is being reset it will return the newly reset password, otherwise will return an empty value
/// </returns>
public async Task<ActionResult<ModelWithNotifications<string?>>?> PostChangePassword(
ChangingPasswordModel changingPasswordModel)
{
IUser? currentUser = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
if (currentUser is null)
{
return null;
}
changingPasswordModel.Id = currentUser.Id;
// all current users have access to reset/manually change their password
Attempt<PasswordChangedModel?> passwordChangeResult =
await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _backOfficeUserManager);
if (passwordChangeResult.Success)
{
// even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
var result = new ModelWithNotifications<string?>(passwordChangeResult.Result?.ResetPassword);
result.AddSuccessNotification(_localizedTextService.Localize("user", "password"), _localizedTextService.Localize("user", "passwordChanged"));
return result;
}
if (passwordChangeResult.Result?.ChangeError?.MemberNames is not null)
{
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage ?? string.Empty);
}
}
return ValidationProblem(ModelState);
}
// TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[ValidateAngularAntiForgeryToken]
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
{
BackOfficeIdentityUser identityUser = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor
.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString(CultureInfo.InvariantCulture));
// deduplicate in case there are duplicates (there shouldn't be now since we have a unique constraint on the external logins
// but there didn't used to be)
var result = new Dictionary<string, string>();
foreach (IIdentityUserLogin l in identityUser.Logins)
{
result[l.LoginProvider] = l.ProviderKey;
}
return result;
}
}