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