2013-02-03 05:06:11 +06:00
using System ;
using System.Collections.Generic ;
2015-11-19 18:12:21 +01:00
using System.ComponentModel ;
2013-02-03 05:06:11 +06:00
using System.Linq ;
using System.Web ;
using System.Web.Security ;
2015-03-24 20:17:37 +11:00
using AutoMapper ;
2013-02-03 05:06:11 +06:00
using Umbraco.Core ;
using Umbraco.Core.Logging ;
2013-07-31 18:21:27 +10:00
using Umbraco.Core.Models.Membership ;
2013-07-31 17:08:56 +10:00
using Umbraco.Core.Security ;
2015-11-19 18:12:21 +01:00
using Microsoft.AspNet.Identity.Owin ;
2015-11-26 13:07:22 +01:00
using Microsoft.Owin ;
2013-04-09 22:11:12 +06:00
using umbraco.businesslogic.Exceptions ;
2016-08-12 12:20:00 +02:00
using Umbraco.Core.Models.Identity ;
2015-11-19 18:12:21 +01:00
using Umbraco.Web.Models.ContentEditing ;
2013-07-31 18:21:27 +10:00
using GlobalSettings = Umbraco . Core . Configuration . GlobalSettings ;
using User = umbraco . BusinessLogic . User ;
2013-02-03 05:06:11 +06:00
namespace Umbraco.Web.Security
{
/// <summary>
2014-01-28 16:58:55 +11:00
/// A utility class used for dealing with USER security in Umbraco
2013-02-03 05:06:11 +06:00
/// </summary>
2013-07-31 17:08:56 +10:00
public class WebSecurity : DisposableObject
2013-02-03 05:06:11 +06:00
{
2013-07-31 17:08:56 +10:00
private HttpContextBase _httpContext ;
2013-07-31 18:21:27 +10:00
private ApplicationContext _applicationContext ;
2013-06-17 16:03:27 +10:00
2013-07-31 18:21:27 +10:00
public WebSecurity ( HttpContextBase httpContext , ApplicationContext applicationContext )
2013-06-17 16:03:27 +10:00
{
_httpContext = httpContext ;
2017-07-03 16:31:47 +10:00
_applicationContext = applicationContext ;
2013-06-17 16:03:27 +10:00
}
2013-07-31 17:08:56 +10:00
2013-02-27 00:19:48 +06:00
/// <summary>
/// Returns true or false if the currently logged in member is authorized based on the parameters provided
/// </summary>
/// <param name="allowAll"></param>
/// <param name="allowTypes"></param>
/// <param name="allowGroups"></param>
/// <param name="allowMembers"></param>
/// <returns></returns>
2014-01-28 16:58:55 +11:00
[Obsolete("Use MembershipHelper.IsMemberAuthorized instead")]
2013-04-04 23:30:17 +06:00
public bool IsMemberAuthorized (
2013-02-27 00:19:48 +06:00
bool allowAll = false ,
IEnumerable < string > allowTypes = null ,
IEnumerable < string > allowGroups = null ,
IEnumerable < int > allowMembers = null )
{
2014-01-28 16:58:55 +11:00
if ( HttpContext . Current = = null | | ApplicationContext . Current = = null )
2013-04-15 12:18:40 -02:00
{
2014-01-28 16:58:55 +11:00
return false ;
2013-04-15 12:18:40 -02:00
}
2014-01-28 16:58:55 +11:00
var helper = new MembershipHelper ( ApplicationContext . Current , new HttpContextWrapper ( HttpContext . Current ) ) ;
return helper . IsMemberAuthorized ( allowAll , allowTypes , allowGroups , allowMembers ) ;
2013-02-27 00:19:48 +06:00
}
2013-02-03 05:06:11 +06:00
2013-08-09 13:24:26 +10:00
private IUser _currentUser ;
2013-04-04 23:30:17 +06:00
2013-02-03 05:06:11 +06:00
/// <summary>
/// Gets the current user.
/// </summary>
/// <value>The current user.</value>
2015-02-18 17:14:55 +01:00
public virtual IUser CurrentUser
2013-02-03 05:06:11 +06:00
{
get
{
2016-10-26 22:38:54 +02:00
//only load it once per instance! (but make sure groups are loaded)
2017-05-10 21:00:30 +10:00
if ( _currentUser = = null )
2013-07-31 17:08:56 +10:00
{
var id = GetUserId ( ) ;
if ( id = = - 1 )
{
return null ;
}
2013-08-09 13:24:26 +10:00
_currentUser = _applicationContext . Services . UserService . GetUserById ( id ) ;
2013-07-31 17:08:56 +10:00
}
return _currentUser ;
2013-02-03 05:06:11 +06:00
}
}
2015-11-26 13:07:22 +01:00
private BackOfficeSignInManager _signInManager ;
private BackOfficeSignInManager SignInManager
{
get
{
if ( _signInManager = = null )
{
var mgr = _httpContext . GetOwinContext ( ) . Get < BackOfficeSignInManager > ( ) ;
if ( mgr = = null )
{
throw new NullReferenceException ( "Could not resolve an instance of " + typeof ( BackOfficeSignInManager ) + " from the " + typeof ( IOwinContext ) ) ;
}
_signInManager = mgr ;
}
return _signInManager ;
}
}
2016-08-12 12:20:00 +02:00
private BackOfficeUserManager < BackOfficeIdentityUser > _userManager ;
protected BackOfficeUserManager < BackOfficeIdentityUser > UserManager
2015-11-26 13:07:22 +01:00
{
2016-08-12 12:20:00 +02:00
get { return _userManager ? ? ( _userManager = _httpContext . GetOwinContext ( ) . GetBackOfficeUserManager ( ) ) ; }
}
2015-11-26 13:07:22 +01:00
2013-02-03 05:06:11 +06:00
/// <summary>
/// Logs a user in.
/// </summary>
2013-04-04 23:30:17 +06:00
/// <param name="userId">The user Id</param>
2013-10-16 12:00:42 +11:00
/// <returns>returns the number of seconds until their session times out</returns>
2015-02-18 17:14:55 +01:00
public virtual double PerformLogin ( int userId )
2013-02-03 05:06:11 +06:00
{
2015-11-19 18:12:21 +01:00
var owinCtx = _httpContext . GetOwinContext ( ) ;
2015-11-26 13:07:22 +01:00
//ensure it's done for owin too
owinCtx . Authentication . SignOut ( Constants . Security . BackOfficeExternalAuthenticationType ) ;
2015-11-19 18:12:21 +01:00
2015-11-26 13:07:22 +01:00
var user = UserManager . FindByIdAsync ( userId ) . Result ;
var userData = Mapper . Map < UserData > ( user ) ;
_httpContext . SetPrincipalForRequest ( userData ) ;
2015-12-15 10:34:11 +01:00
SignInManager . SignInAsync ( user , isPersistent : true , rememberBrowser : false ) . Wait ( ) ;
2015-11-19 18:12:21 +01:00
return TimeSpan . FromMinutes ( GlobalSettings . TimeOutInMinutes ) . TotalSeconds ;
2013-07-31 17:08:56 +10:00
}
2013-02-03 05:06:11 +06:00
2015-11-19 18:12:21 +01:00
[Obsolete("This method should not be used, login is performed by the OWIN pipeline, use the overload that returns double and accepts a UserId instead")]
2015-02-18 17:14:55 +01:00
public virtual FormsAuthenticationTicket PerformLogin ( IUser user )
2013-07-31 17:08:56 +10:00
{
2015-11-26 13:07:22 +01:00
//clear the external cookie - we do this first without owin context because we're writing cookies directly to httpcontext
// and cookie handling is different with httpcontext vs webapi and owin, normally we'd just do:
//_httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType);
2015-03-25 12:21:41 +11:00
var externalLoginCookie = _httpContext . Request . Cookies . Get ( Constants . Security . BackOfficeExternalCookieName ) ;
2015-03-24 20:17:37 +11:00
if ( externalLoginCookie ! = null )
2013-07-31 17:08:56 +10:00
{
2015-03-24 20:17:37 +11:00
externalLoginCookie . Expires = DateTime . Now . AddYears ( - 1 ) ;
_httpContext . Response . Cookies . Set ( externalLoginCookie ) ;
}
2015-11-26 13:07:22 +01:00
//ensure it's done for owin too
_httpContext . GetOwinContext ( ) . Authentication . SignOut ( Constants . Security . BackOfficeExternalAuthenticationType ) ;
2015-03-24 20:17:37 +11:00
var ticket = _httpContext . CreateUmbracoAuthTicket ( Mapper . Map < UserData > ( user ) ) ;
2013-12-03 11:36:17 +11:00
return ticket ;
2013-02-03 05:06:11 +06:00
}
/// <summary>
/// Clears the current login for the currently logged in user
/// </summary>
2015-02-18 17:14:55 +01:00
public virtual void ClearCurrentLogin ( )
2013-02-03 05:06:11 +06:00
{
2013-07-31 17:08:56 +10:00
_httpContext . UmbracoLogout ( ) ;
2015-12-15 10:34:11 +01:00
_httpContext . GetOwinContext ( ) . Authentication . SignOut (
Core . Constants . Security . BackOfficeAuthenticationType ,
Core . Constants . Security . BackOfficeExternalAuthenticationType ) ;
2013-02-03 05:06:11 +06:00
}
2013-11-12 17:38:32 +11:00
/// <summary>
/// Renews the user's login ticket
/// </summary>
2015-02-18 17:14:55 +01:00
public virtual void RenewLoginTimeout ( )
2013-02-03 05:06:11 +06:00
{
2013-10-15 18:46:44 +11:00
_httpContext . RenewUmbracoAuthTicket ( ) ;
2013-02-03 05:06:11 +06:00
}
2013-04-09 22:11:12 +06:00
/// <summary>
/// Validates credentials for a back office user
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
2015-02-18 17:14:55 +01:00
public virtual bool ValidateBackOfficeCredentials ( string username , string password )
2013-04-09 22:11:12 +06:00
{
2014-03-18 19:05:07 +11:00
var membershipProvider = Core . Security . MembershipProviderExtensions . GetUsersMembershipProvider ( ) ;
2013-04-15 12:18:40 -02:00
return membershipProvider ! = null & & membershipProvider . ValidateUser ( username , password ) ;
2013-04-09 22:11:12 +06:00
}
2014-02-10 14:29:29 +11:00
2013-11-20 14:18:03 +11:00
/// <summary>
/// Returns the MembershipUser from the back office membership provider
/// </summary>
/// <param name="username"></param>
/// <param name="setOnline"></param>
/// <returns></returns>
2015-02-18 17:14:55 +01:00
public virtual MembershipUser GetBackOfficeMembershipUser ( string username , bool setOnline )
2013-11-20 14:18:03 +11:00
{
2014-03-18 20:36:02 +11:00
var membershipProvider = Core . Security . MembershipProviderExtensions . GetUsersMembershipProvider ( ) ;
2013-11-20 14:18:03 +11:00
return membershipProvider ! = null ? membershipProvider . GetUser ( username , setOnline ) : null ;
}
/// <summary>
2017-02-03 00:47:28 +11:00
/// Gets (and creates if not found) the back office <see cref="IUser"/> instance for the username specified
2013-11-20 14:18:03 +11:00
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
/// <remarks>
2017-02-03 00:47:28 +11:00
/// This will return an <see cref="IUser"/> instance no matter what membership provider is installed for the back office, it will automatically
/// create any missing <see cref="IUser"/> accounts if one is not found and a custom membership provider or <see cref="IBackOfficeUserPasswordChecker"/> is being used.
2013-11-20 14:18:03 +11:00
/// </remarks>
internal IUser GetBackOfficeUser ( string username )
{
//get the membership user (set user to be 'online' in the provider too)
var membershipUser = GetBackOfficeMembershipUser ( username , true ) ;
2014-03-18 20:36:02 +11:00
var provider = Core . Security . MembershipProviderExtensions . GetUsersMembershipProvider ( ) ;
2013-11-20 14:18:03 +11:00
if ( membershipUser = = null )
{
throw new InvalidOperationException (
"The username & password validated but the membership provider '" +
2014-03-18 20:36:02 +11:00
provider . Name +
2013-11-20 14:18:03 +11:00
"' did not return a MembershipUser with the username supplied" ) ;
}
//regarldess of the membership provider used, see if this user object already exists in the umbraco data
2014-01-08 16:09:35 +11:00
var user = _applicationContext . Services . UserService . GetByUsername ( membershipUser . UserName ) ;
2013-11-20 14:18:03 +11:00
//we're using the built-in membership provider so the user will already be available
2014-03-18 20:36:02 +11:00
if ( provider . IsUmbracoUsersProvider ( ) )
2013-11-20 14:18:03 +11:00
{
if ( user = = null )
{
//this should never happen
throw new InvalidOperationException ( "The user '" + username + "' could not be found in the Umbraco database" ) ;
}
return user ;
}
//we are using a custom membership provider for the back office, in this case we need to create user accounts for the logged in member.
//if we already have a user object in Umbraco we don't need to do anything, otherwise we need to create a mapped Umbraco account.
if ( user ! = null ) return user ;
var email = membershipUser . Email ;
if ( email . IsNullOrWhiteSpace ( ) )
{
//in some cases if there is no email we have to generate one since it is required!
email = Guid . NewGuid ( ) . ToString ( "N" ) + "@example.com" ;
}
2016-10-28 09:20:52 +02:00
user = new Core . Models . Membership . User
2013-11-20 14:18:03 +11:00
{
Email = email ,
Language = GlobalSettings . DefaultUILanguage ,
Name = membershipUser . UserName ,
2014-03-18 19:05:07 +11:00
RawPasswordValue = Guid . NewGuid ( ) . ToString ( "N" ) , //Need to set this to something - will not be used though
2013-11-20 14:18:03 +11:00
Username = membershipUser . UserName ,
2014-01-08 16:09:35 +11:00
IsLockedOut = false ,
2013-11-20 14:18:03 +11:00
IsApproved = true
} ;
2014-01-08 16:09:35 +11:00
_applicationContext . Services . UserService . Save ( user ) ;
2013-11-20 14:18:03 +11:00
return user ;
}
2013-04-04 23:30:17 +06:00
/// <summary>
/// Validates the user node tree permissions.
/// </summary>
/// <param name="umbracoUser"></param>
/// <param name="path">The path.</param>
/// <param name="action">The action.</param>
/// <returns></returns>
internal bool ValidateUserNodeTreePermissions ( User umbracoUser , string path , string action )
{
var permissions = umbracoUser . GetPermissions ( path ) ;
2013-04-15 12:18:40 -02:00
if ( permissions . IndexOf ( action , StringComparison . Ordinal ) > - 1 & & ( path . Contains ( "-20" ) | | ( "," + path + "," ) . Contains ( "," + umbracoUser . StartNodeId + "," ) ) )
2013-04-04 23:30:17 +06:00
return true ;
var user = umbracoUser ;
LogHelper . Info < WebSecurity > ( "User {0} has insufficient permissions in UmbracoEnsuredPage: '{1}', '{2}', '{3}'" , ( ) = > user . Name , ( ) = > path , ( ) = > permissions , ( ) = > action ) ;
return false ;
}
2013-04-09 22:11:12 +06:00
/// <summary>
/// Validates the current user to see if they have access to the specified app
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
internal bool ValidateUserApp ( string app )
{
//if it is empty, don't validate
if ( app . IsNullOrWhiteSpace ( ) )
{
return true ;
}
2014-02-13 16:46:52 +11:00
return CurrentUser . AllowedSections . Any ( uApp = > uApp . InvariantEquals ( app ) ) ;
2013-04-09 22:11:12 +06:00
}
2014-02-13 16:46:52 +11:00
2013-02-03 05:06:11 +06:00
/// <summary>
/// Gets the user id.
/// </summary>
2013-07-31 17:08:56 +10:00
/// <param name="umbracoUserContextId">This is not used</param>
2013-02-03 05:06:11 +06:00
/// <returns></returns>
2013-07-31 17:08:56 +10:00
[Obsolete("This method is no longer used, use the GetUserId() method without parameters instead")]
2013-04-04 23:30:17 +06:00
public int GetUserId ( string umbracoUserContextId )
2013-07-31 17:08:56 +10:00
{
return GetUserId ( ) ;
}
2013-04-04 02:11:31 +06:00
2013-07-31 17:08:56 +10:00
/// <summary>
/// Gets the currnet user's id.
/// </summary>
/// <returns></returns>
2015-02-18 17:14:55 +01:00
public virtual int GetUserId ( )
2013-07-31 17:08:56 +10:00
{
2015-01-12 21:45:52 +11:00
var identity = _httpContext . GetCurrentIdentity ( false ) ;
2013-07-31 17:08:56 +10:00
if ( identity = = null )
2013-04-04 02:11:31 +06:00
return - 1 ;
2013-07-31 18:28:18 +10:00
return Convert . ToInt32 ( identity . Id ) ;
2013-02-03 05:06:11 +06:00
}
2013-11-29 12:42:50 +11:00
/// <summary>
/// Returns the current user's unique session id - used to mitigate csrf attacks or any other reason to validate a request
/// </summary>
/// <returns></returns>
2015-02-18 17:14:55 +01:00
public virtual string GetSessionId ( )
2013-11-29 12:42:50 +11:00
{
2015-01-12 21:45:52 +11:00
var identity = _httpContext . GetCurrentIdentity ( false ) ;
2013-11-29 12:42:50 +11:00
if ( identity = = null )
return null ;
return identity . SessionId ;
}
2013-02-03 05:06:11 +06:00
/// <summary>
/// Validates the user context ID.
/// </summary>
2013-07-31 17:08:56 +10:00
/// <param name="currentUmbracoUserContextId">This doesn't do anything</param>
2013-02-03 05:06:11 +06:00
/// <returns></returns>
2013-07-31 17:08:56 +10:00
[Obsolete("This method is no longer used, use the ValidateCurrentUser() method instead")]
2013-04-04 23:30:17 +06:00
public bool ValidateUserContextId ( string currentUmbracoUserContextId )
2013-02-03 05:06:11 +06:00
{
2013-07-31 17:08:56 +10:00
return ValidateCurrentUser ( ) ;
}
2013-02-03 05:06:11 +06:00
2013-07-31 17:08:56 +10:00
/// <summary>
/// Validates the currently logged in user and ensures they are not timed out
/// </summary>
/// <returns></returns>
2015-02-18 17:14:55 +01:00
public virtual bool ValidateCurrentUser ( )
2013-07-31 17:08:56 +10:00
{
2017-06-14 16:21:56 +02:00
return ValidateCurrentUser ( false , true ) = = ValidateRequestAttempt . Success ;
}
2013-02-03 05:06:11 +06:00
2013-04-04 23:30:17 +06:00
/// <summary>
2014-12-05 10:29:18 +11:00
/// Validates the current user assigned to the request and ensures the stored user data is valid
2013-04-04 23:30:17 +06:00
/// </summary>
2013-04-09 22:11:12 +06:00
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
2017-06-14 16:21:56 +02:00
/// <param name="requiresApproval">If true requires that the user is approved to be validated</param>
2013-04-04 23:30:17 +06:00
/// <returns></returns>
2017-06-14 16:21:56 +02:00
public virtual ValidateRequestAttempt ValidateCurrentUser ( bool throwExceptions , bool requiresApproval = true )
2013-04-04 23:30:17 +06:00
{
2014-12-05 10:29:18 +11:00
//This will first check if the current user is already authenticated - which should be the case in nearly all circumstances
// since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't
// need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request.
2013-04-04 23:30:17 +06:00
2015-07-13 15:52:21 +02:00
if ( IsAuthenticated ( ) = = false )
2013-07-31 17:08:56 +10:00
{
2014-12-05 10:29:18 +11:00
//There is no user
if ( throwExceptions ) throw new InvalidOperationException ( "The user has no umbraco contextid - try logging in" ) ;
return ValidateRequestAttempt . FailedNoContextId ;
}
2013-04-04 23:30:17 +06:00
2014-12-05 10:29:18 +11:00
var user = CurrentUser ;
// Check for console access
2017-06-14 16:21:56 +02:00
if ( user = = null | | ( requiresApproval & & user . IsApproved = = false ) | | ( user . IsLockedOut & & GlobalSettings . RequestIsInUmbracoApplication ( _httpContext ) ) )
2014-12-05 10:29:18 +11:00
{
if ( throwExceptions ) throw new ArgumentException ( "You have no priviledges to the umbraco console. Please contact your administrator" ) ;
return ValidateRequestAttempt . FailedNoPrivileges ;
2013-04-09 22:11:12 +06:00
}
2014-12-05 10:29:18 +11:00
return ValidateRequestAttempt . Success ;
2013-07-31 17:08:56 +10:00
2013-04-09 22:11:12 +06:00
}
/// <summary>
/// Authorizes the full request, checks for SSL and validates the current user
/// </summary>
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
/// <returns></returns>
2013-06-17 16:03:27 +10:00
internal ValidateRequestAttempt AuthorizeRequest ( bool throwExceptions = false )
2013-04-09 22:11:12 +06:00
{
// check for secure connection
2013-06-17 16:03:27 +10:00
if ( GlobalSettings . UseSSL & & _httpContext . Request . IsSecureConnection = = false )
2013-04-09 22:11:12 +06:00
{
2013-04-15 12:18:40 -02:00
if ( throwExceptions ) throw new UserAuthorizationException ( "This installation requires a secure connection (via SSL). Please update the URL to include https://" ) ;
2013-04-09 22:11:12 +06:00
return ValidateRequestAttempt . FailedNoSsl ;
2013-04-04 23:30:17 +06:00
}
2013-06-17 16:03:27 +10:00
return ValidateCurrentUser ( throwExceptions ) ;
2013-04-09 22:11:12 +06:00
}
/// <summary>
/// Checks if the specified user as access to the app
/// </summary>
/// <param name="app"></param>
/// <param name="user"></param>
/// <returns></returns>
2017-05-19 20:17:50 +10:00
internal virtual bool UserHasAppAccess ( string app , IUser user )
2013-08-09 13:45:57 +10:00
{
2014-02-13 16:46:52 +11:00
var apps = user . AllowedSections ;
2013-08-09 13:45:57 +10:00
return apps . Any ( uApp = > uApp . InvariantEquals ( app ) ) ;
}
[Obsolete("Do not use this method if you don't have to, use the overload with IUser instead")]
2013-04-09 22:11:12 +06:00
internal bool UserHasAppAccess ( string app , User user )
{
return user . Applications . Any ( uApp = > uApp . alias = = app ) ;
}
/// <summary>
/// Checks if the specified user by username as access to the app
/// </summary>
/// <param name="app"></param>
/// <param name="username"></param>
/// <returns></returns>
internal bool UserHasAppAccess ( string app , string username )
{
2014-01-08 16:09:35 +11:00
var user = _applicationContext . Services . UserService . GetByUsername ( username ) ;
2013-08-09 13:45:57 +10:00
if ( user = = null )
{
return false ;
}
return UserHasAppAccess ( app , user ) ;
2013-04-04 23:30:17 +06:00
}
2013-11-29 12:42:50 +11:00
[Obsolete("Returns the current user's unique umbraco sesion id - this cannot be set and isn't intended to be used in your code")]
2013-04-04 23:30:17 +06:00
public string UmbracoUserContextId
2013-07-31 17:08:56 +10:00
{
2013-02-03 05:06:11 +06:00
get
{
2013-11-29 12:42:50 +11:00
return _httpContext . GetUmbracoAuthTicket ( ) = = null ? "" : GetSessionId ( ) ;
2013-02-03 05:06:11 +06:00
}
set
{
}
}
2015-07-13 15:52:21 +02:00
/// <summary>
/// Ensures that a back office user is logged in
/// </summary>
/// <returns></returns>
public bool IsAuthenticated ( )
{
return _httpContext . User . Identity . IsAuthenticated & & _httpContext . GetCurrentIdentity ( false ) ! = null ;
}
2013-07-31 17:08:56 +10:00
protected override void DisposeResources ( )
{
_httpContext = null ;
}
2015-07-13 15:52:21 +02:00
2013-02-03 05:06:11 +06:00
}
}