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.Http;
|
2020-05-20 00:20:25 +02:00
|
|
|
|
using System.Net.Mail;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using System.Runtime.Serialization;
|
2019-10-23 17:19:49 +02:00
|
|
|
|
using System.Security.Cryptography;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using System.Threading.Tasks;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using Microsoft.AspNetCore.Routing;
|
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;
|
2019-12-18 13:05:34 +01:00
|
|
|
|
using Umbraco.Core.IO;
|
2019-02-01 15:24:07 +11:00
|
|
|
|
using Umbraco.Core.Logging;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using Umbraco.Core.Models;
|
|
|
|
|
|
using Umbraco.Core.Models.Membership;
|
2019-02-01 15:24:07 +11:00
|
|
|
|
using Umbraco.Core.Persistence;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using Umbraco.Core.Services;
|
2019-12-18 18:55:00 +01:00
|
|
|
|
using Umbraco.Core.Strings;
|
2020-01-27 15:29:04 +10:00
|
|
|
|
using Umbraco.Web.Models;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using Umbraco.Web.Models.ContentEditing;
|
|
|
|
|
|
using Umbraco.Web.WebApi.Filters;
|
|
|
|
|
|
using Constants = Umbraco.Core.Constants;
|
|
|
|
|
|
using IUser = Umbraco.Core.Models.Membership.IUser;
|
|
|
|
|
|
using Task = System.Threading.Tasks.Task;
|
2020-01-20 14:15:54 -08:00
|
|
|
|
using Umbraco.Core.Mapping;
|
2020-01-21 17:03:46 -08:00
|
|
|
|
using Umbraco.Core.Configuration.UmbracoSettings;
|
2020-04-03 11:03:06 +11:00
|
|
|
|
using Umbraco.Core.Hosting;
|
2020-03-24 09:37:46 +01:00
|
|
|
|
using Umbraco.Core.Media;
|
2020-05-18 13:00:32 +01:00
|
|
|
|
using Umbraco.Extensions;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
using Umbraco.Web.BackOffice.Filters;
|
|
|
|
|
|
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.Security;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.BackOffice.Controllers
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-08-04 12:27:21 +10: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;
|
2020-03-12 15:30:22 +01:00
|
|
|
|
private readonly IContentSettings _contentSettings;
|
2020-04-03 11:03:06 +11:00
|
|
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
2020-02-10 11:23:23 +01:00
|
|
|
|
private readonly ISqlContext _sqlContext;
|
2020-02-11 11:43:54 -08:00
|
|
|
|
private readonly IImageUrlGenerator _imageUrlGenerator;
|
2020-03-12 14:36:25 +01:00
|
|
|
|
private readonly ISecuritySettings _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;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
private readonly IWebSecurity _webSecurity;
|
|
|
|
|
|
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 IGlobalSettings _globalSettings;
|
|
|
|
|
|
private readonly BackOfficeUserManager _backOfficeUserManager;
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
private readonly LinkGenerator _linkGenerator;
|
2019-12-18 18:55:00 +01:00
|
|
|
|
|
|
|
|
|
|
public UsersController(
|
|
|
|
|
|
IMediaFileSystem mediaFileSystem,
|
2020-03-12 15:30:22 +01:00
|
|
|
|
IContentSettings contentSettings,
|
2020-04-03 11:03:06 +11:00
|
|
|
|
IHostingEnvironment hostingEnvironment,
|
2020-06-22 10:08:08 +02:00
|
|
|
|
ISqlContext sqlContext,
|
2020-02-14 13:04:49 +01:00
|
|
|
|
IImageUrlGenerator imageUrlGenerator,
|
2020-05-07 09:34:16 +02:00
|
|
|
|
ISecuritySettings securitySettings,
|
2020-05-20 12:43:13 +02:00
|
|
|
|
IRequestAccessor requestAccessor,
|
2020-06-22 10:08:08 +02:00
|
|
|
|
IEmailSender emailSender,
|
|
|
|
|
|
IWebSecurity webSecurity,
|
|
|
|
|
|
AppCaches appCaches,
|
|
|
|
|
|
IShortStringHelper shortStringHelper,
|
|
|
|
|
|
IUserService userService,
|
|
|
|
|
|
ILocalizedTextService localizedTextService,
|
|
|
|
|
|
UmbracoMapper umbracoMapper,
|
|
|
|
|
|
IEntityService entityService,
|
|
|
|
|
|
IMediaService mediaService,
|
|
|
|
|
|
IContentService contentService,
|
|
|
|
|
|
IGlobalSettings globalSettings,
|
|
|
|
|
|
BackOfficeUserManager backOfficeUserManager,
|
|
|
|
|
|
ILogger logger,
|
|
|
|
|
|
LinkGenerator linkGenerator)
|
2019-02-01 15:24:07 +11:00
|
|
|
|
{
|
2019-12-18 13:05:34 +01:00
|
|
|
|
_mediaFileSystem = mediaFileSystem;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_contentSettings = contentSettings;
|
2020-04-03 11:03:06 +11:00
|
|
|
|
_hostingEnvironment = hostingEnvironment;
|
2020-02-10 11:23:23 +01:00
|
|
|
|
_sqlContext = sqlContext;
|
2020-02-11 11:43:54 -08:00
|
|
|
|
_imageUrlGenerator = imageUrlGenerator;
|
2020-03-12 14:36:25 +01:00
|
|
|
|
_securitySettings = securitySettings;
|
2020-05-07 09:34:16 +02:00
|
|
|
|
_requestAccessor = requestAccessor;
|
2020-05-20 11:42:23 +02:00
|
|
|
|
_emailSender = emailSender;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_webSecurity = webSecurity;
|
|
|
|
|
|
_appCaches = appCaches;
|
|
|
|
|
|
_shortStringHelper = shortStringHelper;
|
|
|
|
|
|
_userService = userService;
|
|
|
|
|
|
_localizedTextService = localizedTextService;
|
|
|
|
|
|
_umbracoMapper = umbracoMapper;
|
|
|
|
|
|
_entityService = entityService;
|
|
|
|
|
|
_mediaService = mediaService;
|
|
|
|
|
|
_contentService = contentService;
|
|
|
|
|
|
_globalSettings = globalSettings;
|
|
|
|
|
|
_backOfficeUserManager = backOfficeUserManager;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_linkGenerator = linkGenerator;
|
2019-02-01 15:24:07 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var urls = _webSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (urls == null)
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw new HttpResponseException(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint");
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
return urls;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[AppendUserModifiedHeader("id")]
|
2019-03-15 12:20:24 +11:00
|
|
|
|
[AdminUsersAuthorize]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public async Task<IActionResult> PostSetAvatar(int id, IList<IFormFile> files)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return await PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
internal static async Task<IActionResult> PostSetAvatarInternal(IList<IFormFile> files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, IContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (files is null)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-04 14:40:11 +02:00
|
|
|
|
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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (files.Count == 0)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new NotFoundResult();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var user = userService.GetUserById(id);
|
|
|
|
|
|
if (user == null)
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new NotFoundResult();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var file = files.First();
|
|
|
|
|
|
var fileName = file.FileName.Trim(new[] { '\"' }).TrimEnd();
|
2019-12-18 18:55:00 +01:00
|
|
|
|
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
|
2019-10-23 17:19:49 +02:00
|
|
|
|
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
using (var fs = file.OpenReadStream())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-02-10 11:23:23 +01:00
|
|
|
|
mediaFileSystem.AddFile(user.Avatar, fs, true);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
userService.Save(user);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new OkObjectResult(user.GetUserAvatarUrls(cache, mediaFileSystem, imageUrlGenerator));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[AppendUserModifiedHeader("id")]
|
2019-03-15 12:20:24 +11:00
|
|
|
|
[AdminUsersAuthorize]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public ActionResult<string[]> PostClearAvatar(int id)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var found = _userService.GetUserById(id);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (found == null)
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(found);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
if (filePath.IsNullOrWhiteSpace() == false)
|
|
|
|
|
|
{
|
2020-02-10 11:23:23 +01:00
|
|
|
|
if (_mediaFileSystem.FileExists(filePath))
|
|
|
|
|
|
_mediaFileSystem.DeleteFile(filePath);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +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>
|
2020-06-22 10:08:08 +02:00
|
|
|
|
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
2019-03-15 12:20:24 +11:00
|
|
|
|
[AdminUsersAuthorize]
|
2017-09-12 16:22:16 +02:00
|
|
|
|
public UserDisplay GetById(int id)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = _userService.GetUserById(id);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (user == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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>
|
|
|
|
|
|
/// 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,
|
2020-06-22 10:08:08 +02:00
|
|
|
|
[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
|
|
|
|
|
|
|
2020-03-12 14:36:25 +01:00
|
|
|
|
var hideDisabledUsers = _securitySettings.HideDisabledUsersInBackoffice;
|
2017-09-19 15:51:47 +02:00
|
|
|
|
var excludeUserGroups = new string[0];
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var isAdmin = _webSecurity.CurrentUser.IsAdmin();
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (isAdmin == false)
|
|
|
|
|
|
{
|
2019-01-26 10:52:19 -05:00
|
|
|
|
//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};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-10 11:23:23 +01:00
|
|
|
|
var filterQuery = _sqlContext.Query<IUser>();
|
2017-09-19 15:51:47 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (!_webSecurity.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());
|
2018-05-31 23:05:35 +10:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-05 21:07:12 +01:00
|
|
|
|
if (hideDisabledUsers)
|
2018-06-28 19:54:42 +01:00
|
|
|
|
{
|
2018-08-02 21:55:04 +01:00
|
|
|
|
if (userStates == null || userStates.Any() == false)
|
|
|
|
|
|
{
|
2018-08-02 22:01:45 +01:00
|
|
|
|
userStates = new[] { UserState.Active, UserState.Invited, UserState.LockedOut, UserState.Inactive };
|
2018-08-02 21:55:04 +01:00
|
|
|
|
}
|
2018-06-28 19:54:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-12 16:22:16 +02:00
|
|
|
|
long pageIndex = pageNumber - 1;
|
|
|
|
|
|
long total;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-12 14:36:25 +01: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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
|
|
|
|
|
|
var canSaveUser = authHelper.IsAuthorized(_webSecurity.CurrentUser, null, null, null, userSave.UserGroups);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (canSaveUser == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
identityUser.Name = userSave.Name;
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var created = await _backOfficeUserManager.CreateAsync(identityUser);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (created.Succeeded == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-16 13:53:03 +00:00
|
|
|
|
string resetPassword;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var password = _backOfficeUserManager.GeneratePassword();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var result = await _backOfficeUserManager.AddPasswordAsync(identityUser, password);
|
2020-03-16 13:53:03 +00:00
|
|
|
|
if (result.Succeeded == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-16 13:53:03 +00:00
|
|
|
|
resetPassword = password;
|
|
|
|
|
|
|
2017-09-12 16:22:16 +02:00
|
|
|
|
//now re-look the user back up which will now exist
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = _userService.GetByEmail(userSave.Email);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
//map the save info over onto the user
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(user);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +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)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (EmailSender.CanSendRequiredEmail(_globalSettings) == false)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateNotificationValidationErrorResponse("No Email server is configured");
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
IUser user;
|
2020-03-12 14:36:25 +01:00
|
|
|
|
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
|
|
|
|
|
|
user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
|
|
|
|
|
|
|
|
|
|
|
|
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
|
|
|
|
|
|
var canSaveUser = authHelper.IsAuthorized(_webSecurity.CurrentUser, user, null, null, userSave.UserGroups);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (canSaveUser == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
identityUser.Name = userSave.Name;
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var created = await _backOfficeUserManager.CreateAsync(identityUser);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (created.Succeeded == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//now re-look the user back up
|
2020-06-22 10:08:08 +02:00
|
|
|
|
user = _userService.GetByEmail(userSave.Email);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//map the save info over onto the user
|
2020-06-22 10:08:08 +02:00
|
|
|
|
user = _umbracoMapper.Map(userSave, user);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
//ensure the invited date is set
|
|
|
|
|
|
user.InvitedDate = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
//Save the updated user
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(user);
|
|
|
|
|
|
var display = _umbracoMapper.Map<UserDisplay>(user);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
//send the email
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
await SendUserInviteEmailAsync(display, _webSecurity.CurrentUser.Name, _webSecurity.CurrentUser.Email, user, userSave.Message);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/resendInviteHeader"), _localizedTextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name }));
|
2018-04-09 08:52:54 +02:00
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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");
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = _userService.GetByUsername(username);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (user != null && (extraCheck == null || extraCheck(user)))
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(
|
2020-03-12 14:36:25 +01:00
|
|
|
|
_securitySettings.UsernameIsEmail ? "Email" : "Username",
|
2017-09-19 15:51:47 +02:00
|
|
|
|
"A user with the username already exists");
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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
|
|
|
|
{
|
2020-06-22 10:08:08 +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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var action = _linkGenerator.GetPathByAction("VerifyInvite", "BackOffice", new
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +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);
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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!
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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!
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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
|
|
|
|
|
2020-05-20 00:20:25 +02:00
|
|
|
|
var mailMessage = new MailMessage()
|
|
|
|
|
|
{
|
|
|
|
|
|
Subject = emailSubject,
|
|
|
|
|
|
Body = emailBody,
|
2020-05-20 11:42:23 +02:00
|
|
|
|
IsBodyHtml = true,
|
|
|
|
|
|
To = { to.Email}
|
2020-05-20 00:20:25 +02:00
|
|
|
|
};
|
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>
|
2020-06-22 10:08:08 +02:00
|
|
|
|
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
2017-09-12 16:22:16 +02:00
|
|
|
|
public async Task<UserDisplay> PostSaveUser(UserSave userSave)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (userSave == null) throw new ArgumentNullException(nameof(userSave));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
if (ModelState.IsValid == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(_contentService,_mediaService, _userService, _entityService);
|
|
|
|
|
|
var canSaveUser = authHelper.IsAuthorized(_webSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (canSaveUser == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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.
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
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.
|
2020-03-12 14:36:25 +01:00
|
|
|
|
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)
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
//merge the save data onto the user
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = _umbracoMapper.Map(userSave, found);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(user);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var display = _umbracoMapper.Map<UserDisplay>(user);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-22 10:08:08 +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
|
|
|
|
|
2020-01-27 15:29:04 +10:00
|
|
|
|
/// <summary>
|
2020-01-29 09:17:08 +01:00
|
|
|
|
///
|
2020-01-27 15:29:04 +10:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="changingPasswordModel"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel changingPasswordModel)
|
|
|
|
|
|
{
|
2020-01-29 09:17:08 +01:00
|
|
|
|
changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel));
|
2020-01-27 15:29:04 +10:00
|
|
|
|
|
|
|
|
|
|
if (ModelState.IsValid == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
2020-01-27 15:29:04 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var intId = changingPasswordModel.Id.TryConvertTo<int>();
|
|
|
|
|
|
if (intId.Success == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var found = _userService.GetUserById(intId.Result);
|
2020-01-27 15:29:04 +10:00
|
|
|
|
if (found == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var passwordChanger = new PasswordChanger(_logger);
|
|
|
|
|
|
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_webSecurity.CurrentUser, found, changingPasswordModel, _backOfficeUserManager);
|
2020-01-27 15:29:04 +10:00
|
|
|
|
|
|
|
|
|
|
if (passwordChangeResult.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
|
2020-06-22 10:08:08 +02:00
|
|
|
|
result.AddSuccessNotification(_localizedTextService.Localize("general/success"), _localizedTextService.Localize("user/passwordChangedGeneric"));
|
2020-01-27 15:29:04 +10:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
|
|
|
|
|
{
|
|
|
|
|
|
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateValidationErrorResponse(ModelState);
|
2020-01-27 15:29:04 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-09-12 16:22:16 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Disables the users with the given user ids
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="userIds"></param>
|
2019-03-15 12:39:46 +11:00
|
|
|
|
[AdminUsersAuthorize("userIds")]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public IActionResult PostDisableUsers([FromQuery]int[] userIds)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var tryGetCurrentUserId = _webSecurity.GetUserId();
|
2018-03-02 15:48:21 +01:00
|
|
|
|
if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result))
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateNotificationValidationErrorResponse("The current user cannot disable itself");
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +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;
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(users);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
if (users.Length > 1)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/disableUsersSuccess", new[] {userIds.Length.ToString()}));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +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>
|
2019-03-15 12:39:46 +11:00
|
|
|
|
[AdminUsersAuthorize("userIds")]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public IActionResult PostEnableUsers([FromQuery]int[] userIds)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var users = _userService.GetUsersById(userIds).ToArray();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
foreach (var u in users)
|
|
|
|
|
|
{
|
|
|
|
|
|
u.IsApproved = true;
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(users);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
if (users.Length > 1)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/enableUsersSuccess", new[] { userIds.Length.ToString() }));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +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>
|
2019-03-15 12:39:46 +11:00
|
|
|
|
[AdminUsersAuthorize("userIds")]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public async Task<IActionResult> PostUnlockUsers([FromQuery]int[] userIds)
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
if (userIds.Length <= 0) return Ok();
|
2017-09-19 15:51:47 +02:00
|
|
|
|
|
2020-05-04 18:19:24 +01:00
|
|
|
|
foreach (var u in userIds)
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = await _backOfficeUserManager.FindByIdAsync(u.ToString());
|
2020-05-04 18:19:24 +01:00
|
|
|
|
if (user == null) throw new InvalidOperationException();
|
2020-03-16 13:53:03 +00:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (unlockResult.Succeeded == false)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
throw HttpResponseException.CreateValidationErrorResponse(
|
2020-05-04 18:49:02 +01:00
|
|
|
|
string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.ToErrorMessage()));
|
2017-09-19 15:51:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-05-04 18:19:24 +01:00
|
|
|
|
if (userIds.Length == 1)
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/unlockUserSuccess", new[] {user.Name}));
|
2017-09-19 15:51:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {userIds.Length.ToString()}));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-15 12:39:46 +11:00
|
|
|
|
[AdminUsersAuthorize("userIds")]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public IActionResult PostSetUserGroupsOnUsers([FromQuery]string[] userGroupAliases, [FromQuery]int[] userIds)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Save(users);
|
|
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/setUserGroupOnUsersSuccess"));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-09 08:52:54 +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>
|
2019-03-15 12:39:46 +11:00
|
|
|
|
[AdminUsersAuthorize]
|
2020-06-22 10:08:08 +02:00
|
|
|
|
public IActionResult PostDeleteNonLoggedInUser(int id)
|
2018-04-09 08:52:54 +02:00
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
var user = _userService.GetUserById(id);
|
2018-04-09 08:52:54 +02:00
|
|
|
|
if (user == null)
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return NotFound();
|
2018-04-09 08:52:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
|
|
{
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return BadRequest();
|
2018-04-09 08:52:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userName = user.Name;
|
2020-06-22 10:08:08 +02:00
|
|
|
|
_userService.Delete(user, true);
|
2018-12-12 17:49:24 +01:00
|
|
|
|
|
2020-06-22 10:08:08 +02:00
|
|
|
|
return new UmbracoNotificationSuccessResponse(
|
|
|
|
|
|
_localizedTextService.Localize("speechBubbles/deleteUserSuccess", new[] { userName }));
|
2018-04-09 08:52:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
}
|