using System; using System.Collections.Generic; using System.Linq; using System.Security; using System.Web; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Security { /// /// A utility class used for dealing with USER security in Umbraco /// public class WebSecurity { private readonly HttpContextBase _httpContext; private readonly IUserService _userService; private readonly IGlobalSettings _globalSettings; public WebSecurity(HttpContextBase httpContext, IUserService userService, IGlobalSettings globalSettings) { _httpContext = httpContext; _userService = userService; _globalSettings = globalSettings; } private IUser _currentUser; /// /// Gets the current user. /// /// The current user. public virtual IUser CurrentUser { get { //only load it once per instance! (but make sure groups are loaded) if (_currentUser == null) { var id = GetUserId(); _currentUser = id ? _userService.GetUserById(id.Result) : null; } return _currentUser; } } private BackOfficeSignInManager _signInManager; private BackOfficeSignInManager SignInManager { get { if (_signInManager == null) { var mgr = _httpContext.GetOwinContext().Get(); if (mgr == null) { throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); } _signInManager = mgr; } return _signInManager; } } private BackOfficeUserManager _userManager; protected BackOfficeUserManager UserManager => _userManager ?? (_userManager = _httpContext.GetOwinContext().GetBackOfficeUserManager()); /// /// Logs a user in. /// /// The user Id /// returns the number of seconds until their session times out public virtual double PerformLogin(int userId) { var owinCtx = _httpContext.GetOwinContext(); //ensure it's done for owin too owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); var user = UserManager.FindByIdAsync(userId).Result; SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); _httpContext.SetPrincipalForRequest(owinCtx.Request.User); return TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes).TotalSeconds; } /// /// Clears the current login for the currently logged in user /// public virtual void ClearCurrentLogin() { _httpContext.UmbracoLogout(); _httpContext.GetOwinContext().Authentication.SignOut( Core.Constants.Security.BackOfficeAuthenticationType, Core.Constants.Security.BackOfficeExternalAuthenticationType); } /// /// Renews the user's login ticket /// public virtual void RenewLoginTimeout() { _httpContext.RenewUmbracoAuthTicket(); } /// /// Validates credentials for a back office user /// /// /// /// /// /// This uses ASP.NET Identity to perform the validation /// public virtual bool ValidateBackOfficeCredentials(string username, string password) { //find the user by username var user = UserManager.FindByNameAsync(username).Result; return user != null && UserManager.CheckPasswordAsync(user, password).Result; } /// /// Validates the current user to see if they have access to the specified app /// /// /// internal bool ValidateUserApp(string app) { //if it is empty, don't validate if (app.IsNullOrWhiteSpace()) { return true; } return CurrentUser.AllowedSections.Any(uApp => uApp.InvariantEquals(app)); } /// /// Gets the current user's id. /// /// public virtual Attempt GetUserId() { var identity = _httpContext.GetCurrentIdentity(false); return identity == null ? Attempt.Fail() : Attempt.Succeed(Convert.ToInt32(identity.Id)); } /// /// Returns the current user's unique session id - used to mitigate csrf attacks or any other reason to validate a request /// /// public virtual string GetSessionId() { var identity = _httpContext.GetCurrentIdentity(false); return identity?.SessionId; } /// /// Validates the currently logged in user and ensures they are not timed out /// /// public virtual bool ValidateCurrentUser() { return ValidateCurrentUser(false, true) == ValidateRequestAttempt.Success; } /// /// Validates the current user assigned to the request and ensures the stored user data is valid /// /// set to true if you want exceptions to be thrown if failed /// If true requires that the user is approved to be validated /// public virtual ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true) { //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. if (IsAuthenticated() == false) { //There is no user if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); return ValidateRequestAttempt.FailedNoContextId; } var user = CurrentUser; // Check for console access if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContext))) { if (throwExceptions) throw new ArgumentException("You have no privileges to the umbraco console. Please contact your administrator"); return ValidateRequestAttempt.FailedNoPrivileges; } return ValidateRequestAttempt.Success; } private static bool RequestIsInUmbracoApplication(HttpContextBase context) { return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower(), StringComparison.Ordinal) > -1; } /// /// Authorizes the full request, checks for SSL and validates the current user /// /// set to true if you want exceptions to be thrown if failed /// internal ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) { // check for secure connection if (_globalSettings.UseHttps && _httpContext.Request.IsSecureConnection == false) { if (throwExceptions) throw new SecurityException("This installation requires a secure connection (via SSL). Please update the URL to include https://"); return ValidateRequestAttempt.FailedNoSsl; } return ValidateCurrentUser(throwExceptions); } /// /// Checks if the specified user as access to the app /// /// /// /// internal virtual bool UserHasSectionAccess(string section, IUser user) { return user.HasSectionAccess(section); } /// /// Checks if the specified user by username as access to the app /// /// /// /// internal bool UserHasSectionAccess(string section, string username) { var user = _userService.GetByUsername(username); if (user == null) { return false; } return user.HasSectionAccess(section); } /// /// Ensures that a back office user is logged in /// /// public bool IsAuthenticated() { return _httpContext.User != null && _httpContext.User.Identity.IsAuthenticated && _httpContext.GetCurrentIdentity(false) != null; } } }