using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using umbraco.BusinessLogic; using umbraco.DataLayer; using umbraco.cms.businesslogic.member; namespace Umbraco.Web.Security { /// /// A utility class used for dealing with security in Umbraco /// public class WebSecurity { /// /// Returns true or false if the currently logged in member is authorized based on the parameters provided /// /// /// /// /// /// public bool IsMemberAuthorized( bool allowAll = false, IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { if (allowTypes == null) allowTypes = Enumerable.Empty(); if (allowGroups == null) allowGroups = Enumerable.Empty(); if (allowMembers == null) allowMembers = Enumerable.Empty(); // Allow by default var allowAction = true; // If not set to allow all, need to check current loggined in member if (!allowAll) { // Get member details var member = Member.GetCurrentMember(); if (member == null) { // If not logged on, not allowed allowAction = false; } else { // If types defined, check member is of one of those types if (allowTypes.Any()) { // Allow only if member's type is in list allowAction = allowTypes.Select(x => x.ToLowerInvariant()).Contains(member.ContentType.Alias.ToLowerInvariant()); } // If groups defined, check member is of one of those groups if (allowAction && allowGroups.Any()) { // Allow only if member's type is in list var groups = System.Web.Security.Roles.GetRolesForUser(member.LoginName); allowAction = groups.Select(s => s.ToLower()).Intersect(allowGroups).Any(); } // If specific members defined, check member is of one of those if (allowAction && allowMembers.Any()) { // Allow only if member's type is in list allowAction = allowMembers.Contains(member.Id); } } } return allowAction; } /// /// Gets the SQL helper. /// /// The SQL helper. private ISqlHelper SqlHelper { get { return Application.SqlHelper; } } private const long TicksPrMinute = 600000000; private static readonly int UmbracoTimeOutInMinutes = Core.Configuration.GlobalSettings.TimeOutInMinutes; private User _currentUser; /// /// Gets the current user. /// /// The current user. /// /// 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. /// internal User CurrentUser { get { //only load it once per instance! return _currentUser ?? (_currentUser = User.GetCurrent()); } } /// /// Logs a user in. /// /// The user Id public void PerformLogin(int userId) { var retVal = Guid.NewGuid(); SqlHelper.ExecuteNonQuery( "insert into umbracoUserLogins (contextID, userID, timeout) values (@contextId,'" + userId + "','" + (DateTime.Now.Ticks + (TicksPrMinute * UmbracoTimeOutInMinutes)).ToString() + "') ", SqlHelper.CreateParameter("@contextId", retVal)); UmbracoUserContextId = retVal.ToString(); LogHelper.Info(typeof(WebSecurity), "User Id: {0} logged in", () => userId); } /// /// Clears the current login for the currently logged in user /// public void ClearCurrentLogin() { // 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) { LogHelper.Error(typeof(WebSecurity), string.Format("Login with contextId {0} didn't exist in the database", UmbracoUserContextId), ex); } } public void RenewLoginTimeout() { // 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)); } /// /// Validates the user node tree permissions. /// /// /// The path. /// The action. /// internal bool ValidateUserNodeTreePermissions(User umbracoUser, string path, string action) { var permissions = umbracoUser.GetPermissions(path); if (permissions.IndexOf(action) > -1 && (path.Contains("-20") || ("," + path + ",").Contains("," + umbracoUser.StartNodeId.ToString() + ","))) return true; var user = umbracoUser; LogHelper.Info("User {0} has insufficient permissions in UmbracoEnsuredPage: '{1}', '{2}', '{3}'", () => user.Name, () => path, () => permissions, () => action); return false; } internal void UpdateLogin(long timeout) { // 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)); } internal long GetTimeout(string umbracoUserContextId) { return ApplicationContext.Current.ApplicationCache.GetCacheItem( CacheKeys.UserContextTimeoutCacheKey + umbracoUserContextId, new TimeSpan(0, UmbracoTimeOutInMinutes / 10, 0), () => GetTimeout(true)); } internal long GetTimeout(bool byPassCache) { if (UmbracoSettings.KeepUserLoggedIn) RenewLoginTimeout(); if (byPassCache) { return SqlHelper.ExecuteScalar("select timeout from umbracoUserLogins where contextId=@contextId", SqlHelper.CreateParameter("@contextId", new Guid(UmbracoUserContextId)) ); } return GetTimeout(UmbracoUserContextId); } /// /// Gets the user id. /// /// The umbraco user context ID. /// public int GetUserId(string umbracoUserContextId) { //need to parse to guid Guid gid; if (!Guid.TryParse(umbracoUserContextId, out gid)) { return -1; } var id = ApplicationContext.Current.ApplicationCache.GetCacheItem( CacheKeys.UserContextCacheKey + umbracoUserContextId, new TimeSpan(0, UmbracoTimeOutInMinutes/10, 0), () => SqlHelper.ExecuteScalar( "select userID from umbracoUserLogins where contextID = @contextId", SqlHelper.CreateParameter("@contextId", gid))); if (id == null) return -1; return id.Value; } /// /// Validates the user context ID. /// /// The umbraco user context ID. /// public bool ValidateUserContextId(string currentUmbracoUserContextId) { if ((currentUmbracoUserContextId != "")) { int uid = GetUserId(currentUmbracoUserContextId); long timeout = GetTimeout(currentUmbracoUserContextId); if (timeout > DateTime.Now.Ticks) { return true; } var user = User.GetUser(uid); LogHelper.Info(typeof(WebSecurity), "User {0} (Id:{1}) logged out", () => user.Name, () => user.Id); } return false; } /// /// Validates the current user /// /// /// internal ValidateUserAttempt ValidateCurrentUser(HttpContextBase httpContext) { if (UmbracoUserContextId != "") { var uid = GetUserId(UmbracoUserContextId); var timeout = GetTimeout(UmbracoUserContextId); if (timeout > DateTime.Now.Ticks) { var user = User.GetUser(uid); // Check for console access if (user.Disabled || (user.NoConsole && GlobalSettings.RequestIsInUmbracoApplication(httpContext) && !GlobalSettings.RequestIsLiveEditRedirector(httpContext))) { return ValidateUserAttempt.FailedNoPrivileges; //throw new ArgumentException("You have no priviledges to the umbraco console. Please contact your administrator"); } UpdateLogin(timeout); return ValidateUserAttempt.Success; } return ValidateUserAttempt.FailedTimedOut; //throw new ArgumentException("User has timed out!!"); } return ValidateUserAttempt.FailedNoContextId; //throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); } //TODO: Clean this up!! We also have extension methods in StringExtensions for decrypting/encrypting in med trust // ... though an existing cookie may fail decryption, in that case they'd just get logged out. no problems. /// /// Gets or sets the umbraco user context ID. /// /// The umbraco user context ID. public string UmbracoUserContextId { get { // zb-00004 #29956 : refactor cookies names & handling if (StateHelper.Cookies.HasCookies && StateHelper.Cookies.UserContext.HasValue) return StateHelper.Cookies.UserContext.GetValue(); try { var encTicket = StateHelper.Cookies.UserContext.GetValue(); if (!string.IsNullOrEmpty(encTicket)) return FormsAuthentication.Decrypt(encTicket).UserData; } catch (HttpException ex) { // we swallow this type of exception as it happens if a legacy (pre 4.8.1) cookie is set } catch (ArgumentException ex) { // we swallow this one because it's 99.99% certaincy is legacy based. We'll still log it, though LogHelper.Error(typeof(WebSecurity), "An error occurred reading auth cookie value", ex); } return ""; } set { // zb-00004 #29956 : refactor cookies names & handling if (StateHelper.Cookies.HasCookies) { // Clearing all old cookies before setting a new one. if (StateHelper.Cookies.UserContext.HasValue) StateHelper.Cookies.ClearAll(); if (!String.IsNullOrEmpty(value)) { var ticket = new FormsAuthenticationTicket(1, value, DateTime.Now, DateTime.Now.AddDays(1), false, value, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. var encTicket = FormsAuthentication.Encrypt(ticket); // Create new cookie. StateHelper.Cookies.UserContext.SetValue(value, 1); } else { StateHelper.Cookies.UserContext.Clear(); } } } } } }