using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Web; using System.Web.Security; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using umbraco.businesslogic.Exceptions; using Umbraco.Core.Models.Identity; using Umbraco.Web.Models.ContentEditing; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using User = umbraco.BusinessLogic.User; namespace Umbraco.Web.Security { /// /// A utility class used for dealing with USER security in Umbraco /// public class WebSecurity : DisposableObject { private HttpContextBase _httpContext; private ApplicationContext _applicationContext; public WebSecurity(HttpContextBase httpContext, ApplicationContext applicationContext) { _httpContext = httpContext; _applicationContext = applicationContext; } /// /// Returns true or false if the currently logged in member is authorized based on the parameters provided /// /// /// /// /// /// [Obsolete("Use MembershipHelper.IsMemberAuthorized instead")] public bool IsMemberAuthorized( bool allowAll = false, IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { if (HttpContext.Current == null || ApplicationContext.Current == null) { return false; } var helper = new MembershipHelper(ApplicationContext.Current, new HttpContextWrapper(HttpContext.Current)); return helper.IsMemberAuthorized(allowAll, allowTypes, allowGroups, allowMembers); } 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(); if (id == -1) { return null; } _currentUser = _applicationContext.Services.UserService.GetUserById(id); } 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 { get { return _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; var userData = Mapper.Map(user); _httpContext.SetPrincipalForRequest(userData); SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); return TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; } [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")] public virtual FormsAuthenticationTicket PerformLogin(IUser user) { //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); var externalLoginCookie = _httpContext.Request.Cookies.Get(Constants.Security.BackOfficeExternalCookieName); if (externalLoginCookie != null) { externalLoginCookie.Expires = DateTime.Now.AddYears(-1); _httpContext.Response.Cookies.Set(externalLoginCookie); } //ensure it's done for owin too _httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); var ticket = _httpContext.CreateUmbracoAuthTicket(Mapper.Map(user)); return ticket; } /// /// 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 /// /// /// /// public virtual bool ValidateBackOfficeCredentials(string username, string password) { var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); return membershipProvider != null && membershipProvider.ValidateUser(username, password); } /// /// Returns the MembershipUser from the back office membership provider /// /// /// /// public virtual MembershipUser GetBackOfficeMembershipUser(string username, bool setOnline) { var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); return membershipProvider != null ? membershipProvider.GetUser(username, setOnline) : null; } /// /// Gets (and creates if not found) the back office instance for the username specified /// /// /// /// /// This will return an instance no matter what membership provider is installed for the back office, it will automatically /// create any missing accounts if one is not found and a custom membership provider or is being used. /// internal IUser GetBackOfficeUser(string username) { //get the membership user (set user to be 'online' in the provider too) var membershipUser = GetBackOfficeMembershipUser(username, true); var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); if (membershipUser == null) { throw new InvalidOperationException( "The username & password validated but the membership provider '" + provider.Name + "' 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 var user = _applicationContext.Services.UserService.GetByUsername(membershipUser.UserName); //we're using the built-in membership provider so the user will already be available if (provider.IsUmbracoUsersProvider()) { 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"; } user = new Core.Models.Membership.User { Email = email, Language = GlobalSettings.DefaultUILanguage, Name = membershipUser.UserName, RawPasswordValue = Guid.NewGuid().ToString("N"), //Need to set this to something - will not be used though Username = membershipUser.UserName, IsLockedOut = false, IsApproved = true }; _applicationContext.Services.UserService.Save(user); return user; } /// /// 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, StringComparison.Ordinal) > -1 && (path.Contains("-20") || ("," + path + ",").Contains("," + umbracoUser.StartNodeId + ","))) return true; var user = umbracoUser; LogHelper.Info("User {0} has insufficient permissions in UmbracoEnsuredPage: '{1}', '{2}', '{3}'", () => user.Name, () => path, () => permissions, () => action); return false; } /// /// 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 user id. /// /// This is not used /// [Obsolete("This method is no longer used, use the GetUserId() method without parameters instead")] public int GetUserId(string umbracoUserContextId) { return GetUserId(); } /// /// Gets the currnet user's id. /// /// public virtual int GetUserId() { var identity = _httpContext.GetCurrentIdentity(false); if (identity == null) return -1; return 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); if (identity == null) return null; return identity.SessionId; } /// /// Validates the user context ID. /// /// This doesn't do anything /// [Obsolete("This method is no longer used, use the ValidateCurrentUser() method instead")] public bool ValidateUserContextId(string currentUmbracoUserContextId) { return ValidateCurrentUser(); } /// /// 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 && GlobalSettings.RequestIsInUmbracoApplication(_httpContext))) { if (throwExceptions) throw new ArgumentException("You have no priviledges to the umbraco console. Please contact your administrator"); return ValidateRequestAttempt.FailedNoPrivileges; } return ValidateRequestAttempt.Success; } /// /// 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.UseSSL && _httpContext.Request.IsSecureConnection == false) { if (throwExceptions) throw new UserAuthorizationException("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 UserHasAppAccess(string app, IUser user) { var apps = user.AllowedSections; 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")] internal bool UserHasAppAccess(string app, User user) { return user.Applications.Any(uApp => uApp.alias == app); } /// /// Checks if the specified user by username as access to the app /// /// /// /// internal bool UserHasAppAccess(string app, string username) { var user = _applicationContext.Services.UserService.GetByUsername(username); if (user == null) { return false; } return UserHasAppAccess(app, user); } [Obsolete("Returns the current user's unique umbraco sesion id - this cannot be set and isn't intended to be used in your code")] public string UmbracoUserContextId { get { return _httpContext.GetUmbracoAuthTicket() == null ? "" : GetSessionId(); } set { } } /// /// Ensures that a back office user is logged in /// /// public bool IsAuthenticated() { return _httpContext.User.Identity.IsAuthenticated && _httpContext.GetCurrentIdentity(false) != null; } protected override void DisposeResources() { _httpContext = null; } } }