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

885 lines
37 KiB
C#
Raw Normal View History

2017-09-23 10:08:18 +02:00
using System;
2017-09-12 16:22:16 +02:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
2017-09-12 16:22:16 +02:00
using System.Runtime.Serialization;
using System.Security.Cryptography;
2017-09-12 16:22:16 +02:00
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
2017-09-12 16:22:16 +02:00
using Umbraco.Core;
2020-05-18 08:21:34 +01:00
using Umbraco.Core.BackOffice;
2017-09-12 16:22:16 +02:00
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
2019-12-18 13:05:34 +01:00
using Umbraco.Core.IO;
using Umbraco.Core.Mapping;
using Umbraco.Core.Media;
2017-09-12 16:22:16 +02:00
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
2017-09-12 16:22:16 +02:00
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.WebApi.Filters;
2020-05-18 13:00:32 +01:00
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.BackOffice.ModelBinders;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.ActionResults;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Editors;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
using IUser = Umbraco.Core.Models.Membership.IUser;
using Task = System.Threading.Tasks.Task;
namespace Umbraco.Web.BackOffice.Controllers
2017-09-12 16:22:16 +02:00
{
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
2017-09-12 16:22:16 +02:00
[UmbracoApplicationAuthorize(Constants.Applications.Users)]
[PrefixlessBodyModelValidator]
[IsCurrentUserModelFilter]
public class UsersController : UmbracoAuthorizedJsonController
{
2019-12-18 13:05:34 +01:00
private readonly IMediaFileSystem _mediaFileSystem;
private readonly ContentSettings _contentSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ISqlContext _sqlContext;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly SecuritySettings _securitySettings;
2020-05-07 09:34:16 +02:00
private readonly IRequestAccessor _requestAccessor;
2020-05-20 11:42:23 +02:00
private readonly IEmailSender _emailSender;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly AppCaches _appCaches;
private readonly IShortStringHelper _shortStringHelper;
private readonly IUserService _userService;
private readonly ILocalizedTextService _localizedTextService;
private readonly UmbracoMapper _umbracoMapper;
private readonly IEntityService _entityService;
private readonly IMediaService _mediaService;
private readonly IContentService _contentService;
private readonly GlobalSettings _globalSettings;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly ILoggerFactory _loggerFactory;
private readonly LinkGenerator _linkGenerator;
public UsersController(
IMediaFileSystem mediaFileSystem,
IOptions<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
ISqlContext sqlContext,
IImageUrlGenerator imageUrlGenerator,
IOptions<SecuritySettings> securitySettings,
IRequestAccessor requestAccessor,
IEmailSender emailSender,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
IUserService userService,
ILocalizedTextService localizedTextService,
UmbracoMapper umbracoMapper,
IEntityService entityService,
IMediaService mediaService,
IContentService contentService,
IOptions<GlobalSettings> globalSettings,
IBackOfficeUserManager backOfficeUserManager,
ILoggerFactory loggerFactory,
LinkGenerator linkGenerator)
{
2019-12-18 13:05:34 +01:00
_mediaFileSystem = mediaFileSystem;
_contentSettings = contentSettings.Value;
_hostingEnvironment = hostingEnvironment;
_sqlContext = sqlContext;
_imageUrlGenerator = imageUrlGenerator;
_securitySettings = securitySettings.Value;
2020-05-07 09:34:16 +02:00
_requestAccessor = requestAccessor;
2020-05-20 11:42:23 +02:00
_emailSender = emailSender;
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_appCaches = appCaches;
_shortStringHelper = shortStringHelper;
_userService = userService;
_localizedTextService = localizedTextService;
_umbracoMapper = umbracoMapper;
_entityService = entityService;
_mediaService = mediaService;
_contentService = contentService;
_globalSettings = globalSettings.Value;
_backOfficeUserManager = backOfficeUserManager;
_loggerFactory = loggerFactory;
_linkGenerator = linkGenerator;
}
2017-09-12 16:22:16 +02:00
/// <summary>
/// Returns a list of the sizes of gravatar urls for the user or null if the gravatar server cannot be reached
/// </summary>
/// <returns></returns>
public string[] GetCurrentUserAvatarUrls()
{
var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
2017-09-12 16:22:16 +02:00
if (urls == null)
throw new HttpResponseException(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint");
2017-09-12 16:22:16 +02:00
return urls;
}
[AppendUserModifiedHeader("id")]
[AdminUsersAuthorize]
public async Task<IActionResult> PostSetAvatar(int id, IList<IFormFile> files)
2017-09-12 16:22:16 +02:00
{
return await PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
2017-09-12 16:22:16 +02:00
}
internal static async Task<IActionResult> PostSetAvatarInternal(IList<IFormFile> files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
2017-09-12 16:22:16 +02:00
{
if (files is null)
2017-09-12 16:22:16 +02:00
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var root = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads);
2017-09-12 16:22:16 +02:00
//ensure it exists
Directory.CreateDirectory(root);
//must have a file
if (files.Count == 0)
2017-09-12 16:22:16 +02:00
{
return new NotFoundResult();
2017-09-12 16:22:16 +02:00
}
var user = userService.GetUserById(id);
if (user == null)
return new NotFoundResult();
2017-09-12 16:22:16 +02:00
if (files.Count > 1)
throw HttpResponseException.CreateValidationErrorResponse("The request was not formatted correctly, only one file can be attached to the request");
2017-09-12 16:22:16 +02:00
//get the file info
var file = files.First();
var fileName = file.FileName.Trim(new[] { '\"' }).TrimEnd();
var safeFileName = fileName.ToSafeFileName(shortStringHelper);
2017-09-12 16:22:16 +02:00
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
2020-03-12 15:30:22 +01:00
if (contentSettings.DisallowedUploadFiles.Contains(ext) == false)
2017-09-12 16:22:16 +02:00
{
//generate a path of known data, we don't want this path to be guessable
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
2017-09-12 16:22:16 +02:00
using (var fs = file.OpenReadStream())
2017-09-12 16:22:16 +02:00
{
mediaFileSystem.AddFile(user.Avatar, fs, true);
2017-09-12 16:22:16 +02:00
}
userService.Save(user);
}
return new OkObjectResult(user.GetUserAvatarUrls(cache, mediaFileSystem, imageUrlGenerator));
2017-09-12 16:22:16 +02:00
}
[AppendUserModifiedHeader("id")]
[AdminUsersAuthorize]
public ActionResult<string[]> PostClearAvatar(int id)
2017-09-12 16:22:16 +02:00
{
var found = _userService.GetUserById(id);
2017-09-12 16:22:16 +02:00
if (found == null)
return NotFound();
2017-09-12 16:22:16 +02:00
var filePath = found.Avatar;
//if the filePath is already null it will mean that the user doesn't have a custom avatar and their gravatar is currently
2017-09-19 15:51:47 +02:00
//being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value
2017-09-12 16:22:16 +02:00
//for the avatar.
if (filePath.IsNullOrWhiteSpace() == false)
{
found.Avatar = null;
}
else
{
//set a special value to indicate to not have any avatar
found.Avatar = "none";
}
_userService.Save(found);
2017-09-12 16:22:16 +02:00
if (filePath.IsNullOrWhiteSpace() == false)
{
if (_mediaFileSystem.FileExists(filePath))
_mediaFileSystem.DeleteFile(filePath);
2017-09-12 16:22:16 +02:00
}
return found.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
2017-09-12 16:22:16 +02:00
}
/// <summary>
/// Gets a user by Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[AdminUsersAuthorize]
2017-09-12 16:22:16 +02:00
public UserDisplay GetById(int id)
{
var user = _userService.GetUserById(id);
2017-09-12 16:22:16 +02:00
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var result = _umbracoMapper.Map<IUser, UserDisplay>(user);
2017-09-19 15:51:47 +02:00
return result;
2017-09-12 16:22:16 +02:00
}
/// <summary>
/// Get users by integer ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[AdminUsersAuthorize]
public IEnumerable<UserDisplay> GetByIds([FromJsonPath]int[] ids)
{
if (ids == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
if (ids.Length == 0)
return Enumerable.Empty<UserDisplay>();
var users = _userService.GetUsersById(ids);
if (users == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var result = _umbracoMapper.MapEnumerable<IUser, UserDisplay>(users);
return result;
}
2017-09-12 16:22:16 +02:00
/// <summary>
/// Returns a paged users collection
/// </summary>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="userGroups"></param>
/// <param name="userStates"></param>
/// <param name="filter"></param>
/// <returns></returns>
public PagedUserResult GetPagedUsers(
int pageNumber = 1,
int pageSize = 10,
string orderBy = "username",
Direction orderDirection = Direction.Ascending,
[FromQuery]string[] userGroups = null,
[FromQuery]UserState[] userStates = null,
2017-09-12 16:22:16 +02:00
string filter = "")
{
2017-09-19 15:51:47 +02:00
//following the same principle we had in previous versions, we would only show admins to admins, see
// https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs#L91
// so to do that here, we'll need to check if this current user is an admin and if not we should exclude all user who are
// also admins
var hideDisabledUsers = _securitySettings.HideDisabledUsersInBackOffice;
2017-09-19 15:51:47 +02:00
var excludeUserGroups = new string[0];
var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin();
2017-09-19 15:51:47 +02:00
if (isAdmin == false)
{
//this user is not an admin so in that case we need to exclude all admin users
2017-09-19 15:51:47 +02:00
excludeUserGroups = new[] {Constants.Security.AdminGroupAlias};
}
var filterQuery = _sqlContext.Query<IUser>();
2017-09-19 15:51:47 +02:00
if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsSuper())
2017-09-19 15:51:47 +02:00
{
2019-01-21 15:57:48 +01:00
// only super can see super - but don't use IsSuper, cannot be mapped to SQL
2018-03-16 09:06:44 +01:00
//filterQuery.Where(x => !x.IsSuper());
filterQuery.Where(x => x.Id != Constants.Security.SuperUserId);
2017-09-19 15:51:47 +02:00
}
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
}
if (hideDisabledUsers)
{
if (userStates == null || userStates.Any() == false)
{
userStates = new[] { UserState.Active, UserState.Invited, UserState.LockedOut, UserState.Inactive };
}
}
2017-09-12 16:22:16 +02:00
long pageIndex = pageNumber - 1;
long total;
var result = _userService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, excludeUserGroups, filterQuery);
2017-09-19 15:51:47 +02:00
2017-09-12 16:22:16 +02:00
var paged = new PagedUserResult(total, pageNumber, pageSize)
{
Items = _umbracoMapper.MapEnumerable<IUser, UserBasic>(result),
UserStates = _userService.GetUserStates()
2017-09-12 16:22:16 +02:00
};
return paged;
}
/// <summary>
/// Creates a new user
/// </summary>
/// <param name="userSave"></param>
/// <returns></returns>
public async Task<UserDisplay> PostCreateUser(UserInvite userSave)
{
if (userSave == null) throw new ArgumentNullException("userSave");
if (ModelState.IsValid == false)
{
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-12 16:22:16 +02:00
}
if (_securitySettings.UsernameIsEmail)
2017-09-12 16:22:16 +02:00
{
2017-09-19 15:51:47 +02:00
//ensure they are the same if we're using it
userSave.Username = userSave.Email;
}
else
{
//first validate the username if were showing it
CheckUniqueUsername(userSave.Username, null);
}
CheckUniqueEmail(userSave.Email, null);
//Perform authorization here to see if the current user can actually save this user with the info being requested
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
var canSaveUser = authHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups);
2017-09-19 15:51:47 +02:00
if (canSaveUser == false)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result);
2017-09-12 16:22:16 +02:00
}
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
//format is applied without us having to duplicate that logic
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
2017-09-12 16:22:16 +02:00
identityUser.Name = userSave.Name;
var created = await _backOfficeUserManager.CreateAsync(identityUser);
2017-09-12 16:22:16 +02:00
if (created.Succeeded == false)
{
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
2017-09-12 16:22:16 +02:00
}
string resetPassword;
var password = _backOfficeUserManager.GeneratePassword();
2017-09-12 16:22:16 +02:00
var result = await _backOfficeUserManager.AddPasswordAsync(identityUser, password);
if (result.Succeeded == false)
{
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
2017-09-12 16:22:16 +02:00
}
resetPassword = password;
2017-09-12 16:22:16 +02:00
//now re-look the user back up which will now exist
var user = _userService.GetByEmail(userSave.Email);
2017-09-12 16:22:16 +02:00
//map the save info over onto the user
user = _umbracoMapper.Map(userSave, user);
2017-09-12 16:22:16 +02:00
//since the back office user is creating this user, they will be set to approved
user.IsApproved = true;
_userService.Save(user);
2017-09-12 16:22:16 +02:00
var display = _umbracoMapper.Map<UserDisplay>(user);
2017-09-12 16:22:16 +02:00
display.ResetPasswordValue = resetPassword;
return display;
}
/// <summary>
/// Invites a user
/// </summary>
2017-09-19 15:51:47 +02:00
/// <param name="userSave"></param>
2017-09-12 16:22:16 +02:00
/// <returns></returns>
/// <remarks>
/// This will email the user an invite and generate a token that will be validated in the email
/// </remarks>
public async Task<UserDisplay> PostInviteUser(UserInvite userSave)
{
if (userSave == null) throw new ArgumentNullException("userSave");
if (userSave.Message.IsNullOrWhiteSpace())
ModelState.AddModelError("Message", "Message cannot be empty");
if (ModelState.IsValid == false)
{
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-12 16:22:16 +02:00
}
2017-09-19 15:51:47 +02:00
IUser user;
if (_securitySettings.UsernameIsEmail)
2017-09-12 16:22:16 +02:00
{
2017-09-19 15:51:47 +02:00
//ensure it's the same
userSave.Username = userSave.Email;
}
else
{
//first validate the username if we're showing it
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
}
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager();
if (!EmailSender.CanSendRequiredEmail(GlobalSettings) && !userMgr.HasSendingUserInviteEventHandler)
{
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
2017-09-19 15:51:47 +02:00
}
//Perform authorization here to see if the current user can actually save this user with the info being requested
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
var canSaveUser = authHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
2017-09-19 15:51:47 +02:00
if (canSaveUser == false)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result);
2017-09-12 16:22:16 +02:00
}
if (user == null)
{
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
//format is applied without us having to duplicate that logic
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
2017-09-12 16:22:16 +02:00
identityUser.Name = userSave.Name;
var created = await _backOfficeUserManager.CreateAsync(identityUser);
2017-09-12 16:22:16 +02:00
if (created.Succeeded == false)
{
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
2017-09-12 16:22:16 +02:00
}
//now re-look the user back up
user = _userService.GetByEmail(userSave.Email);
2017-09-12 16:22:16 +02:00
}
//map the save info over onto the user
user = _umbracoMapper.Map(userSave, user);
2017-09-12 16:22:16 +02:00
//ensure the invited date is set
user.InvitedDate = DateTime.Now;
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
//Save the updated user (which will process the user groups too)
_userService.Save(user);
var display = _umbracoMapper.Map<UserDisplay>(user);
2017-09-12 16:22:16 +02:00
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
var inviteArgs = new UserInviteEventArgs(
Request.TryGetHttpContext().Result.GetCurrentRequestIpAddress(),
performingUser: Security.GetUserId().Result,
userSave,
user);
2017-09-12 16:22:16 +02:00
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
try
{
userMgr.RaiseSendingUserInvite(inviteArgs);
}
catch (Exception ex)
{
Logger.Error<UsersController>(ex, "An error occured in a custom event handler while inviting the user");
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse($"An error occured inviting the user (check logs for more info): {ex.Message}"));
}
2017-09-12 16:22:16 +02:00
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
// If the event is handled then no need to send the email
if (inviteArgs.InviteHandled)
{
// if no user result was created then map the minimum args manually for the UI
if (!inviteArgs.ShowUserResult)
{
display = new UserDisplay
{
Name = userSave.Name,
Email = userSave.Email,
Username = userSave.Username
};
}
}
else
{
//send the email
await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message);
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
}
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/resendInviteHeader"), _localizedTextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name }));
2017-09-12 16:22:16 +02:00
return display;
}
2017-09-19 15:51:47 +02:00
private IUser CheckUniqueEmail(string email, Func<IUser, bool> extraCheck)
{
var user = _userService.GetByEmail(email);
2017-09-19 15:51:47 +02:00
if (user != null && (extraCheck == null || extraCheck(user)))
{
ModelState.AddModelError("Email", "A user with the email already exists");
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-19 15:51:47 +02:00
}
return user;
}
private IUser CheckUniqueUsername(string username, Func<IUser, bool> extraCheck)
{
var user = _userService.GetByUsername(username);
2017-09-19 15:51:47 +02:00
if (user != null && (extraCheck == null || extraCheck(user)))
{
ModelState.AddModelError(
_securitySettings.UsernameIsEmail ? "Email" : "Username",
2017-09-19 15:51:47 +02:00
"A user with the username already exists");
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-19 15:51:47 +02:00
}
return user;
}
2017-09-12 16:22:16 +02:00
2018-03-27 10:04:07 +02:00
private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message)
2017-09-12 16:22:16 +02:00
{
var user = await _backOfficeUserManager.FindByIdAsync(((int) userDisplay.Id).ToString());
var token = await _backOfficeUserManager.GenerateEmailConfirmationTokenAsync(user);
2017-09-12 16:22:16 +02:00
var inviteToken = string.Format("{0}{1}{2}",
(int)userDisplay.Id,
WebUtility.UrlEncode("|"),
token.ToUrlBase64());
// Get an mvc helper to get the url
var action = _linkGenerator.GetPathByAction("VerifyInvite", "BackOffice", new
2017-09-12 16:22:16 +02:00
{
area = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment),
2017-09-12 16:22:16 +02:00
invite = inviteToken
});
// Construct full URL using configured application URL (which will fall back to request)
2020-05-07 09:34:16 +02:00
var applicationUri = _requestAccessor.GetApplicationUrl();
2017-09-12 16:22:16 +02:00
var inviteUri = new Uri(applicationUri, action);
var emailSubject = _localizedTextService.Localize("user/inviteEmailCopySubject",
2017-09-12 16:22:16 +02:00
//Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings));
var emailBody = _localizedTextService.Localize("user/inviteEmailCopyFormat",
2017-09-12 16:22:16 +02:00
//Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings),
2018-03-27 10:04:07 +02:00
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
2017-09-12 16:22:16 +02:00
var mailMessage = new MailMessage()
{
Subject = emailSubject,
Body = emailBody,
2020-05-20 11:42:23 +02:00
IsBodyHtml = true,
To = { to.Email}
};
2017-09-12 16:22:16 +02:00
2020-05-20 11:42:23 +02:00
await _emailSender.SendAsync(mailMessage);
2017-09-12 16:22:16 +02:00
}
/// <summary>
/// Saves a user
/// </summary>
/// <param name="userSave"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
public UserDisplay PostSaveUser(UserSave userSave)
2017-09-12 16:22:16 +02:00
{
if (userSave == null) throw new ArgumentNullException(nameof(userSave));
2017-09-12 16:22:16 +02:00
if (ModelState.IsValid == false)
{
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-12 16:22:16 +02:00
}
var intId = userSave.Id.TryConvertTo<int>();
if (intId.Success == false)
throw new HttpResponseException(HttpStatusCode.NotFound);
var found = _userService.GetUserById(intId.Result);
2017-09-12 16:22:16 +02:00
if (found == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
2017-09-19 15:51:47 +02:00
//Perform authorization here to see if the current user can actually save this user with the info being requested
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
var canSaveUser = authHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
2017-09-19 15:51:47 +02:00
if (canSaveUser == false)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result);
2017-09-19 15:51:47 +02:00
}
2017-09-12 16:22:16 +02:00
var hasErrors = false;
Merge branch 'netcore/dev' into netcore/task/6973-migrating-authenticationcontroller # Conflicts: # src/Umbraco.Core/Constants-Security.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs # src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs # src/Umbraco.Web.BackOffice/Controllers/UsersController.cs # src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs # src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js # src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Filters/IsCurrentUserModelFilterAttribute.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs # src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
2020-10-23 10:10:02 +11:00
// we need to check if there's any Deny Local login providers present, if so we need to ensure that the user's email address cannot be changed
var owinContext = Request.TryGetOwinContext().Result;
var hasDenyLocalLogin = owinContext.Authentication.HasDenyLocalLogin();
if (hasDenyLocalLogin)
{
userSave.Email = found.Email; // it cannot change, this would only happen if people are mucking around with the request
}
var existing = _userService.GetByEmail(userSave.Email);
2017-09-12 16:22:16 +02:00
if (existing != null && existing.Id != userSave.Id)
{
ModelState.AddModelError("Email", "A user with the email already exists");
hasErrors = true;
}
existing = _userService.GetByUsername(userSave.Username);
2017-09-12 16:22:16 +02:00
if (existing != null && existing.Id != userSave.Id)
{
ModelState.AddModelError("Username", "A user with the username already exists");
hasErrors = true;
}
// going forward we prefer to align usernames with email, so we should cross-check to make sure
// the email or username isn't somehow being used by anyone.
existing = _userService.GetByEmail(userSave.Username);
2017-09-12 16:22:16 +02:00
if (existing != null && existing.Id != userSave.Id)
{
ModelState.AddModelError("Username", "A user using this as their email already exists");
hasErrors = true;
}
existing = _userService.GetByUsername(userSave.Email);
2017-09-12 16:22:16 +02:00
if (existing != null && existing.Id != userSave.Id)
{
ModelState.AddModelError("Email", "A user using this as their username already exists");
hasErrors = true;
}
2019-09-30 22:26:21 +01:00
// if the found user has their email for username, we want to keep this synced when changing the email.
2017-09-12 16:22:16 +02:00
// we have already cross-checked above that the email isn't colliding with anything, so we can safely assign it here.
if (_securitySettings.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email)
2017-09-12 16:22:16 +02:00
{
userSave.Username = userSave.Email;
}
if (hasErrors)
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
2017-09-12 16:22:16 +02:00
//merge the save data onto the user
var user = _umbracoMapper.Map(userSave, found);
2017-09-12 16:22:16 +02:00
_userService.Save(user);
2017-09-12 16:22:16 +02:00
var display = _umbracoMapper.Map<UserDisplay>(user);
2017-09-12 16:22:16 +02:00
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/operationSavedHeader"), _localizedTextService.Localize("speechBubbles/editUserSaved"));
2017-09-12 16:22:16 +02:00
return display;
2017-09-19 15:51:47 +02:00
}
2017-09-12 16:22:16 +02:00
/// <summary>
///
/// </summary>
/// <param name="changingPasswordModel"></param>
/// <returns></returns>
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel changingPasswordModel)
{
changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel));
if (ModelState.IsValid == false)
{
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
}
var intId = changingPasswordModel.Id.TryConvertTo<int>();
if (intId.Success == false)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var found = _userService.GetUserById(intId.Result);
if (found == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _backOfficeUserManager);
if (passwordChangeResult.Success)
{
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
result.AddSuccessNotification(_localizedTextService.Localize("general/success"), _localizedTextService.Localize("user/passwordChangedGeneric"));
return result;
}
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
}
throw HttpResponseException.CreateValidationErrorResponse(ModelState);
}
2017-09-12 16:22:16 +02:00
/// <summary>
/// Disables the users with the given user ids
/// </summary>
/// <param name="userIds"></param>
[AdminUsersAuthorize("userIds")]
public IActionResult PostDisableUsers([FromQuery]int[] userIds)
2017-09-12 16:22:16 +02:00
{
var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId();
2018-03-02 15:48:21 +01:00
if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result))
2017-09-12 16:22:16 +02:00
{
throw HttpResponseException.CreateNotificationValidationErrorResponse("The current user cannot disable itself");
2017-09-12 16:22:16 +02:00
}
var users = _userService.GetUsersById(userIds).ToArray();
2017-09-12 16:22:16 +02:00
foreach (var u in users)
{
u.IsApproved = false;
u.InvitedDate = null;
}
_userService.Save(users);
2017-09-12 16:22:16 +02:00
if (users.Length > 1)
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/disableUsersSuccess", new[] {userIds.Length.ToString()}));
2017-09-12 16:22:16 +02:00
}
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/disableUserSuccess", new[] { users[0].Name }));
2017-09-12 16:22:16 +02:00
}
/// <summary>
/// Enables the users with the given user ids
/// </summary>
/// <param name="userIds"></param>
[AdminUsersAuthorize("userIds")]
public IActionResult PostEnableUsers([FromQuery]int[] userIds)
2017-09-12 16:22:16 +02:00
{
var users = _userService.GetUsersById(userIds).ToArray();
2017-09-12 16:22:16 +02:00
foreach (var u in users)
{
u.IsApproved = true;
}
_userService.Save(users);
2017-09-12 16:22:16 +02:00
if (users.Length > 1)
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/enableUsersSuccess", new[] { userIds.Length.ToString() }));
2017-09-12 16:22:16 +02:00
}
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name }));
2017-09-19 15:51:47 +02:00
}
/// <summary>
/// Unlocks the users with the given user ids
/// </summary>
/// <param name="userIds"></param>
[AdminUsersAuthorize("userIds")]
public async Task<IActionResult> PostUnlockUsers([FromQuery]int[] userIds)
2017-09-19 15:51:47 +02:00
{
if (userIds.Length <= 0) return Ok();
2020-09-04 01:40:46 +10:00
var notFound = new List<int>();
2017-09-19 15:51:47 +02:00
foreach (var u in userIds)
2017-09-19 15:51:47 +02:00
{
var user = await _backOfficeUserManager.FindByIdAsync(u.ToString());
2020-09-04 01:40:46 +10:00
if (user == null)
{
notFound.Add(u);
continue;
}
var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
2017-09-19 15:51:47 +02:00
if (unlockResult.Succeeded == false)
{
throw HttpResponseException.CreateValidationErrorResponse(
string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.ToErrorMessage()));
2017-09-19 15:51:47 +02:00
}
if (userIds.Length == 1)
2017-09-19 15:51:47 +02:00
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/unlockUserSuccess", new[] {user.Name}));
2017-09-19 15:51:47 +02:00
}
}
return new UmbracoNotificationSuccessResponse(
2020-09-04 01:40:46 +10:00
_localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {(userIds.Length - notFound.Count).ToString()}));
2017-09-12 16:22:16 +02:00
}
[AdminUsersAuthorize("userIds")]
public IActionResult PostSetUserGroupsOnUsers([FromQuery]string[] userGroupAliases, [FromQuery]int[] userIds)
2017-09-12 16:22:16 +02:00
{
var users = _userService.GetUsersById(userIds).ToArray();
var userGroups = _userService.GetUserGroupsByAlias(userGroupAliases).Select(x => x.ToReadOnlyGroup()).ToArray();
2017-09-12 16:22:16 +02:00
foreach (var u in users)
{
u.ClearGroups();
foreach (var userGroup in userGroups)
{
u.AddGroup(userGroup);
}
}
_userService.Save(users);
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/setUserGroupOnUsersSuccess"));
2017-09-12 16:22:16 +02:00
}
/// <summary>
/// Deletes the non-logged in user provided id
/// </summary>
/// <param name="id">User Id</param>
/// <remarks>
/// Limited to users that haven't logged in to avoid issues with related records constrained
/// with a foreign key on the user Id
/// </remarks>
[AdminUsersAuthorize]
public IActionResult PostDeleteNonLoggedInUser(int id)
{
var user = _userService.GetUserById(id);
if (user == null)
{
return NotFound();
}
// Check user hasn't logged in. If they have they may have made content changes which will mean
// the Id is associated with audit trails, versions etc. and can't be removed.
if (user.LastLoginDate != default(DateTime))
{
return BadRequest();
}
var userName = user.Name;
_userService.Delete(user, true);
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/deleteUserSuccess", new[] { userName }));
}
2017-09-12 16:22:16 +02:00
public class PagedUserResult : PagedResult<UserBasic>
{
public PagedUserResult(long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize)
{
UserStates = new Dictionary<UserState, int>();
}
/// <summary>
/// This is basically facets of UserStates key = state, value = count
/// </summary>
[DataMember(Name = "userStates")]
public IDictionary<UserState, int> UserStates { get; set; }
}
2017-09-19 15:51:47 +02:00
2017-09-12 16:22:16 +02:00
}
2017-09-23 10:08:18 +02:00
}