2017-05-11 13:11:41 +10:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2017-06-27 15:44:55 +10:00
|
|
|
using System.Configuration;
|
2017-05-26 00:02:32 +10:00
|
|
|
using System.IO;
|
2017-05-11 13:11:41 +10:00
|
|
|
using System.Linq;
|
2014-10-16 18:45:02 +10:00
|
|
|
using System.Net;
|
2017-05-11 13:11:41 +10:00
|
|
|
using System.Net.Http;
|
2017-06-15 16:47:51 +02:00
|
|
|
using System.Runtime.Serialization;
|
2017-05-26 00:02:32 +10:00
|
|
|
using System.Threading.Tasks;
|
2017-05-24 19:01:01 +10:00
|
|
|
using System.Web;
|
2014-10-16 18:45:02 +10:00
|
|
|
using System.Web.Http;
|
2017-08-21 16:53:58 +10:00
|
|
|
using System.Web.Http.Controllers;
|
2017-06-14 11:29:15 +02:00
|
|
|
using System.Web.Mvc;
|
|
|
|
|
using System.Web.Routing;
|
2017-06-27 15:30:32 +10:00
|
|
|
using System.Web.Security;
|
2017-06-14 11:29:15 +02:00
|
|
|
using System.Web.WebPages;
|
2017-05-11 13:11:41 +10:00
|
|
|
using AutoMapper;
|
|
|
|
|
using ClientDependency.Core;
|
2017-05-29 09:36:07 +02:00
|
|
|
using Microsoft.AspNet.Identity;
|
2017-05-11 13:11:41 +10:00
|
|
|
using Umbraco.Core;
|
2017-06-27 19:55:03 +10:00
|
|
|
using Umbraco.Core.Cache;
|
2017-05-24 19:01:01 +10:00
|
|
|
using Umbraco.Core.Configuration;
|
2017-05-26 00:02:32 +10:00
|
|
|
using Umbraco.Core.IO;
|
2017-05-11 13:11:41 +10:00
|
|
|
using Umbraco.Core.Models;
|
2017-05-29 09:36:07 +02:00
|
|
|
using Umbraco.Core.Models.Identity;
|
2017-05-11 13:11:41 +10:00
|
|
|
using Umbraco.Core.Models.Membership;
|
|
|
|
|
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
2017-08-22 17:55:28 +10:00
|
|
|
using Umbraco.Core.Persistence.Querying;
|
2017-05-29 09:36:07 +02:00
|
|
|
using Umbraco.Core.Security;
|
2017-05-25 12:17:32 +10:00
|
|
|
using Umbraco.Core.Services;
|
2017-05-11 13:11:41 +10:00
|
|
|
using Umbraco.Web.Models.ContentEditing;
|
2014-10-16 18:45:02 +10:00
|
|
|
using Umbraco.Web.Mvc;
|
2017-05-24 19:01:01 +10:00
|
|
|
using Umbraco.Web.WebApi;
|
2014-10-16 18:45:02 +10:00
|
|
|
using Umbraco.Web.WebApi.Filters;
|
2017-08-21 16:53:58 +10:00
|
|
|
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
|
2014-10-16 18:45:02 +10:00
|
|
|
using Constants = Umbraco.Core.Constants;
|
2017-05-29 09:36:07 +02:00
|
|
|
using IUser = Umbraco.Core.Models.Membership.IUser;
|
2017-06-14 11:29:15 +02:00
|
|
|
using Task = System.Threading.Tasks.Task;
|
2014-10-16 18:45:02 +10:00
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Editors
|
|
|
|
|
{
|
|
|
|
|
[PluginController("UmbracoApi")]
|
|
|
|
|
[UmbracoApplicationAuthorize(Constants.Applications.Users)]
|
2017-06-23 16:38:39 +10:00
|
|
|
[PrefixlessBodyModelValidator]
|
2017-06-27 15:44:55 +10:00
|
|
|
[IsCurrentUserModelFilter]
|
2017-05-17 22:58:32 +10:00
|
|
|
public class UsersController : UmbracoAuthorizedJsonController
|
2014-10-16 18:45:02 +10:00
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor
|
|
|
|
|
/// </summary>
|
2017-05-17 22:58:32 +10:00
|
|
|
public UsersController()
|
2014-10-16 18:45:02 +10:00
|
|
|
: this(UmbracoContext.Current)
|
2017-05-11 13:11:41 +10:00
|
|
|
{
|
2014-10-16 18:45:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="umbracoContext"></param>
|
2017-05-17 22:58:32 +10:00
|
|
|
public UsersController(UmbracoContext umbracoContext)
|
2014-10-16 18:45:02 +10:00
|
|
|
: base(umbracoContext)
|
2017-05-11 13:11:41 +10:00
|
|
|
{
|
|
|
|
|
}
|
2017-05-25 12:17:32 +10:00
|
|
|
|
2017-06-20 16:49:24 +10:00
|
|
|
public UsersController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager<BackOfficeIdentityUser> backOfficeUserManager)
|
|
|
|
|
: base(umbracoContext, umbracoHelper, backOfficeUserManager)
|
2017-05-29 17:25:44 +02:00
|
|
|
{
|
|
|
|
|
}
|
2017-08-21 16:53:58 +10:00
|
|
|
|
2017-05-26 02:15:37 +10: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()
|
|
|
|
|
{
|
2017-09-07 22:24:18 +10:00
|
|
|
var urls = UmbracoContext.Security.CurrentUser.GetUserAvatarUrls(ApplicationContext.ApplicationCache.StaticCache);
|
2017-05-26 02:15:37 +10:00
|
|
|
if (urls == null)
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint"));
|
|
|
|
|
|
|
|
|
|
return urls;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-25 20:51:58 +10:00
|
|
|
[AppendUserModifiedHeader("id")]
|
2017-05-26 00:02:32 +10:00
|
|
|
[FileUploadCleanupFilter(false)]
|
2017-05-26 02:15:37 +10:00
|
|
|
public async Task<HttpResponseMessage> PostSetAvatar(int id)
|
2017-05-25 12:17:32 +10:00
|
|
|
{
|
2017-06-27 19:55:03 +10:00
|
|
|
return await PostSetAvatarInternal(Request, Services.UserService, ApplicationContext.ApplicationCache.StaticCache, id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static async Task<HttpResponseMessage> PostSetAvatarInternal(HttpRequestMessage request, IUserService userService, ICacheProvider staticCache, int id)
|
|
|
|
|
{
|
|
|
|
|
if (request.Content.IsMimeMultipartContent() == false)
|
2017-05-26 00:02:32 +10:00
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads");
|
|
|
|
|
//ensure it exists
|
|
|
|
|
Directory.CreateDirectory(root);
|
|
|
|
|
var provider = new MultipartFormDataStreamProvider(root);
|
|
|
|
|
|
2017-06-27 19:55:03 +10:00
|
|
|
var result = await request.Content.ReadAsMultipartAsync(provider);
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
//must have a file
|
|
|
|
|
if (result.FileData.Count == 0)
|
|
|
|
|
{
|
2017-06-27 19:55:03 +10:00
|
|
|
return request.CreateResponse(HttpStatusCode.NotFound);
|
2017-05-26 00:02:32 +10:00
|
|
|
}
|
2017-06-20 14:33:24 +10:00
|
|
|
|
2017-06-27 19:55:03 +10:00
|
|
|
var user = userService.GetUserById(id);
|
2017-05-26 00:02:32 +10:00
|
|
|
if (user == null)
|
2017-06-27 19:55:03 +10:00
|
|
|
return request.CreateResponse(HttpStatusCode.NotFound);
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
var tempFiles = new PostedFiles();
|
|
|
|
|
|
|
|
|
|
if (result.FileData.Count > 1)
|
2017-06-27 19:55:03 +10:00
|
|
|
return request.CreateValidationErrorResponse("The request was not formatted correctly, only one file can be attached to the request");
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
//get the file info
|
|
|
|
|
var file = result.FileData[0];
|
|
|
|
|
var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd();
|
|
|
|
|
var safeFileName = fileName.ToSafeFileName();
|
|
|
|
|
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
|
|
|
|
|
|
|
|
|
|
if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false)
|
|
|
|
|
{
|
2017-05-26 02:15:37 +10:00
|
|
|
//generate a path of known data, we don't want this path to be guessable
|
|
|
|
|
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).ToSHA1() + "." + ext;
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
using (var fs = System.IO.File.OpenRead(file.LocalFileName))
|
|
|
|
|
{
|
|
|
|
|
FileSystemProviderManager.Current.MediaFileSystem.AddFile(user.Avatar, fs, true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 19:55:03 +10:00
|
|
|
userService.Save(user);
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
//track the temp file so the cleanup filter removes it
|
|
|
|
|
tempFiles.UploadedFiles.Add(new ContentItemFile
|
2017-06-20 14:33:24 +10:00
|
|
|
{
|
2017-05-26 00:02:32 +10:00
|
|
|
TempFilePath = file.LocalFileName
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-07 22:24:18 +10:00
|
|
|
return request.CreateResponse(HttpStatusCode.OK, user.GetUserAvatarUrls(staticCache));
|
2017-05-25 12:17:32 +10:00
|
|
|
}
|
|
|
|
|
|
2017-07-25 20:51:58 +10:00
|
|
|
[AppendUserModifiedHeader("id")]
|
2017-05-26 02:15:37 +10:00
|
|
|
public HttpResponseMessage PostClearAvatar(int id)
|
2017-05-25 12:17:32 +10:00
|
|
|
{
|
2017-05-26 00:02:32 +10:00
|
|
|
var found = Services.UserService.GetUserById(id);
|
|
|
|
|
if (found == null)
|
2017-05-26 02:15:37 +10:00
|
|
|
return Request.CreateResponse(HttpStatusCode.NotFound);
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
var filePath = found.Avatar;
|
|
|
|
|
|
2017-07-28 16:02:46 +10:00
|
|
|
//if the filePath is already null it will mean that the user doesn't have a custom avatar and their gravatar is currently
|
|
|
|
|
//being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value
|
|
|
|
|
//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";
|
|
|
|
|
}
|
2017-05-26 00:02:32 +10:00
|
|
|
|
|
|
|
|
Services.UserService.Save(found);
|
|
|
|
|
|
2017-07-28 16:02:46 +10:00
|
|
|
if (filePath.IsNullOrWhiteSpace() == false)
|
|
|
|
|
{
|
|
|
|
|
if (FileSystemProviderManager.Current.MediaFileSystem.FileExists(filePath))
|
|
|
|
|
FileSystemProviderManager.Current.MediaFileSystem.DeleteFile(filePath);
|
|
|
|
|
}
|
2017-05-26 00:02:32 +10:00
|
|
|
|
2017-09-07 22:24:18 +10:00
|
|
|
return Request.CreateResponse(HttpStatusCode.OK, found.GetUserAvatarUrls(ApplicationContext.ApplicationCache.StaticCache));
|
2017-05-25 12:17:32 +10:00
|
|
|
}
|
|
|
|
|
|
2017-05-11 13:11:41 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a user by Id
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
/// <returns></returns>
|
2018-01-02 17:28:16 +11:00
|
|
|
[OutgoingEditorModelEvent]
|
2017-05-11 13:11:41 +10:00
|
|
|
public UserDisplay GetById(int id)
|
|
|
|
|
{
|
|
|
|
|
var user = Services.UserService.GetUserById(id);
|
|
|
|
|
if (user == null)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
}
|
2017-09-08 15:11:39 +10:00
|
|
|
var result = Mapper.Map<IUser, UserDisplay>(user);
|
|
|
|
|
return result;
|
2017-05-11 13:11:41 +10:00
|
|
|
}
|
|
|
|
|
|
2017-05-23 13:18:29 +10: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>
|
2017-06-29 12:38:33 +10:00
|
|
|
/// <param name="userStates"></param>
|
2017-05-23 13:18:29 +10:00
|
|
|
/// <param name="filter"></param>
|
|
|
|
|
/// <returns></returns>
|
2017-06-15 16:47:51 +02:00
|
|
|
public PagedUserResult GetPagedUsers(
|
2017-05-11 13:11:41 +10:00
|
|
|
int pageNumber = 1,
|
2017-05-19 20:17:50 +10:00
|
|
|
int pageSize = 10,
|
2017-05-17 22:58:32 +10:00
|
|
|
string orderBy = "username",
|
2017-05-11 13:11:41 +10:00
|
|
|
Direction orderDirection = Direction.Ascending,
|
2017-05-19 20:17:50 +10:00
|
|
|
[FromUri]string[] userGroups = null,
|
2017-06-29 12:38:33 +10:00
|
|
|
[FromUri]UserState[] userStates = null,
|
2017-05-11 13:11:41 +10:00
|
|
|
string filter = "")
|
|
|
|
|
{
|
2017-08-21 16:53:58 +10: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
|
|
|
|
|
|
2017-08-22 17:27:16 +10:00
|
|
|
var excludeUserGroups = new string[0];
|
2017-08-21 16:53:58 +10:00
|
|
|
var isAdmin = Security.CurrentUser.IsAdmin();
|
|
|
|
|
if (isAdmin == false)
|
|
|
|
|
{
|
2017-08-22 17:27:16 +10:00
|
|
|
//this user is not an admin so in that case we need to exlude all admin users
|
|
|
|
|
excludeUserGroups = new[] {Constants.Security.AdminGroupAlias};
|
2017-08-21 16:53:58 +10:00
|
|
|
}
|
|
|
|
|
|
2017-08-22 17:55:28 +10:00
|
|
|
var filterQuery = Query<IUser>.Builder;
|
|
|
|
|
|
|
|
|
|
//if the current user is not the administrator, then don't include this in the results.
|
|
|
|
|
var isAdminUser = Security.CurrentUser.Id == 0;
|
|
|
|
|
if (isAdminUser == false)
|
|
|
|
|
{
|
|
|
|
|
filterQuery.Where(x => x.Id != 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filter.IsNullOrWhiteSpace() == false)
|
|
|
|
|
{
|
|
|
|
|
filterQuery.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-17 22:58:32 +10:00
|
|
|
long pageIndex = pageNumber - 1;
|
|
|
|
|
long total;
|
2017-08-22 17:55:28 +10:00
|
|
|
var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, excludeUserGroups, filterQuery);
|
2017-07-03 19:27:48 +10:00
|
|
|
|
2017-06-15 16:47:51 +02:00
|
|
|
var paged = new PagedUserResult(total, pageNumber, pageSize)
|
2017-05-11 13:11:41 +10:00
|
|
|
{
|
2017-06-29 13:13:32 +10:00
|
|
|
Items = Mapper.Map<IEnumerable<UserBasic>>(result),
|
2017-06-15 16:47:51 +02:00
|
|
|
UserStates = Services.UserService.GetUserStates()
|
2017-05-11 13:11:41 +10:00
|
|
|
};
|
2017-06-15 16:47:51 +02:00
|
|
|
|
|
|
|
|
return paged;
|
2017-05-11 13:11:41 +10:00
|
|
|
}
|
|
|
|
|
|
2017-05-25 02:03:41 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new user
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="userSave"></param>
|
|
|
|
|
/// <returns></returns>
|
2017-06-15 15:59:29 +02:00
|
|
|
public async Task<UserDisplay> PostCreateUser(UserInvite userSave)
|
2017-05-25 02:03:41 +10:00
|
|
|
{
|
|
|
|
|
if (userSave == null) throw new ArgumentNullException("userSave");
|
|
|
|
|
|
|
|
|
|
if (ModelState.IsValid == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
}
|
2017-09-05 23:31:26 +10:00
|
|
|
|
|
|
|
|
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
|
2017-05-25 02:03:41 +10:00
|
|
|
{
|
2017-09-05 23:31:26 +10:00
|
|
|
//ensure they are the same if we're using it
|
|
|
|
|
userSave.Username = userSave.Email;
|
2017-05-25 02:03:41 +10:00
|
|
|
}
|
2017-09-05 23:31:26 +10:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//first validate the username if were showing it
|
|
|
|
|
CheckUniqueUsername(userSave.Username, null);
|
|
|
|
|
}
|
|
|
|
|
CheckUniqueEmail(userSave.Email, null);
|
2017-05-25 02:03:41 +10:00
|
|
|
|
2017-08-22 12:47:17 +10:00
|
|
|
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
2017-08-22 16:09:06 +10:00
|
|
|
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, null, null, null, userSave.UserGroups);
|
2017-08-22 12:47:17 +10:00
|
|
|
if (canSaveUser == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 15:59:29 +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
|
2017-09-05 23:31:26 +10:00
|
|
|
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
|
2017-07-20 22:02:32 +10:00
|
|
|
identityUser.Name = userSave.Name;
|
|
|
|
|
|
2017-06-27 15:30:32 +10:00
|
|
|
var created = await UserManager.CreateAsync(identityUser);
|
2017-06-15 15:59:29 +02:00
|
|
|
if (created.Succeeded == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(
|
|
|
|
|
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 15:30:32 +10:00
|
|
|
//we need to generate a password, however we can only do that if the user manager has a password validator that
|
|
|
|
|
//we can read values from
|
|
|
|
|
var passwordValidator = UserManager.PasswordValidator as PasswordValidator;
|
|
|
|
|
var resetPassword = string.Empty;
|
|
|
|
|
if (passwordValidator != null)
|
|
|
|
|
{
|
|
|
|
|
var password = UserManager.GeneratePassword();
|
|
|
|
|
|
|
|
|
|
var result = await UserManager.AddPasswordAsync(identityUser.Id, password);
|
|
|
|
|
if (result.Succeeded == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(
|
|
|
|
|
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
|
|
|
|
|
}
|
|
|
|
|
resetPassword = password;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 15:59:29 +02:00
|
|
|
//now re-look the user back up which will now exist
|
|
|
|
|
var user = Services.UserService.GetByEmail(userSave.Email);
|
|
|
|
|
|
|
|
|
|
//map the save info over onto the user
|
|
|
|
|
user = Mapper.Map(userSave, user);
|
|
|
|
|
|
|
|
|
|
//since the back office user is creating this user, they will be set to approved
|
|
|
|
|
user.IsApproved = true;
|
2017-05-25 02:03:41 +10:00
|
|
|
|
|
|
|
|
Services.UserService.Save(user);
|
|
|
|
|
|
2017-06-27 15:30:32 +10:00
|
|
|
var display = Mapper.Map<UserDisplay>(user);
|
|
|
|
|
display.ResetPasswordValue = resetPassword;
|
|
|
|
|
return display;
|
2017-05-25 02:03:41 +10:00
|
|
|
}
|
|
|
|
|
|
2017-05-24 19:01:01 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Invites a user
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="userSave"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// This will email the user an invite and generate a token that will be validated in the email
|
|
|
|
|
/// </remarks>
|
2017-05-29 09:36:07 +02:00
|
|
|
public async Task<UserDisplay> PostInviteUser(UserInvite userSave)
|
2017-05-24 19:01:01 +10:00
|
|
|
{
|
|
|
|
|
if (userSave == null) throw new ArgumentNullException("userSave");
|
|
|
|
|
|
2017-06-15 15:59:29 +02:00
|
|
|
if (userSave.Message.IsNullOrWhiteSpace())
|
2017-06-23 16:38:39 +10:00
|
|
|
ModelState.AddModelError("Message", "Message cannot be empty");
|
2017-06-15 15:59:29 +02:00
|
|
|
|
2017-05-24 19:01:01 +10:00
|
|
|
if (ModelState.IsValid == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
}
|
2017-09-05 18:52:03 +10:00
|
|
|
|
|
|
|
|
if (EmailSender.CanSendRequiredEmail == false)
|
2017-05-24 19:01:01 +10:00
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(
|
|
|
|
|
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
|
|
|
|
|
}
|
2017-06-20 14:33:24 +10:00
|
|
|
|
2017-09-05 23:31:26 +10:00
|
|
|
IUser user;
|
|
|
|
|
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
|
2017-05-24 19:01:01 +10:00
|
|
|
{
|
2017-09-05 23:31:26 +10:00
|
|
|
//ensure it's the same
|
|
|
|
|
userSave.Username = userSave.Email;
|
2017-05-24 19:01:01 +10:00
|
|
|
}
|
2017-09-05 23:31:26 +10:00
|
|
|
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);
|
|
|
|
|
|
2017-08-22 12:47:17 +10:00
|
|
|
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
2017-08-22 16:09:06 +10:00
|
|
|
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups);
|
2017-08-22 12:47:17 +10:00
|
|
|
if (canSaveUser == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 00:46:23 +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
|
2017-09-05 23:31:26 +10:00
|
|
|
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
|
2017-07-20 22:02:32 +10:00
|
|
|
identityUser.Name = userSave.Name;
|
|
|
|
|
|
|
|
|
|
var created = await UserManager.CreateAsync(identityUser);
|
2017-06-15 00:46:23 +02:00
|
|
|
if (created.Succeeded == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(
|
|
|
|
|
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
|
|
|
|
|
}
|
2017-06-13 18:38:16 +02:00
|
|
|
|
2017-06-15 00:46:23 +02:00
|
|
|
//now re-look the user back up
|
|
|
|
|
user = Services.UserService.GetByEmail(userSave.Email);
|
|
|
|
|
}
|
2017-06-13 18:47:20 +02:00
|
|
|
|
2017-06-15 00:46:23 +02:00
|
|
|
//map the save info over onto the user
|
|
|
|
|
user = Mapper.Map(userSave, user);
|
2017-06-20 14:33:24 +10:00
|
|
|
|
2017-06-15 16:47:51 +02:00
|
|
|
//ensure the invited date is set
|
|
|
|
|
user.InvitedDate = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
//Save the updated user
|
2017-06-20 14:33:24 +10:00
|
|
|
Services.UserService.Save(user);
|
2017-06-14 11:29:15 +02:00
|
|
|
var display = Mapper.Map<UserDisplay>(user);
|
|
|
|
|
|
2017-06-14 16:21:56 +02:00
|
|
|
//send the email
|
|
|
|
|
|
2018-02-22 16:30:41 +10:00
|
|
|
await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message);
|
2017-06-14 11:29:15 +02:00
|
|
|
|
|
|
|
|
return display;
|
|
|
|
|
}
|
2017-09-05 23:31:26 +10:00
|
|
|
|
|
|
|
|
private IUser CheckUniqueEmail(string email, Func<IUser, bool> extraCheck)
|
|
|
|
|
{
|
|
|
|
|
var user = Services.UserService.GetByEmail(email);
|
|
|
|
|
if (user != null && (extraCheck == null || extraCheck(user)))
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError("Email", "A user with the email already exists");
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
}
|
|
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IUser CheckUniqueUsername(string username, Func<IUser, bool> extraCheck)
|
|
|
|
|
{
|
|
|
|
|
var user = Services.UserService.GetByUsername(username);
|
|
|
|
|
if (user != null && (extraCheck == null || extraCheck(user)))
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError(
|
|
|
|
|
UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail ? "Email" : "Username",
|
|
|
|
|
"A user with the username already exists");
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
}
|
|
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-14 16:21:56 +02:00
|
|
|
private HttpContextBase EnsureHttpContext()
|
|
|
|
|
{
|
|
|
|
|
var attempt = this.TryGetHttpContext();
|
|
|
|
|
if (attempt.Success == false)
|
|
|
|
|
throw new InvalidOperationException("This method requires that an HttpContext be active");
|
|
|
|
|
return attempt.Result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-22 16:30:41 +10:00
|
|
|
private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message)
|
2017-06-14 11:29:15 +02:00
|
|
|
{
|
2017-06-20 14:33:24 +10:00
|
|
|
var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id);
|
2017-06-14 16:21:56 +02:00
|
|
|
|
|
|
|
|
var inviteToken = string.Format("{0}{1}{2}",
|
2017-06-14 11:29:15 +02:00
|
|
|
(int)userDisplay.Id,
|
2017-06-13 18:38:16 +02:00
|
|
|
WebUtility.UrlEncode("|"),
|
|
|
|
|
token.ToUrlBase64());
|
|
|
|
|
|
2017-06-14 16:21:56 +02:00
|
|
|
// Get an mvc helper to get the url
|
|
|
|
|
var http = EnsureHttpContext();
|
|
|
|
|
var urlHelper = new UrlHelper(http.Request.RequestContext);
|
|
|
|
|
var action = urlHelper.Action("VerifyInvite", "BackOffice",
|
|
|
|
|
new
|
|
|
|
|
{
|
|
|
|
|
area = GlobalSettings.UmbracoMvcArea,
|
|
|
|
|
invite = inviteToken
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Construct full URL using configured application URL (which will fall back to request)
|
|
|
|
|
var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl);
|
|
|
|
|
var inviteUri = new Uri(applicationUri, action);
|
|
|
|
|
|
2017-06-20 12:25:27 +10:00
|
|
|
var emailSubject = Services.TextService.Localize("user/inviteEmailCopySubject",
|
|
|
|
|
//Ensure the culture of the found user is used for the email!
|
|
|
|
|
UserExtensions.GetUserCulture(to.Language, Services.TextService));
|
|
|
|
|
var emailBody = Services.TextService.Localize("user/inviteEmailCopyFormat",
|
|
|
|
|
//Ensure the culture of the found user is used for the email!
|
|
|
|
|
UserExtensions.GetUserCulture(to.Language, Services.TextService),
|
2018-02-22 16:30:41 +10:00
|
|
|
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
|
2017-05-29 15:13:04 +02:00
|
|
|
|
2017-09-05 18:52:03 +10:00
|
|
|
await UserManager.EmailService.SendAsync(
|
|
|
|
|
//send the special UmbracoEmailMessage which configures it's own sender
|
|
|
|
|
//to allow for events to handle sending the message if no smtp is configured
|
|
|
|
|
new UmbracoEmailMessage(new EmailSender(true))
|
|
|
|
|
{
|
|
|
|
|
Body = emailBody,
|
|
|
|
|
Destination = userDisplay.Email,
|
|
|
|
|
Subject = emailSubject
|
|
|
|
|
});
|
2017-06-20 12:25:27 +10:00
|
|
|
|
2017-05-24 19:01:01 +10:00
|
|
|
}
|
|
|
|
|
|
2017-05-23 13:18:29 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Saves a user
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="userSave"></param>
|
|
|
|
|
/// <returns></returns>
|
2018-01-04 18:32:25 +11:00
|
|
|
[OutgoingEditorModelEvent]
|
2017-07-20 12:53:09 +10:00
|
|
|
public async Task<UserDisplay> PostSaveUser(UserSave userSave)
|
2017-05-11 13:11:41 +10:00
|
|
|
{
|
|
|
|
|
if (userSave == null) throw new ArgumentNullException("userSave");
|
|
|
|
|
|
|
|
|
|
if (ModelState.IsValid == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
}
|
2017-05-25 12:17:32 +10:00
|
|
|
|
2017-05-11 13:11:41 +10:00
|
|
|
var intId = userSave.Id.TryConvertTo<int>();
|
|
|
|
|
if (intId.Success == false)
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
|
|
|
|
var found = Services.UserService.GetUserById(intId.Result);
|
|
|
|
|
if (found == null)
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
2017-08-22 12:39:21 +10:00
|
|
|
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
|
|
|
|
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
2017-08-22 16:09:06 +10:00
|
|
|
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
|
2017-08-22 12:39:21 +10:00
|
|
|
if (canSaveUser == false)
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-25 02:03:41 +10:00
|
|
|
var hasErrors = false;
|
|
|
|
|
|
|
|
|
|
var existing = Services.UserService.GetByEmail(userSave.Email);
|
2017-05-25 12:17:32 +10:00
|
|
|
if (existing != null && existing.Id != userSave.Id)
|
2017-05-25 02:03:41 +10:00
|
|
|
{
|
|
|
|
|
ModelState.AddModelError("Email", "A user with the email already exists");
|
|
|
|
|
hasErrors = true;
|
|
|
|
|
}
|
2017-06-23 16:38:39 +10:00
|
|
|
existing = Services.UserService.GetByUsername(userSave.Username);
|
2017-05-25 12:17:32 +10:00
|
|
|
if (existing != null && existing.Id != userSave.Id)
|
2017-05-25 02:03:41 +10:00
|
|
|
{
|
2017-06-23 16:38:39 +10:00
|
|
|
ModelState.AddModelError("Username", "A user with the username already exists");
|
2017-05-25 02:03:41 +10:00
|
|
|
hasErrors = true;
|
2017-06-20 14:33:24 +10:00
|
|
|
}
|
2017-07-10 12:22:35 +02:00
|
|
|
// 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 = Services.UserService.GetByEmail(userSave.Username);
|
|
|
|
|
if (existing != null && existing.Id != userSave.Id)
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError("Username", "A user using this as their email already exists");
|
|
|
|
|
hasErrors = true;
|
|
|
|
|
}
|
|
|
|
|
existing = Services.UserService.GetByUsername(userSave.Email);
|
|
|
|
|
if (existing != null && existing.Id != userSave.Id)
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError("Email", "A user using this as their username already exists");
|
|
|
|
|
hasErrors = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the found user has his email for username, we want to keep this synced when changing the email.
|
|
|
|
|
// we have already cross-checked above that the email isn't colliding with anything, so we can safely assign it here.
|
2017-09-20 13:34:19 +02:00
|
|
|
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email)
|
2017-07-10 12:22:35 +02:00
|
|
|
{
|
|
|
|
|
userSave.Username = userSave.Email;
|
|
|
|
|
}
|
2017-09-08 13:48:32 +10:00
|
|
|
|
2017-06-23 14:08:58 +10:00
|
|
|
if (userSave.ChangePassword != null)
|
|
|
|
|
{
|
2017-09-15 16:34:51 +02:00
|
|
|
var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
|
2017-07-20 12:53:09 +10:00
|
|
|
|
2018-02-13 00:15:04 +11:00
|
|
|
//this will change the password and raise appropriate events
|
2017-09-04 22:03:03 +10:00
|
|
|
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager);
|
2017-06-23 14:08:58 +10:00
|
|
|
if (passwordChangeResult.Success)
|
|
|
|
|
{
|
2017-06-23 16:38:39 +10:00
|
|
|
//need to re-get the user
|
|
|
|
|
found = Services.UserService.GetUserById(intId.Result);
|
2017-06-23 14:08:58 +10:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hasErrors = true;
|
2017-09-04 22:03:03 +10:00
|
|
|
|
|
|
|
|
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
|
|
|
|
{
|
|
|
|
|
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
|
|
|
|
}
|
2017-06-23 14:08:58 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-25 02:03:41 +10:00
|
|
|
if (hasErrors)
|
|
|
|
|
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
|
|
|
|
|
2017-05-25 12:17:32 +10:00
|
|
|
//merge the save data onto the user
|
|
|
|
|
var user = Mapper.Map(userSave, found);
|
2017-05-25 02:03:41 +10:00
|
|
|
|
|
|
|
|
Services.UserService.Save(user);
|
|
|
|
|
|
2017-05-25 12:17:32 +10:00
|
|
|
var display = Mapper.Map<UserDisplay>(user);
|
2017-09-08 13:48:32 +10:00
|
|
|
|
2017-05-25 12:17:32 +10:00
|
|
|
display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved"));
|
|
|
|
|
return display;
|
2017-06-29 14:39:11 +10:00
|
|
|
}
|
2017-06-20 14:33:24 +10:00
|
|
|
|
2014-10-16 18:45:02 +10:00
|
|
|
/// <summary>
|
2017-05-23 13:18:29 +10:00
|
|
|
/// Disables the users with the given user ids
|
2014-10-16 18:45:02 +10:00
|
|
|
/// </summary>
|
2017-05-23 13:18:29 +10:00
|
|
|
/// <param name="userIds"></param>
|
2017-06-23 14:49:04 +10:00
|
|
|
public HttpResponseMessage PostDisableUsers([FromUri]int[] userIds)
|
2014-10-16 18:45:02 +10:00
|
|
|
{
|
2017-06-23 14:49:04 +10:00
|
|
|
if (userIds.Contains(Security.GetUserId()))
|
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(
|
|
|
|
|
Request.CreateNotificationValidationErrorResponse("The current user cannot disable itself"));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-23 15:23:10 +10:00
|
|
|
var users = Services.UserService.GetUsersById(userIds).ToArray();
|
2017-05-23 13:18:29 +10:00
|
|
|
foreach (var u in users)
|
2014-10-16 18:45:02 +10:00
|
|
|
{
|
2017-05-25 12:17:32 +10:00
|
|
|
u.IsApproved = false;
|
2017-07-10 13:38:39 +02:00
|
|
|
u.InvitedDate = null;
|
2014-10-16 18:45:02 +10:00
|
|
|
}
|
2017-05-23 15:23:10 +10:00
|
|
|
Services.UserService.Save(users);
|
|
|
|
|
|
2017-06-23 14:49:04 +10:00
|
|
|
if (users.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/disableUsersSuccess", new[] {userIds.Length.ToString()}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/disableUserSuccess", new[] { users[0].Name }));
|
2017-05-23 13:18:29 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Enables the users with the given user ids
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="userIds"></param>
|
2017-06-23 14:49:04 +10:00
|
|
|
public HttpResponseMessage PostEnableUsers([FromUri]int[] userIds)
|
2017-05-23 13:18:29 +10:00
|
|
|
{
|
|
|
|
|
var users = Services.UserService.GetUsersById(userIds).ToArray();
|
|
|
|
|
foreach (var u in users)
|
|
|
|
|
{
|
2017-05-25 12:17:32 +10:00
|
|
|
u.IsApproved = true;
|
2017-05-23 13:18:29 +10:00
|
|
|
}
|
|
|
|
|
Services.UserService.Save(users);
|
|
|
|
|
|
2017-06-23 14:49:04 +10:00
|
|
|
if (users.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/enableUsersSuccess", new[] { userIds.Length.ToString() }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name }));
|
2017-08-21 16:53:58 +10:00
|
|
|
}
|
|
|
|
|
|
2017-08-10 13:39:33 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Unlocks the users with the given user ids
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="userIds"></param>
|
2017-08-15 12:31:32 +10:00
|
|
|
public async Task<HttpResponseMessage> PostUnlockUsers([FromUri]int[] userIds)
|
2017-08-10 13:39:33 +02:00
|
|
|
{
|
2017-08-15 12:31:32 +10:00
|
|
|
if (userIds.Length <= 0)
|
|
|
|
|
return Request.CreateResponse(HttpStatusCode.OK);
|
|
|
|
|
|
|
|
|
|
if (userIds.Length == 1)
|
2017-08-10 13:39:33 +02:00
|
|
|
{
|
2017-08-15 12:31:32 +10:00
|
|
|
var unlockResult = await UserManager.SetLockoutEndDateAsync(userIds[0], DateTimeOffset.Now);
|
|
|
|
|
if (unlockResult.Succeeded == false)
|
|
|
|
|
{
|
|
|
|
|
return Request.CreateValidationErrorResponse(
|
|
|
|
|
string.Format("Could not unlock for user {0} - error {1}", userIds[0], unlockResult.Errors.First()));
|
|
|
|
|
}
|
|
|
|
|
var user = await UserManager.FindByIdAsync(userIds[0]);
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name }));
|
2017-08-10 13:39:33 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-15 12:31:32 +10:00
|
|
|
foreach (var u in userIds)
|
2017-08-10 13:39:33 +02:00
|
|
|
{
|
2017-08-15 12:31:32 +10:00
|
|
|
var unlockResult = await UserManager.SetLockoutEndDateAsync(u, DateTimeOffset.Now);
|
|
|
|
|
if (unlockResult.Succeeded == false)
|
|
|
|
|
{
|
|
|
|
|
return Request.CreateValidationErrorResponse(
|
|
|
|
|
string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.First()));
|
|
|
|
|
}
|
2017-08-10 13:39:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
2017-08-21 16:53:58 +10:00
|
|
|
Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] { userIds.Length.ToString() }));
|
2014-10-16 18:45:02 +10:00
|
|
|
}
|
2017-06-15 16:47:51 +02:00
|
|
|
|
2017-07-12 11:31:34 +02:00
|
|
|
public HttpResponseMessage PostSetUserGroupsOnUsers([FromUri]string[] userGroupAliases, [FromUri]int[] userIds)
|
|
|
|
|
{
|
|
|
|
|
var users = Services.UserService.GetUsersById(userIds).ToArray();
|
2017-07-12 13:56:18 +02:00
|
|
|
var userGroups = Services.UserService.GetUserGroupsByAlias(userGroupAliases).Select(x => x.ToReadOnlyGroup()).ToArray();
|
2017-07-12 11:31:34 +02:00
|
|
|
foreach (var u in users)
|
|
|
|
|
{
|
|
|
|
|
u.ClearGroups();
|
|
|
|
|
foreach (var userGroup in userGroups)
|
|
|
|
|
{
|
|
|
|
|
u.AddGroup(userGroup);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Services.UserService.Save(users);
|
|
|
|
|
return Request.CreateNotificationSuccessResponse(
|
|
|
|
|
Services.TextService.Localize("speechBubbles/setUserGroupOnUsersSuccess"));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 13:13:32 +10:00
|
|
|
public class PagedUserResult : PagedResult<UserBasic>
|
2017-06-15 16:47:51 +02:00
|
|
|
{
|
|
|
|
|
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-08-22 12:39:21 +10:00
|
|
|
|
2017-08-21 16:53:58 +10:00
|
|
|
}
|
2018-02-13 00:15:04 +11:00
|
|
|
}
|