2013-02-03 05:06:11 +06:00
using System ;
using System.Collections.Generic ;
2013-10-17 22:40:38 +11:00
using System.ComponentModel.DataAnnotations ;
2013-02-03 05:06:11 +06:00
using System.Linq ;
using System.Web ;
using System.Web.Security ;
using Umbraco.Core ;
2013-04-04 02:11:31 +06:00
using Umbraco.Core.Cache ;
2013-02-03 05:06:11 +06:00
using Umbraco.Core.Logging ;
2013-08-02 15:16:04 +10:00
using Umbraco.Core.Security ;
2013-10-17 22:40:38 +11:00
using Umbraco.Web.Models ;
2013-08-02 15:16:04 +10:00
using umbraco ;
2013-02-03 05:06:11 +06:00
using umbraco.BusinessLogic ;
using umbraco.DataLayer ;
2013-04-09 22:11:12 +06:00
using umbraco.businesslogic.Exceptions ;
2013-02-27 00:19:48 +06:00
using umbraco.cms.businesslogic.member ;
2013-08-02 15:16:04 +10:00
using GlobalSettings = Umbraco . Core . Configuration . GlobalSettings ;
using UmbracoSettings = Umbraco . Core . Configuration . UmbracoSettings ;
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-04-04 23:30:17 +06:00
public class WebSecurity
2013-02-03 05:06:11 +06: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-02-27 00:19:48 +06:00
{
2014-01-28 16:58:55 +11:00
return false ;
2013-02-27 00:19:48 +06: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
/// <summary>
/// Gets the SQL helper.
/// </summary>
/// <value>The SQL helper.</value>
2013-04-04 23:30:17 +06:00
private ISqlHelper SqlHelper
2013-02-03 05:06:11 +06:00
{
get { return Application . SqlHelper ; }
}
private const long TicksPrMinute = 600000000 ;
2013-04-15 12:18:40 -02:00
private static readonly int UmbracoTimeOutInMinutes = GlobalSettings . TimeOutInMinutes ;
2013-02-03 05:06:11 +06:00
2013-04-04 23:30:17 +06:00
private User _currentUser ;
2013-02-03 05:06:11 +06:00
/// <summary>
/// Gets the current user.
/// </summary>
/// <value>The current user.</value>
2013-04-04 23:30:17 +06:00
/// <remarks>
/// This is internal because we don't want to expose the legacy User object on this class, instead we'll wait until IUser
/// is public. If people want to reference the current user, they can reference it from the UmbracoContext.
/// </remarks>
internal User CurrentUser
2013-02-03 05:06:11 +06:00
{
get
{
2013-04-04 23:30:17 +06:00
//only load it once per instance!
return _currentUser ? ? ( _currentUser = User . GetCurrent ( ) ) ;
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>
public void PerformLogin ( int userId )
2013-02-03 05:06:11 +06:00
{
var retVal = Guid . NewGuid ( ) ;
SqlHelper . ExecuteNonQuery (
2013-04-04 23:30:17 +06:00
"insert into umbracoUserLogins (contextID, userID, timeout) values (@contextId,'" + userId + "','" +
2013-04-15 12:18:40 -02:00
( DateTime . Now . Ticks + ( TicksPrMinute * UmbracoTimeOutInMinutes ) ) +
2013-02-03 05:06:11 +06:00
"') " ,
SqlHelper . CreateParameter ( "@contextId" , retVal ) ) ;
UmbracoUserContextId = retVal . ToString ( ) ;
2013-07-31 11:10:33 +10:00
LogHelper . Info < WebSecurity > ( "User Id: {0} logged in" , ( ) = > userId ) ;
2013-02-03 05:06:11 +06:00
}
/// <summary>
/// Clears the current login for the currently logged in user
/// </summary>
2013-04-04 23:30:17 +06:00
public void ClearCurrentLogin ( )
2013-02-03 05:06:11 +06:00
{
// Added try-catch in case login doesn't exist in the database
// Either due to old cookie or running multiple sessions on localhost with different port number
try
{
SqlHelper . ExecuteNonQuery (
"DELETE FROM umbracoUserLogins WHERE contextId = @contextId" ,
SqlHelper . CreateParameter ( "@contextId" , UmbracoUserContextId ) ) ;
}
catch ( Exception ex )
{
2013-07-31 11:10:33 +10:00
LogHelper . Error < WebSecurity > ( string . Format ( "Login with contextId {0} didn't exist in the database" , UmbracoUserContextId ) , ex ) ;
2013-02-03 05:06:11 +06:00
}
2013-07-31 11:10:33 +10:00
//this clears the cookie
UmbracoUserContextId = "" ;
2013-02-03 05:06:11 +06:00
}
2013-04-04 23:30:17 +06:00
public void RenewLoginTimeout ( )
2013-02-03 05:06:11 +06:00
{
// only call update if more than 1/10 of the timeout has passed
SqlHelper . ExecuteNonQuery (
"UPDATE umbracoUserLogins SET timeout = @timeout WHERE contextId = @contextId" ,
SqlHelper . CreateParameter ( "@timeout" , DateTime . Now . Ticks + ( TicksPrMinute * UmbracoTimeOutInMinutes ) ) ,
SqlHelper . CreateParameter ( "@contextId" , UmbracoUserContextId ) ) ;
}
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>
internal bool ValidateBackOfficeCredentials ( string username , string password )
{
2013-04-15 12:18:40 -02:00
var membershipProvider = Membership . Providers [ UmbracoSettings . DefaultBackofficeProvider ] ;
return membershipProvider ! = null & & membershipProvider . ValidateUser ( username , password ) ;
2013-04-09 22:11:12 +06:00
}
2014-02-10 14:29:29 +11:00
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 17:19:28 +11:00
return CurrentUser . Applications . Any ( uApp = > uApp . alias . InvariantEquals ( app ) ) ;
2013-04-09 22:11:12 +06:00
}
2013-04-04 23:30:17 +06:00
internal void UpdateLogin ( long timeout )
2013-02-03 05:06:11 +06:00
{
// only call update if more than 1/10 of the timeout has passed
if ( timeout - ( ( ( TicksPrMinute * UmbracoTimeOutInMinutes ) * 0.8 ) ) < DateTime . Now . Ticks )
SqlHelper . ExecuteNonQuery (
"UPDATE umbracoUserLogins SET timeout = @timeout WHERE contextId = @contextId" ,
SqlHelper . CreateParameter ( "@timeout" , DateTime . Now . Ticks + ( TicksPrMinute * UmbracoTimeOutInMinutes ) ) ,
SqlHelper . CreateParameter ( "@contextId" , UmbracoUserContextId ) ) ;
}
2013-04-04 23:30:17 +06:00
internal long GetTimeout ( string umbracoUserContextId )
2013-02-03 05:06:11 +06:00
{
2013-04-04 02:11:31 +06:00
return ApplicationContext . Current . ApplicationCache . GetCacheItem (
CacheKeys . UserContextTimeoutCacheKey + umbracoUserContextId ,
new TimeSpan ( 0 , UmbracoTimeOutInMinutes / 10 , 0 ) ,
( ) = > GetTimeout ( true ) ) ;
2013-02-03 05:06:11 +06:00
}
2013-04-04 23:30:17 +06:00
internal long GetTimeout ( bool byPassCache )
2013-02-03 05:06:11 +06:00
{
if ( UmbracoSettings . KeepUserLoggedIn )
RenewLoginTimeout ( ) ;
if ( byPassCache )
{
return SqlHelper . ExecuteScalar < long > ( "select timeout from umbracoUserLogins where contextId=@contextId" ,
SqlHelper . CreateParameter ( "@contextId" , new Guid ( UmbracoUserContextId ) )
) ;
}
2013-04-15 12:18:40 -02:00
2013-02-03 05:06:11 +06:00
return GetTimeout ( UmbracoUserContextId ) ;
}
/// <summary>
/// Gets the user id.
/// </summary>
/// <param name="umbracoUserContextId">The umbraco user context ID.</param>
/// <returns></returns>
2013-04-04 23:30:17 +06:00
public int GetUserId ( string umbracoUserContextId )
2013-02-03 05:06:11 +06:00
{
2013-04-04 02:11:31 +06:00
//need to parse to guid
2013-04-15 12:18:40 -02:00
Guid guid ;
if ( Guid . TryParse ( umbracoUserContextId , out guid ) = = false )
2013-02-03 05:06:11 +06:00
{
return - 1 ;
}
2013-04-04 02:11:31 +06:00
2013-04-15 12:18:40 -02:00
var id = ApplicationContext . Current . ApplicationCache . GetCacheItem (
2013-04-04 02:11:31 +06:00
CacheKeys . UserContextCacheKey + umbracoUserContextId ,
2013-04-15 12:18:40 -02:00
new TimeSpan ( 0 , UmbracoTimeOutInMinutes / 10 , 0 ) ,
2013-04-04 02:11:31 +06:00
( ) = > SqlHelper . ExecuteScalar < int? > (
"select userID from umbracoUserLogins where contextID = @contextId" ,
2013-04-15 12:18:40 -02:00
SqlHelper . CreateParameter ( "@contextId" , guid ) ) ) ;
2013-04-04 02:11:31 +06:00
if ( id = = null )
return - 1 ;
2013-04-15 12:18:40 -02:00
return id . Value ;
2013-02-03 05:06:11 +06:00
}
/// <summary>
/// Validates the user context ID.
/// </summary>
/// <param name="currentUmbracoUserContextId">The umbraco user context ID.</param>
/// <returns></returns>
2013-04-04 23:30:17 +06:00
public bool ValidateUserContextId ( string currentUmbracoUserContextId )
2013-02-03 05:06:11 +06:00
{
if ( ( currentUmbracoUserContextId ! = "" ) )
{
int uid = GetUserId ( currentUmbracoUserContextId ) ;
long timeout = GetTimeout ( currentUmbracoUserContextId ) ;
if ( timeout > DateTime . Now . Ticks )
{
return true ;
}
2013-04-04 02:11:31 +06:00
var user = User . GetUser ( uid ) ;
2013-02-03 05:06:11 +06:00
LogHelper . Info ( typeof ( WebSecurity ) , "User {0} (Id:{1}) logged out" , ( ) = > user . Name , ( ) = > user . Id ) ;
}
return false ;
}
2013-04-04 23:30:17 +06:00
/// <summary>
/// Validates the current user
/// </summary>
/// <param name="httpContext"></param>
2013-04-09 22:11:12 +06:00
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
2013-04-04 23:30:17 +06:00
/// <returns></returns>
2013-04-09 22:11:12 +06:00
internal ValidateRequestAttempt ValidateCurrentUser ( HttpContextBase httpContext , bool throwExceptions = false )
2013-04-04 23:30:17 +06:00
{
if ( UmbracoUserContextId ! = "" )
{
var uid = GetUserId ( UmbracoUserContextId ) ;
var timeout = GetTimeout ( UmbracoUserContextId ) ;
if ( timeout > DateTime . Now . Ticks )
{
var user = User . GetUser ( uid ) ;
// Check for console access
2013-04-15 12:18:40 -02:00
if ( user . Disabled | | ( user . NoConsole & & GlobalSettings . RequestIsInUmbracoApplication ( httpContext ) & & GlobalSettings . RequestIsLiveEditRedirector ( httpContext ) = = false ) )
2013-04-04 23:30:17 +06:00
{
2013-04-09 22:11:12 +06:00
if ( throwExceptions ) throw new ArgumentException ( "You have no priviledges to the umbraco console. Please contact your administrator" ) ;
return ValidateRequestAttempt . FailedNoPrivileges ;
2013-04-04 23:30:17 +06:00
}
UpdateLogin ( timeout ) ;
2013-04-09 22:11:12 +06:00
return ValidateRequestAttempt . Success ;
2013-04-04 23:30:17 +06:00
}
2013-04-09 22:11:12 +06:00
if ( throwExceptions ) throw new ArgumentException ( "User has timed out!!" ) ;
return ValidateRequestAttempt . FailedTimedOut ;
}
if ( throwExceptions ) throw new InvalidOperationException ( "The user has no umbraco contextid - try logging in" ) ;
return ValidateRequestAttempt . FailedNoContextId ;
}
/// <summary>
/// Authorizes the full request, checks for SSL and validates the current user
/// </summary>
/// <param name="httpContext"></param>
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
/// <returns></returns>
internal ValidateRequestAttempt AuthorizeRequest ( HttpContextBase httpContext , bool throwExceptions = false )
{
// check for secure connection
2013-04-15 12:18:40 -02: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-04-09 22:11:12 +06:00
return ValidateCurrentUser ( httpContext , throwExceptions ) ;
}
/// <summary>
/// Checks if the specified user as access to the app
/// </summary>
/// <param name="app"></param>
/// <param name="user"></param>
/// <returns></returns>
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 )
{
var uid = User . getUserId ( username ) ;
if ( uid < 0 ) return false ;
var usr = User . GetUser ( uid ) ;
if ( usr = = null ) return false ;
return UserHasAppAccess ( app , usr ) ;
2013-04-04 23:30:17 +06:00
}
2013-02-03 05:06:11 +06:00
/// <summary>
/// Gets or sets the umbraco user context ID.
/// </summary>
/// <value>The umbraco user context ID.</value>
2013-04-04 23:30:17 +06:00
public string UmbracoUserContextId
2013-08-02 15:16:04 +10:00
{
2013-02-03 05:06:11 +06:00
get
{
2013-08-02 15:16:04 +10:00
var authTicket = HttpContext . Current . GetUmbracoAuthTicket ( ) ;
if ( authTicket = = null )
2013-02-03 05:06:11 +06:00
{
2013-08-02 15:16:04 +10:00
return "" ;
2013-02-03 05:06:11 +06:00
}
2013-08-02 15:16:04 +10:00
var identity = authTicket . CreateUmbracoIdentity ( ) ;
if ( identity = = null )
{
HttpContext . Current . UmbracoLogout ( ) ;
return "" ;
}
return identity . UserContextId ;
2013-02-03 05:06:11 +06:00
}
set
{
2013-08-02 15:16:04 +10:00
if ( value . IsNullOrWhiteSpace ( ) )
2013-02-03 05:06:11 +06:00
{
2013-08-02 15:16:04 +10:00
HttpContext . Current . UmbracoLogout ( ) ;
}
else
{
var uid = GetUserId ( value ) ;
if ( uid = = - 1 )
2013-02-03 05:06:11 +06:00
{
2013-08-02 15:16:04 +10:00
HttpContext . Current . UmbracoLogout ( ) ;
2013-02-03 05:06:11 +06:00
}
else
{
2013-08-02 15:16:04 +10:00
var user = User . GetUser ( uid ) ;
HttpContext . Current . CreateUmbracoAuthTicket (
new UserData
{
Id = uid ,
AllowedApplications = user . Applications . Select ( x = > x . alias ) . ToArray ( ) ,
Culture = ui . Culture ( user ) ,
RealName = user . Name ,
Roles = new string [ ] { user . UserType . Alias } ,
StartContentNode = user . StartNodeId ,
StartMediaNode = user . StartMediaId ,
UserContextId = value ,
Username = user . LoginName
} ) ;
2013-02-03 05:06:11 +06:00
}
}
}
}
}
}