2018-06-29 19:52:40 +02:00
using System ;
using System.Linq ;
using System.Net ;
using System.Net.Http ;
using System.Collections.Generic ;
using System.Security.Principal ;
using System.Threading.Tasks ;
using System.Web ;
using System.Web.Http ;
using System.Web.Mvc ;
2020-03-16 13:53:03 +00:00
using Microsoft.AspNetCore.Identity ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core ;
2018-07-06 17:36:33 +02:00
using Umbraco.Core.Cache ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core.Models ;
using Umbraco.Core.Services ;
using Umbraco.Web.Models ;
using Umbraco.Web.Models.ContentEditing ;
using Umbraco.Web.Mvc ;
using Umbraco.Web.Security ;
using Umbraco.Web.WebApi ;
using Umbraco.Web.WebApi.Filters ;
using Umbraco.Core.Configuration ;
using Umbraco.Core.Logging ;
2018-07-06 17:36:33 +02:00
using Umbraco.Core.Persistence ;
2018-06-29 19:52:40 +02:00
using IUser = Umbraco . Core . Models . Membership . IUser ;
2019-11-25 21:20:00 +11:00
using Umbraco.Core.Mapping ;
2020-01-07 13:50:38 +01:00
using Umbraco.Web.Models.Identity ;
2020-01-21 17:03:46 -08:00
using Umbraco.Core.Configuration.UmbracoSettings ;
2020-02-10 11:23:23 +01:00
using Umbraco.Core.IO ;
2020-02-14 13:04:49 +01:00
using Umbraco.Web.Routing ;
2018-06-29 19:52:40 +02:00
namespace Umbraco.Web.Editors
{
/// <summary>
/// The API controller used for editing content
/// </summary>
[PluginController("UmbracoApi")]
[ValidationFilter]
[AngularJsonOnlyConfiguration]
[IsBackOffice]
public class AuthenticationController : UmbracoApiController
{
2020-03-16 13:53:03 +00:00
private BackOfficeUserManager2 < BackOfficeIdentityUser > _userManager ;
private BackOfficeSignInManager2 _signInManager ;
2019-11-25 21:20:00 +11:00
private readonly IUserPasswordConfiguration _passwordConfiguration ;
2020-02-10 11:23:23 +01:00
private readonly IRuntimeState _runtimeState ;
2020-01-21 17:03:46 -08:00
private readonly IUmbracoSettingsSection _umbracoSettingsSection ;
2020-02-10 11:23:23 +01:00
private readonly IIOHelper _ioHelper ;
2018-06-29 19:52:40 +02:00
2020-02-14 13:04:49 +01:00
public AuthenticationController (
IUserPasswordConfiguration passwordConfiguration ,
IGlobalSettings globalSettings ,
IUmbracoContextAccessor umbracoContextAccessor ,
ISqlContext sqlContext ,
ServiceContext services ,
AppCaches appCaches ,
IProfilingLogger logger ,
IRuntimeState runtimeState ,
UmbracoHelper umbracoHelper ,
UmbracoMapper umbracoMapper ,
IUmbracoSettingsSection umbracoSettingsSection ,
IIOHelper ioHelper ,
IPublishedUrlProvider publishedUrlProvider )
: base ( globalSettings , umbracoContextAccessor , sqlContext , services , appCaches , logger , runtimeState , umbracoHelper , umbracoMapper , publishedUrlProvider )
2019-01-31 15:09:31 +11:00
{
2019-11-25 23:10:54 +11:00
_passwordConfiguration = passwordConfiguration ? ? throw new ArgumentNullException ( nameof ( passwordConfiguration ) ) ;
2020-02-10 11:23:23 +01:00
_runtimeState = runtimeState ? ? throw new ArgumentNullException ( nameof ( runtimeState ) ) ;
2020-01-21 17:03:46 -08:00
_umbracoSettingsSection = umbracoSettingsSection ? ? throw new ArgumentNullException ( nameof ( umbracoSettingsSection ) ) ;
2020-02-10 11:23:23 +01:00
_ioHelper = ioHelper ? ? throw new ArgumentNullException ( nameof ( ioHelper ) ) ;
2019-01-31 15:09:31 +11:00
}
2018-11-28 16:19:50 +01:00
2020-03-16 13:53:03 +00:00
protected BackOfficeUserManager2 < BackOfficeIdentityUser > UserManager = > _userManager
? ? ( _userManager = TryGetOwinContext ( ) . Result . GetBackOfficeUserManager2 ( ) ) ;
2018-07-06 17:36:33 +02:00
2020-03-16 13:53:03 +00:00
protected BackOfficeSignInManager2 SignInManager = > _signInManager
? ? ( _signInManager = TryGetOwinContext ( ) . Result . GetBackOfficeSignInManager2 ( ) ) ;
2018-07-06 17:36:33 +02:00
2018-06-29 19:52:40 +02:00
/// <summary>
/// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog
/// </summary>
/// <returns></returns>
[WebApi.UmbracoAuthorize(requireApproval: false)]
2019-12-03 15:28:55 +11:00
public IDictionary < string , object > GetPasswordConfig ( int userId )
2018-06-29 19:52:40 +02:00
{
2019-12-03 15:28:55 +11:00
return _passwordConfiguration . GetConfiguration ( userId ! = UmbracoContext . Security . CurrentUser . Id ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Checks if a valid token is specified for an invited user and if so logs the user in and returns the user object
/// </summary>
/// <param name="id"></param>
/// <param name="token"></param>
/// <returns></returns>
/// <remarks>
/// This will also update the security stamp for the user so it can only be used once
/// </remarks>
[ValidateAngularAntiForgeryToken]
public async Task < UserDisplay > PostVerifyInvite ( [ FromUri ] int id , [ FromUri ] string token )
{
if ( string . IsNullOrWhiteSpace ( token ) )
throw new HttpResponseException ( Request . CreateResponse ( HttpStatusCode . NotFound ) ) ;
var decoded = token . FromUrlBase64 ( ) ;
if ( decoded . IsNullOrWhiteSpace ( ) )
throw new HttpResponseException ( Request . CreateResponse ( HttpStatusCode . NotFound ) ) ;
2020-03-16 13:53:03 +00:00
var identityUser = await UserManager . FindByIdAsync ( id . ToString ( ) ) ;
2018-06-29 19:52:40 +02:00
if ( identityUser = = null )
throw new HttpResponseException ( Request . CreateResponse ( HttpStatusCode . NotFound ) ) ;
2020-03-16 13:53:03 +00:00
var result = await UserManager . ConfirmEmailAsync ( identityUser , decoded ) ;
2018-06-29 19:52:40 +02:00
if ( result . Succeeded = = false )
{
throw new HttpResponseException ( Request . CreateNotificationValidationErrorResponse ( string . Join ( ", " , result . Errors ) ) ) ;
}
Request . TryGetOwinContext ( ) . Result . Authentication . SignOut (
Core . Constants . Security . BackOfficeAuthenticationType ,
Core . Constants . Security . BackOfficeExternalAuthenticationType ) ;
await SignInManager . SignInAsync ( identityUser , false , false ) ;
var user = Services . UserService . GetUserById ( id ) ;
return Mapper . Map < UserDisplay > ( user ) ;
}
[WebApi.UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
public async Task < HttpResponseMessage > PostUnLinkLogin ( UnLinkLoginModel unlinkLoginModel )
{
2020-03-16 13:53:03 +00:00
var user = await UserManager . FindByIdAsync ( User . Identity . GetUserId ( ) ) ;
2018-06-29 19:52:40 +02:00
var result = await UserManager . RemoveLoginAsync (
2020-03-16 13:53:03 +00:00
user ,
unlinkLoginModel . LoginProvider ,
unlinkLoginModel . ProviderKey ) ;
2018-06-29 19:52:40 +02:00
if ( result . Succeeded )
{
await SignInManager . SignInAsync ( user , isPersistent : true , rememberBrowser : false ) ;
return Request . CreateResponse ( HttpStatusCode . OK ) ;
}
else
{
AddModelErrors ( result ) ;
return Request . CreateValidationErrorResponse ( ModelState ) ;
}
}
/// <summary>
/// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest)
/// </summary>
/// <returns></returns>
[System.Web.Http.HttpGet]
public bool IsAuthenticated ( )
{
var attempt = UmbracoContext . Security . AuthorizeRequest ( ) ;
if ( attempt = = ValidateRequestAttempt . Success )
{
return true ;
}
return false ;
}
/// <summary>
/// Returns the currently logged in Umbraco user
/// </summary>
/// <returns></returns>
/// <remarks>
/// We have the attribute [SetAngularAntiForgeryTokens] applied because this method is called initially to determine if the user
/// is valid before the login screen is displayed. The Auth cookie can be persisted for up to a day but the csrf cookies are only session
/// cookies which means that the auth cookie could be valid but the csrf cookies are no longer there, in that case we need to re-set the csrf cookies.
/// </remarks>
[WebApi.UmbracoAuthorize]
[SetAngularAntiForgeryTokens]
[CheckIfUserTicketDataIsStale]
public UserDetail GetCurrentUser ( )
{
var user = UmbracoContext . Security . CurrentUser ;
var result = Mapper . Map < UserDetail > ( user ) ;
var httpContextAttempt = TryGetHttpContext ( ) ;
if ( httpContextAttempt . Success )
{
//set their remaining seconds
result . SecondsUntilTimeout = httpContextAttempt . Result . GetRemainingAuthSeconds ( ) ;
}
return result ;
}
/// <summary>
/// When a user is invited they are not approved but we need to resolve the partially logged on (non approved)
/// user.
/// </summary>
/// <returns></returns>
/// <remarks>
/// We cannot user GetCurrentUser since that requires they are approved, this is the same as GetCurrentUser but doesn't require them to be approved
/// </remarks>
[WebApi.UmbracoAuthorize(requireApproval: false)]
[SetAngularAntiForgeryTokens]
public UserDetail GetCurrentInvitedUser ( )
{
var user = UmbracoContext . Security . CurrentUser ;
if ( user . IsApproved )
{
2019-01-26 10:52:19 -05:00
// if they are approved, than they are no longer invited and we can return an error
2018-06-29 19:52:40 +02:00
throw new HttpResponseException ( Request . CreateUserNoAccessResponse ( ) ) ;
}
var result = Mapper . Map < UserDetail > ( user ) ;
var httpContextAttempt = TryGetHttpContext ( ) ;
if ( httpContextAttempt . Success )
{
2019-01-26 10:52:19 -05:00
// set their remaining seconds
2018-06-29 19:52:40 +02:00
result . SecondsUntilTimeout = httpContextAttempt . Result . GetRemainingAuthSeconds ( ) ;
}
return result ;
}
2019-01-26 10:52:19 -05:00
// TODO: This should be on the CurrentUserController?
2018-06-29 19:52:40 +02:00
[WebApi.UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
public async Task < Dictionary < string , string > > GetCurrentUserLinkedLogins ( )
{
2020-03-16 13:53:03 +00:00
var identityUser = await UserManager . FindByIdAsync ( UmbracoContext . Security . GetUserId ( ) . ResultOr ( 0 ) . ToString ( ) ) ;
2018-06-29 19:52:40 +02:00
return identityUser . Logins . ToDictionary ( x = > x . LoginProvider , x = > x . ProviderKey ) ;
}
/// <summary>
/// Logs a user in
/// </summary>
/// <returns></returns>
[SetAngularAntiForgeryTokens]
public async Task < HttpResponseMessage > PostLogin ( LoginModel loginModel )
{
var http = EnsureHttpContext ( ) ;
var owinContext = TryGetOwinContext ( ) . Result ;
2019-01-26 10:52:19 -05:00
// Sign the user in with username/password, this also gives a chance for developers to
// custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker
2018-06-29 19:52:40 +02:00
var result = await SignInManager . PasswordSignInAsync (
loginModel . Username , loginModel . Password , isPersistent : true , shouldLockout : true ) ;
2020-03-16 13:53:03 +00:00
if ( result . Succeeded )
2018-06-29 19:52:40 +02:00
{
2020-03-16 13:53:03 +00:00
// get the user
var user = Services . UserService . GetByUsername ( loginModel . Username ) ;
UserManager . RaiseLoginSuccessEvent ( user . Id ) ;
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
return SetPrincipalAndReturnUserDetail ( user , owinContext . Request . User ) ;
}
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
if ( result . RequiresTwoFactor )
{
var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions ;
if ( twofactorOptions = = null )
{
throw new HttpResponseException (
Request . CreateErrorResponse (
HttpStatusCode . BadRequest ,
"UserManager does not implement " + typeof ( IUmbracoBackOfficeTwoFactorOptions ) ) ) ;
}
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
var twofactorView = twofactorOptions . GetTwoFactorView (
owinContext ,
UmbracoContext ,
loginModel . Username ) ;
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
if ( twofactorView . IsNullOrWhiteSpace ( ) )
{
throw new HttpResponseException (
Request . CreateErrorResponse (
HttpStatusCode . BadRequest ,
typeof ( IUmbracoBackOfficeTwoFactorOptions ) + ".GetTwoFactorView returned an empty string" ) ) ;
}
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
var attemptedUser = Services . UserService . GetByUsername ( loginModel . Username ) ;
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
// create a with information to display a custom two factor send code view
var verifyResponse = Request . CreateResponse ( HttpStatusCode . PaymentRequired , new
{
twoFactorView = twofactorView ,
userId = attemptedUser . Id
} ) ;
2018-06-29 19:52:40 +02:00
2020-03-16 13:53:03 +00:00
UserManager . RaiseLoginRequiresVerificationEvent ( attemptedUser . Id ) ;
return verifyResponse ;
2018-06-29 19:52:40 +02:00
}
2020-03-16 13:53:03 +00:00
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException ( HttpStatusCode . BadRequest ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Processes a password reset request. Looks for a match on the provided email address
/// and if found sends an email with a link to reset it
/// </summary>
/// <returns></returns>
[SetAngularAntiForgeryTokens]
public async Task < HttpResponseMessage > PostRequestPasswordReset ( RequestPasswordResetModel model )
{
// If this feature is switched off in configuration the UI will be amended to not make the request to reset password available.
// So this is just a server-side secondary check.
2020-01-21 17:03:46 -08:00
if ( _umbracoSettingsSection . Security . AllowPasswordReset = = false )
2018-06-29 19:52:40 +02:00
{
throw new HttpResponseException ( HttpStatusCode . BadRequest ) ;
}
2020-03-16 13:53:03 +00:00
var identityUser = await UserManager . FindByEmailAsync ( model . Email ) ;
2018-06-29 19:52:40 +02:00
if ( identityUser ! = null )
{
var user = Services . UserService . GetByEmail ( model . Email ) ;
if ( user ! = null )
{
2020-03-16 13:53:03 +00:00
var code = await UserManager . GeneratePasswordResetTokenAsync ( identityUser ) ;
2018-06-29 19:52:40 +02:00
var callbackUrl = ConstructCallbackUrl ( identityUser . Id , code ) ;
var message = Services . TextService . Localize ( "resetPasswordEmailCopyFormat" ,
2019-01-26 10:52:19 -05:00
// Ensure the culture of the found user is used for the email!
2019-11-14 16:59:43 +11:00
UmbracoUserExtensions . GetUserCulture ( identityUser . Culture , Services . TextService , GlobalSettings ) ,
2018-06-29 19:52:40 +02:00
new [ ] { identityUser . UserName , callbackUrl } ) ;
2020-03-16 13:53:03 +00:00
// TODO: SB: SendEmailAsync
/ * await UserManager . SendEmailAsync ( identityUser . Id ,
2018-06-29 19:52:40 +02:00
Services . TextService . Localize ( "login/resetPasswordEmailCopySubject" ,
2019-01-26 10:52:19 -05:00
// Ensure the culture of the found user is used for the email!
2019-11-14 16:59:43 +11:00
UmbracoUserExtensions . GetUserCulture ( identityUser . Culture , Services . TextService , GlobalSettings ) ) ,
2020-03-16 13:53:03 +00:00
message ) ; * /
2018-06-29 19:52:40 +02:00
UserManager . RaiseForgotPasswordRequestedEvent ( user . Id ) ;
}
}
return Request . CreateResponse ( HttpStatusCode . OK ) ;
}
/// <summary>
2019-01-26 10:52:19 -05:00
/// Used to retrieve the 2FA providers for code submission
2018-06-29 19:52:40 +02:00
/// </summary>
/// <returns></returns>
[SetAngularAntiForgeryTokens]
public async Task < IEnumerable < string > > Get2FAProviders ( )
{
var userId = await SignInManager . GetVerifiedUserIdAsync ( ) ;
2019-05-06 08:22:03 +02:00
if ( userId = = int . MinValue )
2018-06-29 19:52:40 +02:00
{
Logger . Warn < AuthenticationController > ( "Get2FAProviders :: No verified user found, returning 404" ) ;
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
2020-03-16 13:53:03 +00:00
var user = await UserManager . FindByIdAsync ( userId . ToString ( ) ) ;
var userFactors = await UserManager . GetValidTwoFactorProvidersAsync ( user ) ;
2018-06-29 19:52:40 +02:00
return userFactors ;
}
[SetAngularAntiForgeryTokens]
public async Task < IHttpActionResult > PostSend2FACode ( [ FromBody ] string provider )
{
if ( provider . IsNullOrWhiteSpace ( ) )
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
var userId = await SignInManager . GetVerifiedUserIdAsync ( ) ;
2019-05-06 08:22:03 +02:00
if ( userId = = int . MinValue )
2018-06-29 19:52:40 +02:00
{
Logger . Warn < AuthenticationController > ( "Get2FAProviders :: No verified user found, returning 404" ) ;
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
// Generate the token and send it
if ( await SignInManager . SendTwoFactorCodeAsync ( provider ) = = false )
{
return BadRequest ( "Invalid code" ) ;
}
return Ok ( ) ;
}
[SetAngularAntiForgeryTokens]
public async Task < HttpResponseMessage > PostVerify2FACode ( Verify2FACodeModel model )
{
if ( ModelState . IsValid = = false )
{
return Request . CreateValidationErrorResponse ( ModelState ) ;
}
var userName = await SignInManager . GetVerifiedUserNameAsync ( ) ;
if ( userName = = null )
{
Logger . Warn < AuthenticationController > ( "Get2FAProviders :: No verified user found, returning 404" ) ;
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
var result = await SignInManager . TwoFactorSignInAsync ( model . Provider , model . Code , isPersistent : true , rememberBrowser : false ) ;
var owinContext = TryGetOwinContext ( ) . Result ;
var user = Services . UserService . GetByUsername ( userName ) ;
2020-03-16 13:53:03 +00:00
if ( result . Succeeded )
2018-06-29 19:52:40 +02:00
{
2020-03-16 13:53:03 +00:00
UserManager . RaiseLoginSuccessEvent ( user . Id ) ;
return SetPrincipalAndReturnUserDetail ( user , owinContext . Request . User ) ;
2018-06-29 19:52:40 +02:00
}
2020-03-16 13:53:03 +00:00
if ( result . IsLockedOut )
{
UserManager . RaiseAccountLockedEvent ( user . Id ) ;
return Request . CreateValidationErrorResponse ( "User is locked out" ) ;
}
return Request . CreateValidationErrorResponse ( "Invalid code" ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Processes a set password request. Validates the request and sets a new password.
/// </summary>
/// <returns></returns>
[SetAngularAntiForgeryTokens]
public async Task < HttpResponseMessage > PostSetPassword ( SetPasswordModel model )
{
2020-03-16 13:53:03 +00:00
var identityUser = await UserManager . FindByIdAsync ( model . UserId . ToString ( ) ) ;
var result = await UserManager . ResetPasswordAsync ( identityUser , model . ResetCode , model . Password ) ;
2018-06-29 19:52:40 +02:00
if ( result . Succeeded )
{
2020-03-16 13:53:03 +00:00
var lockedOut = await UserManager . IsLockedOutAsync ( identityUser ) ;
2018-06-29 19:52:40 +02:00
if ( lockedOut )
{
2018-08-14 22:36:47 +01:00
Logger . Info < AuthenticationController > ( "User {UserId} is currently locked out, unlocking and resetting AccessFailedCount" , model . UserId ) ;
2018-06-29 19:52:40 +02:00
2019-01-26 10:52:19 -05:00
//// var user = await UserManager.FindByIdAsync(model.UserId);
2020-03-16 13:53:03 +00:00
var unlockResult = await UserManager . SetLockoutEndDateAsync ( identityUser , DateTimeOffset . Now ) ;
2018-06-29 19:52:40 +02:00
if ( unlockResult . Succeeded = = false )
{
2018-08-14 22:36:47 +01:00
Logger . Warn < AuthenticationController > ( "Could not unlock for user {UserId} - error {UnlockError}" , model . UserId , unlockResult . Errors . First ( ) ) ;
2018-06-29 19:52:40 +02:00
}
2020-03-16 13:53:03 +00:00
var resetAccessFailedCountResult = await UserManager . ResetAccessFailedCountAsync ( identityUser ) ;
2018-06-29 19:52:40 +02:00
if ( resetAccessFailedCountResult . Succeeded = = false )
{
2018-08-14 22:36:47 +01:00
Logger . Warn < AuthenticationController > ( "Could not reset access failed count {UserId} - error {UnlockError}" , model . UserId , unlockResult . Errors . First ( ) ) ;
2018-06-29 19:52:40 +02:00
}
}
2019-01-26 10:52:19 -05:00
// They've successfully set their password, we can now update their user account to be confirmed
// if user was only invited, then they have not been approved
// but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite)
// means we have verified their email
2020-03-16 13:53:03 +00:00
// TODO: SB: ConfirmEmailAsync
if ( ! await UserManager . IsEmailConfirmedAsync ( identityUser ) )
2018-10-01 14:32:46 +02:00
{
2020-03-16 13:53:03 +00:00
await UserManager . ConfirmEmailAsync ( identityUser , model . ResetCode ) ;
2018-10-01 14:32:46 +02:00
}
2019-01-26 10:52:19 -05:00
// invited is not approved, never logged in, invited date present
2018-10-01 14:32:46 +02:00
/ *
if ( LastLoginDate = = default & & IsApproved = = false & & InvitedDate ! = null )
return UserState . Invited ;
* /
2018-11-27 13:46:43 +01:00
if ( identityUser ! = null & & ! identityUser . IsApproved )
2018-10-01 14:32:46 +02:00
{
var user = Services . UserService . GetByUsername ( identityUser . UserName ) ;
2019-01-26 10:52:19 -05:00
// also check InvitedDate and never logged in, otherwise this would allow a disabled user to reactivate their account with a forgot password
2018-10-01 14:32:46 +02:00
if ( user . LastLoginDate = = default & & user . InvitedDate ! = null )
{
user . IsApproved = true ;
user . InvitedDate = null ;
Services . UserService . Save ( user ) ;
}
}
2018-06-29 19:52:40 +02:00
UserManager . RaiseForgotPasswordChangedSuccessEvent ( model . UserId ) ;
return Request . CreateResponse ( HttpStatusCode . OK ) ;
}
return Request . CreateValidationErrorResponse (
2020-03-16 13:53:03 +00:00
result . Errors . Any ( ) ? result . Errors . First ( ) . Description : "Set password failed" ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
/// Logs the current user out
/// </summary>
/// <returns></returns>
[ClearAngularAntiForgeryToken]
[ValidateAngularAntiForgeryToken]
public HttpResponseMessage PostLogout ( )
{
var owinContext = Request . TryGetOwinContext ( ) . Result ;
owinContext . Authentication . SignOut (
Core . Constants . Security . BackOfficeAuthenticationType ,
Core . Constants . Security . BackOfficeExternalAuthenticationType ) ;
2018-08-14 22:36:47 +01:00
Logger . Info < AuthenticationController > ( "User {UserName} from IP address {RemoteIpAddress} has logged out" , User . Identity = = null ? "UNKNOWN" : User . Identity . Name , owinContext . Request . RemoteIpAddress ) ;
2018-06-29 19:52:40 +02:00
if ( UserManager ! = null )
{
2019-05-02 14:56:12 +02:00
int . TryParse ( User . Identity . GetUserId ( ) , out var userId ) ;
2018-06-29 19:52:40 +02:00
UserManager . RaiseLogoutSuccessEvent ( userId ) ;
}
return Request . CreateResponse ( HttpStatusCode . OK ) ;
}
/// <summary>
/// This is used when the user is auth'd successfully and we need to return an OK with user details along with setting the current Principal in the request
/// </summary>
/// <param name="user"></param>
/// <param name="principal"></param>
/// <returns></returns>
private HttpResponseMessage SetPrincipalAndReturnUserDetail ( IUser user , IPrincipal principal )
{
if ( user = = null ) throw new ArgumentNullException ( "user" ) ;
if ( principal = = null ) throw new ArgumentNullException ( nameof ( principal ) ) ;
var userDetail = Mapper . Map < UserDetail > ( user ) ;
2019-01-26 10:52:19 -05:00
// update the userDetail and set their remaining seconds
2018-06-29 19:52:40 +02:00
userDetail . SecondsUntilTimeout = TimeSpan . FromMinutes ( GlobalSettings . TimeOutInMinutes ) . TotalSeconds ;
2019-01-26 10:52:19 -05:00
// create a response with the userDetail object
2018-06-29 19:52:40 +02:00
var response = Request . CreateResponse ( HttpStatusCode . OK , userDetail ) ;
2019-01-26 10:52:19 -05:00
// ensure the user is set for the current request
2018-06-29 19:52:40 +02:00
Request . SetPrincipalForRequest ( principal ) ;
return response ;
}
private string ConstructCallbackUrl ( int userId , string code )
{
// Get an mvc helper to get the url
var http = EnsureHttpContext ( ) ;
var urlHelper = new UrlHelper ( http . Request . RequestContext ) ;
var action = urlHelper . Action ( "ValidatePasswordResetCode" , "BackOffice" ,
new
{
2020-02-10 11:23:23 +01:00
area = GlobalSettings . GetUmbracoMvcArea ( _ioHelper ) ,
2018-06-29 19:52:40 +02:00
u = userId ,
r = code
} ) ;
// Construct full URL using configured application URL (which will fall back to request)
2020-02-10 11:23:23 +01:00
var applicationUri = _runtimeState . ApplicationUrl ;
2018-06-29 19:52:40 +02:00
var callbackUri = new Uri ( applicationUri , action ) ;
return callbackUri . ToString ( ) ;
}
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 ;
}
private void AddModelErrors ( IdentityResult result , string prefix = "" )
{
foreach ( var error in result . Errors )
{
2020-03-16 13:53:03 +00:00
ModelState . AddModelError ( prefix , error . Description ) ;
2018-06-29 19:52:40 +02:00
}
}
}
}