Files
Umbraco-CMS/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs

331 lines
13 KiB
C#
Raw Normal View History

using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
2021-10-07 09:51:04 +02:00
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
2018-03-27 10:04:07 +02:00
using Newtonsoft.Json;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.Filters;
2021-02-10 11:42:04 +01:00
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
2021-10-07 09:51:04 +02:00
using Umbraco.Cms.Web.Common.DependencyInjection;
2021-02-26 14:21:23 +00:00
using Umbraco.Cms.Web.Common.Security;
2020-05-18 13:00:32 +01:00
using Umbraco.Extensions;
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
{
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)
{
_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;
}
2019-12-18 13:05:34 +01:00
/// <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)
{
2022-08-18 14:38:28 +02:00
EntityPermissionCollection permissions = _userService
.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, nodeIds);
var permissionsDictionary = new Dictionary<int, string[]>();
foreach (var nodeId in nodeIds)
2019-12-18 13:05:34 +01:00
{
var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray();
permissionsDictionary.Add(nodeId, aggregatePerms);
}
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>
/// Saves a tour status for the current user
/// </summary>
/// <param name="status"></param>
/// <returns></returns>
public IEnumerable<UserTourStatus> PostSetUserTour(UserTourStatus? status)
{
if (status == null)
2018-03-27 10:04:07 +02:00
{
throw new ArgumentNullException(nameof(status));
}
2018-03-27 10:04:07 +02:00
List<UserTourStatus>? userTours = null;
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
{
userTours = new List<UserTourStatus> { status };
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null)
2018-03-27 10:04:07 +02:00
{
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData =
JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
2018-03-27 10:04:07 +02:00
}
return userTours;
}
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData is not null)
{
userTours = JsonConvert
.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity
.CurrentUser.TourData)?.ToList();
UserTourStatus? found = userTours?.FirstOrDefault(x => x.Alias == status.Alias);
if (found != null)
2018-03-27 10:04:07 +02:00
{
//remove it and we'll replace it next
userTours?.Remove(found);
2018-03-27 10:04:07 +02:00
}
2022-04-01 11:09:51 +02:00
userTours?.Add(status);
2018-03-27 10:04:07 +02:00
}
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.TourData = JsonConvert.SerializeObject(userTours);
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
2018-03-27 10:04:07 +02:00
/// <summary>
/// Returns the user's tours
/// </summary>
/// <returns></returns>
public IEnumerable<UserTourStatus>? GetUserTours()
{
if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.TourData.IsNullOrWhiteSpace() ?? true)
2018-03-27 10:04:07 +02:00
{
return Enumerable.Empty<UserTourStatus>();
2018-03-27 10:04:07 +02:00
}
2017-09-12 16:22:16 +02:00
IEnumerable<UserTourStatus>? userTours =
JsonConvert.DeserializeObject<IEnumerable<UserTourStatus>>(_backofficeSecurityAccessor.BackOfficeSecurity
.CurrentUser.TourData!);
return userTours ?? Enumerable.Empty<UserTourStatus>();
}
2017-09-12 16:22:16 +02:00
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)
{
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
var userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString();
if (userId is null)
{
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
throw new InvalidOperationException("Could not find user Id");
2018-03-27 10:04:07 +02:00
}
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
var user = await _backOfficeUserManager.FindByIdAsync(userId);
if (user == null) throw new InvalidOperationException("Could not find user");
2017-09-12 16:22:16 +02:00
IdentityResult result = await _backOfficeUserManager.AddPasswordAsync(user, newPassword);
2017-09-12 16:22:16 +02:00
if (result.Succeeded == false)
2018-03-27 10:04:07 +02:00
{
//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());
2017-09-12 16:22:16 +02:00
return ValidationProblem(ModelState);
2018-03-27 10:04:07 +02:00
}
2022-04-01 11:09:51 +02:00
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;
_userService.Save(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
}
2017-09-12 16:22:16 +02:00
2022-04-01 11:09:51 +02:00
//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();
2017-09-12 16:22:16 +02:00
}
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);
}
2017-09-12 16:22:16 +02:00
/// <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)
2017-09-12 16:22:16 +02:00
{
return null;
}
changingPasswordModel.Id = currentUser.Id;
2022-04-01 11:09:51 +02:00
// all current users have access to reset/manually change their password
2021-02-20 19:16:31 +00:00
Attempt<PasswordChangedModel?> passwordChangeResult =
await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _backOfficeUserManager);
2021-02-20 19:16:31 +00:00
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);
}
2017-09-19 15:51:47 +02:00
// TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[ValidateAngularAntiForgeryToken]
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
{
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
var userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().ResultOr(0).ToString(CultureInfo.InvariantCulture);
if (userId is null)
{
throw new InvalidOperationException("Could not find user Id");
}
BackOfficeIdentityUser? identityUser = await _backOfficeUserManager.FindByIdAsync(userId);
if (identityUser is null)
{
throw new InvalidOperationException("Could not find user");
}
// 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)
2020-08-20 11:54:35 +02:00
{
result[l.LoginProvider] = l.ProviderKey;
2020-08-20 11:54:35 +02:00
}
return result;
}
}