diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index a5a2a4eb60..748e90469d 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -63,9 +63,9 @@ namespace Umbraco.Web.Cache // fixme - not sure I like these? TagsValueConverter.ClearCaches(); - MultipleMediaPickerPropertyConverter.ClearCaches(); + MediaPickerLegacyValueConverter.ClearCaches(); SliderValueConverter.ClearCaches(); - MediaPickerPropertyConverter.ClearCaches(); + MediaPickerValueConverter.ClearCaches(); // notify _facadeService.Notify(payloads); diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 057c77cf04..4777cad6da 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -50,6 +50,60 @@ namespace Umbraco.Web.Editors get { return _signInManager ?? (_signInManager = TryGetOwinContext().Result.GetBackOfficeSignInManager()); } } + /// + /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog + /// + /// + [WebApi.UmbracoAuthorize(requireApproval: false)] + public IDictionary GetMembershipProviderConfig() + { + //TODO: Check if the current PasswordValidator is an IMembershipProviderPasswordValidator, if + //it's not than we should return some generic defaults + var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); + return provider.GetConfiguration(Services.UserService); + } + + /// + /// Checks if a valid token is specified for an invited user and if so logs the user in and returns the user object + /// + /// + /// + /// + /// + /// This will also update the security stamp for the user so it can only be used once + /// + [ValidateAngularAntiForgeryToken] + public async Task PostVerifyInvite([FromUri]int id, [FromUri]string token) + { + if (string.IsNullOrWhiteSpace(token)) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var decoded = token.FromUrlBase64(); + if (decoded.IsNullOrWhiteSpace()) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var identityUser = await UserManager.FindByIdAsync(id); + if (identityUser == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var result = await UserManager.ConfirmEmailAsync(id, decoded); + + if (result.Succeeded == false) + { + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(string.Join(", ", result.Errors))); + } + + Request.TryGetOwinContext().Result.Authentication.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); + + await SignInManager.SignInAsync(identityUser, false, false); + + var user = ApplicationContext.Services.UserService.GetUserById(id); + + return Mapper.Map(user); + } + [WebApi.UmbracoAuthorize] [ValidateAngularAntiForgeryToken] public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel) @@ -97,9 +151,10 @@ namespace Umbraco.Web.Editors /// [WebApi.UmbracoAuthorize] [SetAngularAntiForgeryTokens] + [CheckIfUserTicketDataIsStale] public UserDetail GetCurrentUser() { - var user = Services.UserService.GetUserById(UmbracoContext.Security.GetUserId()); + var user = UmbracoContext.Security.CurrentUser; var result = Mapper.Map(user); var httpContextAttempt = TryGetHttpContext(); if (httpContextAttempt.Success) @@ -111,6 +166,38 @@ namespace Umbraco.Web.Editors return result; } + /// + /// When a user is invited they are not approved but we need to resolve the partially logged on (non approved) + /// user. + /// + /// + /// + /// We cannot user GetCurrentUser since that requires they are approved, this is the same as GetCurrentUser but doesn't require them to be approved + /// + [WebApi.UmbracoAuthorize(requireApproval:false)] + [SetAngularAntiForgeryTokens] + public UserDetail GetCurrentInvitedUser() + { + var user = UmbracoContext.Security.CurrentUser; + + if (user.IsApproved) + { + //if they are approved, than they are no longer invited and we can return an error + throw new HttpResponseException(Request.CreateUserNoAccessResponse()); + } + + var result = Mapper.Map(user); + var httpContextAttempt = TryGetHttpContext(); + if (httpContextAttempt.Success) + { + //set their remaining seconds + result.SecondsUntilTimeout = httpContextAttempt.Result.GetRemainingAuthSeconds(); + } + + return result; + } + + //TODO: This should be on the CurrentUserController? [WebApi.UmbracoAuthorize] [ValidateAngularAntiForgeryToken] public async Task> GetCurrentUserLinkedLogins() @@ -128,6 +215,8 @@ namespace Umbraco.Web.Editors { var http = EnsureHttpContext(); + //Sign the user in with username/password, this also gives a chance for developers to + //custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker var result = await SignInManager.PasswordSignInAsync( loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); @@ -136,7 +225,7 @@ namespace Umbraco.Web.Editors case SignInStatus.Success: //get the user - var user = Security.GetBackOfficeUser(loginModel.Username); + var user = Services.UserService.GetByUsername(loginModel.Username); return SetPrincipalAndReturnUserDetail(user); case SignInStatus.RequiresVerification: @@ -162,7 +251,7 @@ namespace Umbraco.Web.Editors typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); } - var attemptedUser = Security.GetBackOfficeUser(loginModel.Username); + var attemptedUser = Services.UserService.GetByUsername(loginModel.Username); //create a with information to display a custom two factor send code view var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new @@ -281,7 +370,7 @@ namespace Umbraco.Web.Editors { case SignInStatus.Success: //get the user - var user = Security.GetBackOfficeUser(userName); + var user = Services.UserService.GetByUsername(userName); return SetPrincipalAndReturnUserDetail(user); case SignInStatus.LockedOut: return Request.CreateValidationErrorResponse("User is locked out"); @@ -290,7 +379,7 @@ namespace Umbraco.Web.Editors return Request.CreateValidationErrorResponse("Invalid code"); } } - + /// /// Processes a set password request. Validates the request and sets a new password. /// diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 8f1ff94a16..99f9838e7e 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Globalization; using System.IO; using System.Linq; @@ -8,10 +7,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; -using System.Web.Configuration; using System.Web.Mvc; using System.Web.UI; -using ClientDependency.Core.Config; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; @@ -20,24 +17,19 @@ using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; -using Umbraco.Web.HealthCheck; using Umbraco.Web.Models; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; -using Umbraco.Web.PropertyEditors; using Umbraco.Web.Security.Identity; using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.WebServices; using Umbraco.Core.Services; using Umbraco.Web.Composing; -using Umbraco.Web.Security; using Action = Umbraco.Web._Legacy.Actions.Action; using Constants = Umbraco.Core.Constants; @@ -85,6 +77,66 @@ namespace Umbraco.Web.Editors () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel { Path = GlobalSettings.Path })); } + [HttpGet] + public async Task VerifyInvite(string invite) + { + if (invite == null) + { + Logger.Warn("VerifyUser endpoint reached with invalid token: NULL"); + return RedirectToAction("Default"); + } + + var parts = Server.UrlDecode(invite).Split('|'); + + if (parts.Length != 2) + { + Logger.Warn("VerifyUser endpoint reached with invalid token: " + invite); + return RedirectToAction("Default"); + } + + var token = parts[1]; + + var decoded = token.FromUrlBase64(); + if (decoded.IsNullOrWhiteSpace()) + { + Logger.Warn("VerifyUser endpoint reached with invalid token: " + invite); + return RedirectToAction("Default"); + } + + var id = parts[0]; + int intId; + if (int.TryParse(id, out intId) == false) + { + Logger.Warn("VerifyUser endpoint reached with invalid token: " + invite); + return RedirectToAction("Default"); + } + + var identityUser = await UserManager.FindByIdAsync(intId); + if (identityUser == null) + { + Logger.Warn("VerifyUser endpoint reached with non existing user: " + id); + return RedirectToAction("Default"); + } + + var result = await UserManager.ConfirmEmailAsync(intId, decoded); + + if (result.Succeeded == false) + { + Logger.Warn("Could not verify email, Error: " + string.Join(",", result.Errors) + ", Token: " + invite); + return RedirectToAction("Default"); + } + + //sign the user in + + AuthenticationManager.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); + + await SignInManager.SignInAsync(identityUser, false, false); + + return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1"); + } + /// /// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to /// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen. @@ -189,12 +241,7 @@ namespace Umbraco.Web.Editors return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.Indented }; } - private string GetMaxRequestLength() - { - var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; - if (section == null) return string.Empty; - return section.MaxRequestLength.ToString(); - } + /// /// Returns the JavaScript object representing the static server variables javascript object @@ -204,263 +251,21 @@ namespace Umbraco.Web.Editors [MinifyJavaScriptResult(Order = 1)] public JavaScriptResult ServerVariables() { - Func getResult = () => - { - var defaultVals = new Dictionary - { - { - "umbracoUrls", new Dictionary - { - //TODO: Add 'umbracoApiControllerBaseUrl' which people can use in JS - // to prepend their URL. We could then also use this in our own resources instead of - // having each url defined here explicitly - we can do that in v8! for now - // for umbraco services we'll stick to explicitly defining the endpoints. - - {"externalLoginsUrl", Url.Action("ExternalLogin", "BackOffice")}, - {"externalLinkLoginsUrl", Url.Action("LinkLogin", "BackOffice")}, - {"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")}, - {"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")}, - {"gridConfig", Url.Action("GetGridConfig", "BackOffice")}, - {"serverVarsJs", Url.Action("Application", "BackOffice")}, - //API URLs - { - "packagesRestApiBaseUrl", UmbracoConfig.For.UmbracoSettings().PackageRepositories.GetDefault().RestApiUrl - }, - { - "redirectUrlManagementApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEnableState()) - }, - { - "embedApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEmbed("", 0, 0)) - }, - { - "userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostDisableUser(0)) - }, - { - "contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostSave(null)) - }, - { - "mediaApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetRootMedia()) - }, - { - "imagesApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetBigThumbnail(0)) - }, - { - "sectionApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetSections()) - }, - { - "treeApplicationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetApplicationTrees(null, null, null, true)) - }, - { - "contentTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren(0)) - }, - { - "mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren(0)) - }, - { - "macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetMacroParameters(0)) - }, - { - "authenticationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostLogin(null)) - }, - { - "currentUserApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetMembershipProviderConfig()) - }, - { - "legacyApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.DeleteLegacyItem(null, null, null)) - }, - { - "entityApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0, UmbracoEntityTypes.Media)) - }, - { - "dataTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, - { - "dashboardApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetDashboard(null)) - }, - { - "logApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEntityLog(0)) - }, - { - "gravatarApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCurrentUserGravatarUrl()) - }, - { - "memberApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetByKey(Guid.Empty)) - }, - { - "packageInstallApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.Fetch(string.Empty)) - }, - { - "relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, - { - "rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetConfiguration()) - }, - { - "stylesheetApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAll()) - }, - { - "memberTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllTypes()) - }, - { - "memberGroupApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllGroups()) - }, - { - "updateCheckApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCheck()) - }, - { - "tagApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllTags(null)) - }, - { - "templateApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, - { - "memberTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "mediaTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "contentTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "tagsDataBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetTags("")) - }, - { - "examineMgmtBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetIndexerDetails()) - }, - { - "xmlDataIntegrityBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.CheckContentXmlTable()) - }, - { - "healthCheckBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllHealthChecks()) - }, - { - "templateQueryApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostTemplateQuery(null)) - }, - { - "codeFileApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetByPath("", "")) - }, - { - "facadeStatusBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetFacadeStatusUrl()) - }, - { - "nuCacheStatusBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetStatus()) - } - } - }, - { - "umbracoSettings", new Dictionary - { - {"umbracoPath", GlobalSettings.Path}, - {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, - {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, - { - "imageFileTypes", - string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) - }, - { - "disallowedUploadFiles", - string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles) - }, - { - "allowedUploadFiles", - string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.AllowedUploadFiles) - }, - { - "maxFileSize", - GetMaxRequestLength() - }, - {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, - {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, - {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, - {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, - } - }, - { - "umbracoPlugins", new Dictionary - { - {"trees", GetTreePluginsMetaData()} - } - }, - { - "isDebuggingEnabled", HttpContext.IsDebuggingEnabled - }, - { - "application", GetApplicationState() - }, - { - "externalLogins", new Dictionary - { - { - "providers", HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() - .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) - .Select(p => new - { - authType = p.AuthenticationType, caption = p.Caption, - //TODO: Need to see if this exposes any sensitive data! - properties = p.Properties - }) - .ToArray() - } - } - } - }; - - //Parse the variables to a string - return ServerVariablesParser.Parse(defaultVals); - }; + var serverVars = new BackOfficeServerVariables(Url, ApplicationContext, UmbracoConfig.For.UmbracoSettings()); //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled - ? getResult() + ? ServerVariablesParser.Parse(serverVars.GetServerVariables()) : ApplicationCache.RuntimeCache.GetCacheItem( typeof(BackOfficeController) + "ServerVariables", - () => getResult(), + () => ServerVariablesParser.Parse(serverVars.GetServerVariables()), new TimeSpan(0, 10, 0)); return JavaScript(result); } + + [HttpPost] public ActionResult ExternalLogin(string provider, string redirectUrl = null) { @@ -642,60 +447,52 @@ namespace Umbraco.Web.Editors } else { - var defaultUserType = autoLinkOptions.GetDefaultUserType(UmbracoContext, loginInfo); - var userType = Services.UserService.GetUserTypeByAlias(defaultUserType); - if (userType == null) + if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null"); + if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null"); + + var groups = Services.UserService.GetUserGroupsByAlias(autoLinkOptions.GetDefaultUserGroups(UmbracoContext, loginInfo)); + + var autoLinkUser = BackOfficeIdentityUser.CreateNew( + loginInfo.Email, + loginInfo.Email, + autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo)); + autoLinkUser.Name = loginInfo.ExternalIdentity.Name; + foreach (var userGroup in groups) { - ViewData[TokenExternalSignInError] = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; + autoLinkUser.AddRole(userGroup.Alias); + } + + //call the callback if one is assigned + if (autoLinkOptions.OnAutoLinking != null) + { + autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); + } + + var userCreationResult = await UserManager.CreateAsync(autoLinkUser); + + if (userCreationResult.Succeeded == false) + { + ViewData[TokenExternalSignInError] = userCreationResult.Errors; } else { - - if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null"); - if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null"); - - var autoLinkUser = new BackOfficeIdentityUser() + var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); + if (linkResult.Succeeded == false) { - Email = loginInfo.Email, - Name = loginInfo.ExternalIdentity.Name, - UserTypeAlias = userType.Alias, - AllowedSections = autoLinkOptions.GetDefaultAllowedSections(UmbracoContext, loginInfo), - Culture = autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo), - UserName = loginInfo.Email - }; + ViewData[TokenExternalSignInError] = linkResult.Errors; - //call the callback if one is assigned - if (autoLinkOptions.OnAutoLinking != null) - { - autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo); - } - - var userCreationResult = await UserManager.CreateAsync(autoLinkUser); - - if (userCreationResult.Succeeded == false) - { - ViewData[TokenExternalSignInError] = userCreationResult.Errors; + //If this fails, we should really delete the user since it will be in an inconsistent state! + var deleteResult = await UserManager.DeleteAsync(autoLinkUser); + if (deleteResult.Succeeded == false) + { + //DOH! ... this isn't good, combine all errors to be shown + ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); + } } else { - var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); - if (linkResult.Succeeded == false) - { - ViewData[TokenExternalSignInError] = linkResult.Errors; - - //If this fails, we should really delete the user since it will be in an inconsistent state! - var deleteResult = await UserManager.DeleteAsync(autoLinkUser); - if (deleteResult.Succeeded == false) - { - //DOH! ... this isn't good, combine all errors to be shown - ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); - } - } - else - { - //sign in - await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); - } + //sign in + await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false); } } } @@ -708,59 +505,6 @@ namespace Umbraco.Web.Editors return false; } - /// - /// Returns the server variables regarding the application state - /// - /// - private Dictionary GetApplicationState() - { - if (_runtime.Level != RuntimeLevel.Run) - return null; - - return new Dictionary - { - // assembly version - { "assemblyVersion", UmbracoVersion.AssemblyVersion }, - // Umbraco version - { "version", _runtime.SemanticVersion.ToSemanticString() }, - // client dependency version, - { "cdf", ClientDependencySettings.Instance.Version }, - // for dealing with virtual paths on the client side when hosted in virtual directories - { "applicationPath", _runtime.ApplicationVirtualPath.EnsureEndsWith('/') }, - // server's GMT time offset in minutes - { "serverTimeOffset", Convert.ToInt32(DateTimeOffset.Now.Offset.TotalMinutes) } - }; - } - - - private IEnumerable> GetTreePluginsMetaData() - { - var treeTypes = Current.TypeLoader.GetAttributedTreeControllers(); // fixme inject - //get all plugin trees with their attributes - var treesWithAttributes = treeTypes.Select(x => new - { - tree = x, - attributes = - x.GetCustomAttributes(false) - }).ToArray(); - - var pluginTreesWithAttributes = treesWithAttributes - //don't resolve any tree decorated with CoreTreeAttribute - .Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false)) - //we only care about trees with the PluginControllerAttribute - .Where(x => x.attributes.Any(a => a is PluginControllerAttribute)) - .ToArray(); - - return (from p in pluginTreesWithAttributes - let treeAttr = p.attributes.OfType().Single() - let pluginAttr = p.attributes.OfType().Single() - select new Dictionary - { - {"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName} - }).ToArray(); - - } - /// /// Returns the JavaScript blocks for any legacy trees declared /// diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs new file mode 100644 index 0000000000..66aaccd96a --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Web; +using System.Web.Configuration; +using System.Web.Mvc; +using ClientDependency.Core.Config; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.Trees; +using Umbraco.Web.WebServices; + +namespace Umbraco.Web.Editors +{ + /// + /// Used to collect the server variables for use in the back office angular app + /// + internal class BackOfficeServerVariables + { + private readonly UrlHelper _urlHelper; + private readonly IRuntimeState _runtimeState; + private readonly HttpContextBase _httpContext; + private readonly IOwinContext _owinContext; + + public BackOfficeServerVariables(UrlHelper urlHelper, IRuntimeState runtimeState) + { + _urlHelper = urlHelper; + _runtimeState = runtimeState; + _httpContext = _urlHelper.RequestContext.HttpContext; + _owinContext = _httpContext.GetOwinContext(); + } + + /// + /// Returns the server variables for non-authenticated users + /// + /// + internal Dictionary BareMinimumServerVariables() + { + //this is the filter for the keys that we'll keep based on the full version of the server vars + var keepOnlyKeys = new Dictionary + { + {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"application", new[] {"applicationPath", "cacheBuster"}}, + {"isDebuggingEnabled", new string[] { }} + }; + //now do the filtering... + var defaults = GetServerVariables(); + foreach (var key in defaults.Keys.ToArray()) + { + if (keepOnlyKeys.ContainsKey(key) == false) + { + defaults.Remove(key); + } + else + { + var asDictionary = defaults[key] as IDictionary; + if (asDictionary != null) + { + var toKeep = keepOnlyKeys[key]; + foreach (var k in asDictionary.Keys.Cast().ToArray()) + { + if (toKeep.Contains(k) == false) + { + asDictionary.Remove(k); + } + } + } + } + } + + //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address + // so based on compat and how things are currently working we need to replace the serverVarsJs one + ((Dictionary) defaults["umbracoUrls"])["serverVarsJs"] = _urlHelper.Action("ServerVariables", "BackOffice"); + + return defaults; + } + + /// + /// Returns the server variables for authenticated users + /// + /// + internal Dictionary GetServerVariables() + { + var defaultVals = new Dictionary + { + { + "umbracoUrls", new Dictionary + { + //TODO: Add 'umbracoApiControllerBaseUrl' which people can use in JS + // to prepend their URL. We could then also use this in our own resources instead of + // having each url defined here explicitly - we can do that in v8! for now + // for umbraco services we'll stick to explicitly defining the endpoints. + + {"externalLoginsUrl", _urlHelper.Action("ExternalLogin", "BackOffice")}, + {"externalLinkLoginsUrl", _urlHelper.Action("LinkLogin", "BackOffice")}, + {"legacyTreeJs", _urlHelper.Action("LegacyTreeJs", "BackOffice")}, + {"manifestAssetList", _urlHelper.Action("GetManifestAssetList", "BackOffice")}, + {"gridConfig", _urlHelper.Action("GetGridConfig", "BackOffice")}, + //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address + {"serverVarsJs", _urlHelper.Action("Application", "BackOffice")}, + //API URLs + { + "packagesRestApiBaseUrl", UmbracoConfig.For.UmbracoSettings().PackageRepositories.GetDefault().RestApiUrl + }, + { + "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetEnableState()) + }, + { + "embedApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetEmbed("", 0, 0)) + }, + { + "userApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostSaveUser(null)) + }, + { + "userGroupsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostSaveUserGroup(null)) + }, + { + "contentApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostSave(null)) + }, + { + "mediaApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetRootMedia()) + }, + { + "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetBigThumbnail(0)) + }, + { + "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSections()) + }, + { + "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetApplicationTrees(null, null, null, true)) + }, + { + "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllowedChildren(0)) + }, + { + "mediaTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllowedChildren(0)) + }, + { + "macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetMacroParameters(0)) + }, + { + "authenticationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostLogin(null)) + }, + { + "currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostChangePassword(null)) + }, + { + "legacyApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.DeleteLegacyItem(null, null, null)) + }, + { + "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0, UmbracoEntityTypes.Media)) + }, + { + "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, + { + "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetDashboard(null)) + }, + { + "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetEntityLog(0)) + }, + { + "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetByKey(Guid.Empty)) + }, + { + "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.Fetch(string.Empty)) + }, + { + "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, + { + "rteApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetConfiguration()) + }, + { + "stylesheetApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAll()) + }, + { + "memberTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllTypes()) + }, + { + "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetCheck()) + }, + { + "tagApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllTags(null)) + }, + { + "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, + { + "memberTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "mediaTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "contentTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetTags("")) + }, + { + "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetIndexerDetails()) + }, + { + "xmlDataIntegrityBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.CheckContentXmlTable()) + }, + { + "healthCheckBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllHealthChecks()) + }, + { + "templateQueryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.PostTemplateQuery(null)) + }, + { + "codeFileApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetByPath("", "")) + }, + { + "facadeStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetFacadeStatusUrl()) + }, + { + "nuCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetStatus()) + } + } + }, + { + "umbracoSettings", new Dictionary + { + {"umbracoPath", GlobalSettings.Path}, + {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, + {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, + { + "imageFileTypes", + string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) + }, + { + "disallowedUploadFiles", + string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles) + }, + { + "allowedUploadFiles", + string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.AllowedUploadFiles) + }, + { + "maxFileSize", + GetMaxRequestLength() + }, + {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, + {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, + {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, + {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, + {"emailServerConfigured", GlobalSettings.HasSmtpServerConfigured(_httpContext.Request.ApplicationPath)}, + } + }, + { + "umbracoPlugins", new Dictionary + { + {"trees", GetTreePluginsMetaData()} + } + }, + { + "isDebuggingEnabled", _httpContext.IsDebuggingEnabled + }, + { + "application", GetApplicationState() + }, + { + "externalLogins", new Dictionary + { + { + "providers", _owinContext.Authentication.GetExternalAuthenticationTypes() + .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) + .Select(p => new + { + authType = p.AuthenticationType, caption = p.Caption, + //TODO: Need to see if this exposes any sensitive data! + properties = p.Properties + }) + .ToArray() + } + } + } + }; + return defaultVals; + } + + private IEnumerable> GetTreePluginsMetaData() + { + var treeTypes = TreeControllerTypes.Value; + //get all plugin trees with their attributes + var treesWithAttributes = treeTypes.Select(x => new + { + tree = x, + attributes = + x.GetCustomAttributes(false) + }).ToArray(); + + var pluginTreesWithAttributes = treesWithAttributes + //don't resolve any tree decorated with CoreTreeAttribute + .Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false)) + //we only care about trees with the PluginControllerAttribute + .Where(x => x.attributes.Any(a => a is PluginControllerAttribute)) + .ToArray(); + + return (from p in pluginTreesWithAttributes + let treeAttr = p.attributes.OfType().Single() + let pluginAttr = p.attributes.OfType().Single() + select new Dictionary + { + {"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName} + }).ToArray(); + + } + + /// + /// A lazy reference to all tree controller types + /// + /// + /// We are doing this because if we constantly resolve the tree controller types from the PluginManager it will re-scan and also re-log that + /// it's resolving which is unecessary and annoying. + /// + private static readonly Lazy> TreeControllerTypes + = new Lazy>(() => Current.TypeLoader.GetAttributedTreeControllers().ToArray()); // fixme inject + + /// + /// Returns the server variables regarding the application state + /// + /// + private Dictionary GetApplicationState() + { + if (_runtimeState.Level != RuntimeLevel.Run) + return null; + + var app = new Dictionary + { + {"assemblyVersion", UmbracoVersion.AssemblyVersion} + }; + + var version = _runtimeState.SemanticVersion.ToSemanticString(); + + app.Add("cacheBuster", $"{version}.{ClientDependencySettings.Instance.Version}".GenerateHash()); + app.Add("version", version); + + //useful for dealing with virtual paths on the client side when hosted in virtual directories especially + app.Add("applicationPath", _httpContext.Request.ApplicationPath.EnsureEndsWith('/')); + + //add the server's GMT time offset in minutes + app.Add("serverTimeOffset", Convert.ToInt32(DateTimeOffset.Now.Offset.TotalMinutes)); + + return app; + } + + private static string GetMaxRequestLength() + { + return ConfigurationManager.GetSection("system.web/httpRuntime") is HttpRuntimeSection section + ? section.MaxRequestLength.ToString() + : string.Empty; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b8d03985d1..a7adf07049 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -9,6 +9,7 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; using AutoMapper; +using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -23,6 +24,7 @@ using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Persistence.Querying; using Umbraco.Web.PublishedCache; +using Umbraco.Core.Events; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -71,6 +73,120 @@ namespace Umbraco.Web.Editors return foundContent.Select(Mapper.Map); } + /// + /// Updates the permissions for a content item for a particular user group + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to update + /// + [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] + public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) + { + if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var content = Services.ContentService.GetById(saveModel.ContentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //current permissions explicitly assigned to this content item + var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) + .ToDictionary(x => x.UserGroupId, x => x); + + var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); + + //loop through each user group + foreach (var userGroup in allUserGroups) + { + //check if there's a permission set posted up for this user group + IEnumerable groupPermissions; + if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) + { + //create a string collection of the assigned letters + var groupPermissionCodes = groupPermissions.ToArray(); + + //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions + //for this group/node which will go back to the defaults + if (groupPermissionCodes.Length == 0) + { + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored + else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) + { + //only remove them if they are actually currently assigned + if (contentPermissions.ContainsKey(userGroup.Id)) + { + //remove these permissions from this node for this group since the ones being assigned are the same as the defaults + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + } + //if they are different we need to update, otherwise there's nothing to update + else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) + { + + Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); + } + } + } + + return GetDetailedPermissions(content, allUserGroups); + } + + /// + /// Returns the user group permissions for user groups assigned to this node + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to view + /// + [EnsureUserPermissionForContent("contentId", 'R')] + public IEnumerable GetDetailedPermissions(int contentId) + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var allUserGroups = Services.UserService.GetAllUserGroups(); + + return GetDetailedPermissions(content, allUserGroups); + } + + private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) + { + //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. + //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. + + var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); + + var defaultPermissionsAsDictionary = defaultPermissionsByGroup + .ToDictionary(x => Convert.ToInt32(x.Id), x => x); + + //get the actual assigned permissions + var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); + + //iterate over assigned and update the defaults with the real values + foreach (var assignedGroupPermission in assignedPermissionsByGroup) + { + var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; + + //clone the default permissions model to the assigned ones + defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + } + + return defaultPermissionsByGroup; + } + /// /// Returns an item to be used to display the recycle bin for content /// @@ -94,6 +210,36 @@ namespace Umbraco.Web.Editors return display; } + public ContentItemDisplay GetBlueprintById(int id) + { + var foundContent = Services.ContentService.GetBlueprintById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = Mapper.Map(foundContent); + + SetupBlueprint(content, foundContent); + + return content; + } + + private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) + { + content.AllowPreview = false; + + //set a custom path since the tree that renders this has the content type id as the parent + content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); + + content.AllowedActions = new[] {"A"}; + + var excludeProps = new[] {"_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"}; + var propsTab = content.Tabs.Last(); + propsTab.Properties = propsTab.Properties + .Where(p => excludeProps.Contains(p.Alias) == false); + } + /// /// Gets the content json for the content id /// @@ -153,6 +299,26 @@ namespace Umbraco.Web.Editors return mapped; } + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(int blueprintId) + { + var blueprint = Services.ContentService.GetBlueprintById(blueprintId); + if (blueprint == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + blueprint.Id = 0; + blueprint.Name = string.Empty; + + var mapped = Mapper.Map(blueprint); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + /// /// Gets the Url for a given node ID /// @@ -253,6 +419,7 @@ namespace Umbraco.Web.Editors /// /// Returns permissions for all nodes passed in for the current user + /// TODO: This should be moved to the CurrentUserController? /// /// /// @@ -264,11 +431,18 @@ namespace Umbraco.Web.Editors .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); } + /// + /// Checks a nodes permission for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + /// [HttpGet] public bool HasPermission(string permissionToCheck, int nodeId) { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); - if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); + if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) { return true; } @@ -276,6 +450,69 @@ namespace Umbraco.Web.Editors return false; } + /// + /// Creates a blueprint from a content item + /// + /// The content id to copy + /// The name of the blueprint + /// + [HttpPost] + public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var content = Services.ContentService.GetById(contentId); + if (content == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + EnsureUniqueName(name, content, "name"); + + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId()); + + Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId()); + + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddSuccessNotification( + Services.TextService.Localize("blueprints/createdBlueprintHeading"), + Services.TextService.Localize("blueprints/createdBlueprintMessage", new[]{ content.Name}) + ); + + return notificationModel; + } + + private void EnsureUniqueName(string name, IContent content, string modelName) + { + var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); + if (existing.Any(x => x.Name == name && x.Id != content.Id)) + { + ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSaveBlueprint( + [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + { + var contentItemDisplay = PostSaveInternal(contentItem, + content => + { + EnsureUniqueName(content.Name, content, "Name"); + + Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); + //we need to reuse the underlying logic so return the result that it wants + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); + }); + SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); + + return contentItemDisplay; + } + /// /// Saves content /// @@ -285,6 +522,12 @@ namespace Umbraco.Web.Editors public ContentItemDisplay PostSave( [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + { + return PostSaveInternal(contentItem, + content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + } + + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) { //If we've reached here it means: // * Our model has been bound @@ -292,7 +535,6 @@ namespace Umbraco.Web.Editors // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - MapPropertyValues(contentItem); //We need to manually check the validation results here because: @@ -332,7 +574,7 @@ namespace Umbraco.Web.Editors if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) { //save the item - var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + var saveResult = saveMethod(contentItem.PersistedContent); wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; } @@ -362,8 +604,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); } else { @@ -375,8 +617,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); } else { @@ -399,6 +641,8 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); } + display.PersistedContent = contentItem.PersistedContent; + return display; } @@ -435,6 +679,22 @@ namespace Umbraco.Web.Editors } + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteBlueprint(int id) + { + var found = Services.ContentService.GetBlueprintById(id); + + if (found == null) + { + return HandleContentNotFound(id, false); + } + + Services.ContentService.DeleteBlueprint(found); + + return Request.CreateResponse(HttpStatusCode.OK); + } + /// /// Moves an item to the recycle bin, if it is already there then it will permanently delete it /// @@ -754,8 +1014,6 @@ namespace Umbraco.Web.Editors } } - - /// /// Performs a permissions check for the user to check if it has access to the node based on /// start node and/or permissions for the node @@ -764,6 +1022,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against @@ -773,10 +1032,16 @@ namespace Umbraco.Web.Editors IUser user, IUserService userService, IContentService contentService, + IEntityService entityService, int nodeId, char[] permissionsToCheck = null, IContent contentItem = null) { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (userService == null) throw new ArgumentNullException("userService"); + if (contentService == null) throw new ArgumentNullException("contentService"); + if (entityService == null) throw new ArgumentNullException("entityService"); if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) { @@ -790,35 +1055,33 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.NotFound); } - + var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? user.HasContentRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinContent) + ? user.HasContentBinAccess(entityService) + : user.HasPathAccess(contentItem, entityService); if (hasPathAccess == false) { return false; } - if (permissionsToCheck == null || permissionsToCheck.Any() == false) + if (permissionsToCheck == null || permissionsToCheck.Length == 0) { return true; } - var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + var path = contentItem != null ? contentItem.Path : nodeId.ToString(); + var permission = userService.GetPermissionsForPath(user, path); var allowed = true; foreach (var p in permissionsToCheck) { - if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) { allowed = false; } diff --git a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs index b1fc989cd2..806836cd53 100644 --- a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs @@ -23,18 +23,21 @@ namespace Umbraco.Web.Editors private readonly IContentService _contentService; private readonly WebSecurity _security; private readonly IUserService _userService; + private readonly IEntityService _entityService; public ContentPostValidateAttribute() { } - public ContentPostValidateAttribute(IContentService contentService, IUserService userService, WebSecurity security) + public ContentPostValidateAttribute(IContentService contentService, IUserService userService, IEntityService entityService, WebSecurity security) { if (contentService == null) throw new ArgumentNullException("contentService"); if (userService == null) throw new ArgumentNullException("userService"); + if (entityService == null) throw new ArgumentNullException("entityService"); if (security == null) throw new ArgumentNullException("security"); _contentService = contentService; _userService = userService; + _entityService = entityService; _security = security; } @@ -53,6 +56,11 @@ namespace Umbraco.Web.Editors get { return _userService ?? Current.Services.UserService; } } + private IEntityService EntityService + { + get { return _entityService ?? ApplicationContext.Current.Services.EntityService; } + } + public override bool AllowMultiple { get { return true; } @@ -140,7 +148,8 @@ namespace Umbraco.Web.Editors actionContext.Request.Properties, Security.CurrentUser, UserService, - ContentService, + ContentService, + EntityService, contentIdToCheck, permissionToCheck.ToArray(), contentToCheck) == false) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 6fdafddc87..ff21203d68 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -279,7 +279,18 @@ namespace Umbraco.Web.Editors { basic.Name = localizedTextService.UmbracoDictionaryTranslate(basic.Name); basic.Description = localizedTextService.UmbracoDictionaryTranslate(basic.Description); - } + } + + //map the blueprints + var blueprints = Services.ContentService.GetBlueprintsForContentTypes(types.Select(x => x.Id).ToArray()).ToArray(); + foreach (var basic in basics) + { + var docTypeBluePrints = blueprints.Where(x => x.ContentTypeId == (int) basic.Id).ToArray(); + foreach (var blueprint in docTypeBluePrints) + { + basic.Blueprints[blueprint.Id] = blueprint.Name; + } + } return basics; } diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 6a07c150fa..1dc431b069 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -1,11 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Web.Http; using Umbraco.Core.Services; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +using Umbraco.Core.Security; +using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -17,14 +19,54 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class CurrentUserController : UmbracoAuthorizedJsonController { + /// - /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog + /// When a user is invited and they click on the invitation link, they will be partially logged in + /// where they can set their username/password /// + /// /// - public IDictionary GetMembershipProviderConfig() + /// + /// This only works when the user is logged in (partially) + /// + [WebApi.UmbracoAuthorize(requireApproval: false)] + [OverrideAuthorization] + public async Task PostSetInvitedUserPassword([FromBody]string newPassword) { - var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return provider.GetConfiguration(Services.UserService); // fixme inject + var result = await UserManager.AddPasswordAsync(Security.GetUserId(), newPassword); + + if (result.Succeeded == false) + { + //it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form + // so that is why it is being used here. + ModelState.AddModelError( + "value", + string.Join(", ", result.Errors)); + + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + //They've successfully set their password, we can now update their user account to be approved + Security.CurrentUser.IsApproved = true; + Services.UserService.Save(Security.CurrentUser); + + //now we can return their full object since they are now really logged into the back office + var userDisplay = Mapper.Map(Security.CurrentUser); + var httpContextAttempt = TryGetHttpContext(); + if (httpContextAttempt.Success) + { + //set their remaining seconds + userDisplay.SecondsUntilTimeout = httpContextAttempt.Result.GetRemainingAuthSeconds(); + } + return userDisplay; + } + + [AppendUserModifiedHeader] + [FileUploadCleanupFilter(false)] + public async Task PostSetAvatar() + { + //borrow the logic from the user controller + return await UsersController.PostSetAvatarInternal(Request, Services.UserService, ApplicationContext.ApplicationCache.StaticCache, Security.GetUserId()); } /// @@ -34,17 +76,11 @@ namespace Umbraco.Web.Editors /// /// If the password is being reset it will return the newly reset password, otherwise will return an empty value /// - public ModelWithNotifications PostChangePassword(ChangingPasswordModel data) + public async Task> PostChangePassword(ChangingPasswordModel data) { - var userProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); + var passwordChanger = new PasswordChanger(Logger, Services.UserService); + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, data, ModelState, UserManager); - //TODO: WE need to support this! - requires UI updates, etc... - if (userProvider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); - } - - var passwordChangeResult = Members.ChangePassword(Security.CurrentUser.Username, data, userProvider); if (passwordChangeResult.Success) { //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword @@ -53,12 +89,6 @@ namespace Umbraco.Web.Editors return result; } - //it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form - // so that is why it is being used here. - ModelState.AddPropertyError( - passwordChangeResult.Result.ChangeError, - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index 0842ae7800..949a2752a9 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -36,13 +36,12 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.InternalServerError); var user = Security.CurrentUser; - var userType = user.UserType.Alias; var allowedSections = string.Join(",", user.AllowedSections); var language = user.Language; var version = UmbracoVersion.SemanticVersion.ToSemanticString(); - var url = string.Format(baseUrl + "{0}?section={0}&type={1}&allowed={2}&lang={3}&version={4}", section, userType, allowedSections, language, version); - var key = "umbraco-dynamic-dashboard-" + userType + language + allowedSections.Replace(",", "-") + section; + var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); + var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; var content = ApplicationCache.RuntimeCache.GetCacheItem(key); var result = new JObject(); diff --git a/src/Umbraco.Web/Editors/DashboardSecurity.cs b/src/Umbraco.Web/Editors/DashboardSecurity.cs index 0f5f715296..c7e8852a16 100644 --- a/src/Umbraco.Web/Editors/DashboardSecurity.cs +++ b/src/Umbraco.Web/Editors/DashboardSecurity.cs @@ -61,45 +61,57 @@ namespace Umbraco.Web.Editors { var allowedSoFar = false; - //Check if this item as any grant-by-section arguments, if so check if the user has access to any of the sections approved, if so they will - // be allowed to see it (so far) - if (grantedBySectionTypes.Any()) + // if there's no grantBySection or grant rules defined - we allow access so far and skip to checking deny rules + if (grantedBySectionTypes.Any() == false && grantedTypes.Any() == false) { - var allowedApps = sectionService.GetAllowedSections(Convert.ToInt32(user.Id)) - .Select(x => x.Alias) - .ToArray(); - - var allApprovedSections = grantedBySectionTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allApprovedSections.Any(allowedApps.Contains)) - { - allowedSoFar = true; - } - } - - //Check if this item as any grant arguments, if so check if the user is one of the user types approved, if so they will - // be allowed to see it (so far) - if (grantedTypes.Any()) - { - var allApprovedUserTypes = grantedTypes.SelectMany(g => g.Value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allApprovedUserTypes.InvariantContains(user.UserType.Alias)) - { - allowedSoFar = true; - } - } - else - { - //if there are not explicit grant types then everyone is allowed so far and we'll only disallow on a deny basis allowedSoFar = true; } + // else we check the rules and only allow if one matches + else + { + // check if this item has any grant-by-section arguments. + // if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far) + if (grantedBySectionTypes.Any()) + { + var allowedApps = sectionService.GetAllowedSections(Convert.ToInt32(user.Id)) + .Select(x => x.Alias) + .ToArray(); - //Check if this item as any deny arguments, if so check if the user is one of the user types approved, if so they will + var allApprovedSections = grantedBySectionTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + if (allApprovedSections.Any(allowedApps.Contains)) + { + allowedSoFar = true; + } + } + + // if not already granted access, check if this item as any grant arguments. + // if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far) + if (allowedSoFar == false && grantedTypes.Any()) + { + var allApprovedUserTypes = grantedTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + foreach (var userGroup in user.Groups) + { + if (allApprovedUserTypes.InvariantContains(userGroup.Alias)) + { + allowedSoFar = true; + break; + } + } + } + } + + // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will // be denied to see it no matter what if (denyTypes.Any()) { var allDeniedUserTypes = denyTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allDeniedUserTypes.InvariantContains(user.UserType.Alias)) + foreach (var userGroup in user.Groups) { - allowedSoFar = false; + if (allDeniedUserTypes.InvariantContains(userGroup.Alias)) + { + allowedSoFar = false; + break; + } } } diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index f969b97ec5..77c392bf8c 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Editors { + /// /// The API controller used for editing data types /// diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index 8dad002285..863a30cb70 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Editors var dataType = (DataTypeSave)actionContext.ActionArguments["dataType"]; dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); - dataType.Alias = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); + dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':'); //Validate that the property editor exists var propertyEditor = Current.PropertyEditors[dataType.SelectedEditor]; diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 5fb656cf29..f5f18e995e 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -20,8 +20,9 @@ using Examine; using System.Text.RegularExpressions; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; -using Examine.LuceneEngine; using Umbraco.Core.Xml; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; namespace Umbraco.Web.Editors { @@ -54,6 +55,8 @@ namespace Umbraco.Web.Editors } } + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + /// /// Returns an Umbraco alias given a string /// @@ -104,42 +107,40 @@ namespace Umbraco.Web.Editors /// /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search /// methods might be used in things like pickers in the content editor. - /// + /// [HttpGet] - public IEnumerable SearchAll(string query) + public IDictionary SearchAll(string query) { + var result = new Dictionary(); + if (string.IsNullOrEmpty(query)) - return Enumerable.Empty(); - + return result; + var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - - var result = new List(); - - if (allowedSections.InvariantContains(Constants.Applications.Content)) + var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); + + foreach (var searchableTree in searchableTrees) { - result.Add(new EntityTypeSearchResult + if (allowedSections.Contains(searchableTree.Value.AppAlias)) + { + var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); + if (tree == null) continue; //shouldn't occur + #error why cannot we use a collectino? + var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); + var treeAttribute = tree.GetTreeAttribute(); + + long total; + + result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult { - Results = ExamineSearch(query, UmbracoEntityTypes.Document), - EntityType = UmbracoEntityTypes.Document.ToString() - }); - } - if (allowedSections.InvariantContains(Constants.Applications.Media)) - { - result.Add(new EntityTypeSearchResult - { - Results = ExamineSearch(query, UmbracoEntityTypes.Media), - EntityType = UmbracoEntityTypes.Media.ToString() - }); - } - if (allowedSections.InvariantContains(Constants.Applications.Members)) - { - result.Add(new EntityTypeSearchResult - { - Results = ExamineSearch(query, UmbracoEntityTypes.Member), - EntityType = UmbracoEntityTypes.Member.ToString() - }); - - } + Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), + TreeAlias = searchableTree.Key, + AppAlias = searchableTree.Value.AppAlias, + JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, + JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName + }; + } + } return result; } @@ -459,8 +460,8 @@ namespace Umbraco.Web.Editors //the EntityService cannot search members of a certain type, this is currently not supported and would require //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search - int total; - var searchResult = ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out total, id); + long total; + var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); return new PagedResult(total, pageNumber, pageSize) { @@ -543,11 +544,32 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { + IEnumerable entities; long totalRecords; - //if it's from root, don't return recycled - var entities = id == Constants.System.Root - ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed:false) - : Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (id == Constants.System.Root) + { + // root is special: we reduce it to start nodes + + int[] aids = null; + switch (type) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + entities = aids == null || aids.Contains(Constants.System.Root) + ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) + : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + else + { + entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } if (totalRecords == 0) { @@ -588,311 +610,14 @@ namespace Umbraco.Web.Editors /// /// /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) { - int total; - return ExamineSearch(query, entityType, 200, 0, out total, searchFrom); + long total; + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom); } - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - /// - /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, int pageSize, int pageIndex, out int totalFound, string searchFrom = null) - { - //TODO: We need to update this to support paging + - var sb = new StringBuilder(); - - string type; - - var fields = new[] { "id", "__NodeId" }; - - //TODO: WE should really just allow passing in a lucene raw query - switch (entityType) - { - case UmbracoEntityTypes.Member: - - type = "member"; - fields = new[] { "id", "__NodeId", "email", "loginName"}; - if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") - { - sb.Append("+__NodeTypeAlias:"); - sb.Append(searchFrom); - sb.Append(" "); - } - break; - case UmbracoEntityTypes.Media: - type = "media"; - - var mediaSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartMediaId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out mediaSearchFrom) && mediaSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(mediaSearchFrom > 0 - ? mediaSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartMediaId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } - break; - case UmbracoEntityTypes.Document: - type = "content"; - - var contentSearchFrom = int.MinValue; - - if (Security.CurrentUser.StartContentId > 0 || - //if searchFrom is specified and it is greater than 0 - (searchFrom != null && int.TryParse(searchFrom, out contentSearchFrom) && contentSearchFrom > 0)) - { - sb.Append("+__Path: \\-1*\\,"); - sb.Append(contentSearchFrom > 0 - ? contentSearchFrom.ToString(CultureInfo.InvariantCulture) - : Security.CurrentUser.StartContentId.ToString(CultureInfo.InvariantCulture)); - sb.Append("\\,* "); - } - break; - default: - throw new NotSupportedException("The " + typeof(EntityController) + " currently does not support searching against object type " + entityType); - } - - var internalSearcher = ExamineManager.Instance.GetSearcher(Constants.Examine.InternalIndexer); - - //build a lucene query: - // the __nodeName will be boosted 10x without wildcards - // then __nodeName will be matched normally with wildcards - // the rest will be normal without wildcards - - - //check if text is surrounded by single or double quotes, if so, then exact match - var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") - || Regex.IsMatch(query, "^\'.*?\'$"); - - if (surroundedByQuotes) - { - //strip quotes, escape string, the replace again - query = query.Trim('\"', '\''); - - query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - - //nothing to search - if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) - { - totalFound = 0; - return new List(); - } - - //update the query with the query term - if (query.IsNullOrWhiteSpace() == false) - { - //add back the surrounding quotes - query = string.Format("{0}{1}{0}", "\"", query); - - //node name exactly boost x 10 - sb.Append("+(__nodeName: ("); - sb.Append(query.ToLower()); - sb.Append(")^10.0 "); - - foreach (var f in fields) - { - //additional fields normally - sb.Append(f); - sb.Append(": ("); - sb.Append(query); - sb.Append(") "); - } - - sb.Append(") "); - } - } - else - { - var trimmed = query.Trim(new[] {'\"', '\''}); - - //nothing to search - if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) - { - totalFound = 0; - return new List(); - } - - //update the query with the query term - if (trimmed.IsNullOrWhiteSpace() == false) - { - query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - - var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - //node name exactly boost x 10 - sb.Append("+(__nodeName:"); - sb.Append("\""); - sb.Append(query.ToLower()); - sb.Append("\""); - sb.Append("^10.0 "); - - //node name normally with wildcards - sb.Append(" __nodeName:"); - sb.Append("("); - foreach (var w in querywords) - { - sb.Append(w.ToLower()); - sb.Append("* "); - } - sb.Append(") "); - - - foreach (var f in fields) - { - //additional fields normally - sb.Append(f); - sb.Append(":"); - sb.Append("("); - foreach (var w in querywords) - { - sb.Append(w.ToLower()); - sb.Append("* "); - } - sb.Append(")"); - sb.Append(" "); - } - - sb.Append(") "); - } - } - - //must match index type - sb.Append("+__IndexType:"); - sb.Append(type); - - var raw = internalSearcher.CreateSearchCriteria().RawQuery(sb.ToString()); - - // fixme - ISearcher has not been updated in Examine for v8? - throw new NotImplementedException(); - //var result = internalSearcher - // //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested - // .Search(raw, pageSize * (pageIndex + 1)); - var result = new SR(); - - totalFound = result.TotalItemCount; - - var pagedResult = result.Skip(pageIndex); - - switch (entityType) - { - case UmbracoEntityTypes.Member: - return MemberFromSearchResults(pagedResult.ToArray()); - case UmbracoEntityTypes.Media: - return MediaFromSearchResults(pagedResult); - case UmbracoEntityTypes.Document: - return ContentFromSearchResults(pagedResult); - default: - throw new NotSupportedException("The " + typeof(EntityController) + " currently does not support searching against object type " + entityType); - } - } - - private class SR : ISearchResults - { - public IEnumerator GetEnumerator() - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public IEnumerable Skip(int skip) - { - throw new NotImplementedException(); - } - - public int TotalItemCount { get; } - } - - /// - /// Returns a collection of entities for media based on search results - /// - /// - /// - private IEnumerable MemberFromSearchResults(SearchResult[] results) - { - var mapped = Mapper.Map>(results).ToArray(); - //add additional data - foreach (var m in mapped) - { - //if no icon could be mapped, it will be set to document, so change it to picture - if (m.Icon == "icon-document") - { - m.Icon = "icon-user"; - } - - var searchResult = results.First(x => x.LongId.ToInvariantString() == m.Id.ToString()); - if (searchResult.Fields.ContainsKey("email") && searchResult.Fields["email"] != null) - { - m.AdditionalData["Email"] = results.First(x => x.LongId.ToInvariantString() == m.Id.ToString()).Fields["email"]; - } - if (searchResult.Fields.ContainsKey("__key") && searchResult.Fields["__key"] != null) - { - Guid key; - if (Guid.TryParse(searchResult.Fields["__key"], out key)) - { - m.Key = key; - } - } - } - return mapped; - } - - /// - /// Returns a collection of entities for media based on search results - /// - /// - /// - private IEnumerable MediaFromSearchResults(IEnumerable results) - { - var mapped = Mapper.Map>(results).ToArray(); - //add additional data - foreach (var m in mapped) - { - //if no icon could be mapped, it will be set to document, so change it to picture - if (m.Icon == "icon-document") - { - m.Icon = "icon-picture"; - } - } - return mapped; - } - - /// - /// Returns a collection of entities for content based on search results - /// - /// - /// - private IEnumerable ContentFromSearchResults(IEnumerable results) - { - var mapped = Mapper.Map>(results).ToArray(); - //add additional data - foreach (var m in mapped) - { - var intId = m.Id.TryConvertTo(); - if (intId.Success) - { - m.AdditionalData["Url"] = Umbraco.NiceUrl(intId.Result); - } - } - return mapped; - } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) { @@ -926,10 +651,43 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - return Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .OrderBy(x => x.Level) - .Select(Mapper.Map); + int[] aids = null; + switch (entityType) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); + } + + return ids.Length == 0 + ? Enumerable.Empty() + : Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .OrderBy(x => x.Level) + .Select(Mapper.Map); } //now we need to convert the unknown ones switch (entityType) diff --git a/src/Umbraco.Web/Editors/GravatarController.cs b/src/Umbraco.Web/Editors/GravatarController.cs deleted file mode 100644 index 2dda8a448a..0000000000 --- a/src/Umbraco.Web/Editors/GravatarController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Net; -using Umbraco.Core; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Editors -{ - /// - /// API controller used for getting Gravatar urls - /// - [PluginController("UmbracoApi")] - public class GravatarController : UmbracoAuthorizedJsonController - { - public string GetCurrentUserGravatarUrl() - { - var userService = Services.UserService; - var user = userService.GetUserById(UmbracoContext.Security.CurrentUser.Id); - var gravatarHash = user.Email.ToMd5(); - var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash; - - // Test if we can reach this URL, will fail when there's network or firewall errors - var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); - // Require response within 10 seconds - request.Timeout = 10000; - try - { - using ((HttpWebResponse)request.GetResponse()) { } - } - catch (Exception) - { - // There was an HTTP or other error, return an null instead - return null; - } - - return gravatarUrl; - } - } -} diff --git a/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs b/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs new file mode 100644 index 0000000000..a7164b3c65 --- /dev/null +++ b/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + /// + /// This sets the IsCurrentUser property on any outgoing model or any collection of models + /// + internal class IsCurrentUserModelFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext.Response == null) return; + + var user = UmbracoContext.Current.Security.CurrentUser; + if (user == null) return; + + var objectContent = actionExecutedContext.Response.Content as ObjectContent; + if (objectContent != null) + { + var model = objectContent.Value as UserBasic; + if (model != null) + { + model.IsCurrentUser = (int) model.Id == user.Id; + } + else + { + var collection = objectContent.Value as IEnumerable; + if (collection != null) + { + foreach (var userBasic in collection) + { + userBasic.IsCurrentUser = (int) userBasic.Id == user.Id; + } + } + else + { + var paged = objectContent.Value as UsersController.PagedUserResult; + if (paged != null && paged.Items != null) + { + foreach (var userBasic in paged.Items) + { + userBasic.IsCurrentUser = (int)userBasic.Id == user.Id; + } + } + } + } + } + + base.OnActionExecuted(actionExecutedContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 608480ee70..1a7b19f9d8 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -234,6 +234,13 @@ namespace Umbraco.Web.Editors } #region GetChildren + + private int[] _userStartNodes; + protected int[] UserStartNodes + { + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + } + /// /// Returns the child media objects - using the entity INT id /// @@ -246,6 +253,25 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { + //if a request is made for the root node data but the user's start node is not the default, then + // we need to return their start nodes + if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + { + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map>) + }; + return pr; + } + + // else proceed as usual + long totalChildren; IMedia[] children; if (pageNumber > 0 && pageSize > 0) @@ -649,7 +675,9 @@ namespace Umbraco.Web.Editors if (CheckPermissions( new Dictionary(), Security.CurrentUser, - Services.MediaService, parentId) == false) + Services.MediaService, + Services.EntityService, + parentId) == false) { return Request.CreateResponse( HttpStatusCode.Forbidden, @@ -861,11 +889,17 @@ namespace Umbraco.Web.Editors /// The storage to add the content item to so it can be reused /// /// + /// /// The content to lookup, if the contentItem is not specified /// Specifies the already resolved content item to check against, setting this ignores the nodeId /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null) + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (mediaService == null) throw new ArgumentNullException("mediaService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) { media = mediaService.GetById(nodeId); @@ -877,19 +911,13 @@ namespace Umbraco.Web.Editors if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) { throw new HttpResponseException(HttpStatusCode.NotFound); - } + } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); + ? user.HasMediaRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinMedia) + ? user.HasMediaBinAccess(entityService) + : user.HasPathAccess(media, entityService); return hasPathAccess; } diff --git a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs index b6b94d694d..7784129f8a 100644 --- a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs @@ -20,17 +20,19 @@ namespace Umbraco.Web.Editors internal sealed class MediaPostValidateAttribute : ActionFilterAttribute { private readonly IMediaService _mediaService; + private readonly IEntityService _entityService; private readonly WebSecurity _security; public MediaPostValidateAttribute() { } - public MediaPostValidateAttribute(IMediaService mediaService, WebSecurity security) + public MediaPostValidateAttribute(IMediaService mediaService, IEntityService entityService, WebSecurity security) { if (mediaService == null) throw new ArgumentNullException("mediaService"); if (security == null) throw new ArgumentNullException("security"); _mediaService = mediaService; + _entityService = entityService; _security = security; } @@ -39,6 +41,11 @@ namespace Umbraco.Web.Editors get { return _mediaService ?? Current.Services.MediaService; } } + private IEntityService EntityService + { + get { return _entityService ?? ApplicationContext.Current.Services.EntityService; } + } + private WebSecurity Security { get { return _security ?? UmbracoContext.Current.Security; } @@ -82,6 +89,7 @@ namespace Umbraco.Web.Editors actionContext.Request.Properties, Security.CurrentUser, MediaService, + EntityService, contentIdToCheck, contentToCheck) == false) { diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 3907944154..24e5b0a3fa 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [EnableOverrideAuthorization] - [MediaTypeControllerControllerConfigurationAttribute] + [MediaTypeControllerControllerConfiguration] public class MediaTypeController : ContentTypeControllerBase { /// @@ -213,7 +213,7 @@ namespace Umbraco.Web.Editors basic.Description = TranslateItem(basic.Description); } - return basics; + return basics.OrderBy(x => x.Name); } /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index f6b26bb436..90b9768190 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -205,8 +205,10 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + emptyContent = new Member(contentType); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); return Mapper.Map(emptyContent); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 3e08852bfb..17b46cc849 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -536,6 +536,26 @@ namespace Umbraco.Web.Editors var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath)); ins.InstallFiles(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); + + //set a restarting marker and reset the app pool + ApplicationContext.RestartApplicationPool(Request.TryGetHttpContext().Result); + + model.IsRestarting = true; + + return model; + } + + [HttpPost] + public PackageInstallModel CheckRestart(PackageInstallModel model) + { + if (model.IsRestarting == false) return model; + + //check for the key, if it's not there we're are restarted + if (Request.TryGetHttpContext().Result.Application.AllKeys.Contains("AppPoolRestarting") == false) + { + //reset it + model.IsRestarting = false; + } return model; } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs new file mode 100644 index 0000000000..ad62c0b0e9 --- /dev/null +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -0,0 +1,240 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using System.Web.Http.ModelBinding; +using System.Web.Security; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models; +using IUser = Umbraco.Core.Models.Membership.IUser; + +namespace Umbraco.Web.Editors +{ + internal class PasswordChanger + { + private readonly ILogger _logger; + private readonly IUserService _userService; + + public PasswordChanger(ILogger logger, IUserService userService) + { + _logger = logger; + _userService = userService; + } + + public async Task> ChangePasswordWithIdentityAsync( + IUser currentUser, + ChangingPasswordModel passwordModel, + ModelStateDictionary modelState, + BackOfficeUserManager userMgr) + { + if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); + if (userMgr == null) throw new ArgumentNullException(nameof(userMgr)); + + //check if this identity implementation is powered by an underlying membership provider (it will be in most cases) + var membershipPasswordHasher = userMgr.PasswordHasher as IMembershipProviderPasswordHasher; + + //check if this identity implementation is powered by an IUserAwarePasswordHasher (it will be by default in 7.7+ but not for upgrades) + + if (membershipPasswordHasher != null && !(userMgr.PasswordHasher is IUserAwarePasswordHasher)) + { + //if this isn't using an IUserAwarePasswordHasher, then fallback to the old way + if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer) + throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); + return ChangePasswordWithMembershipProvider(currentUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider); + } + + //if we are here, then a IUserAwarePasswordHasher is available, however we cannot proceed in that case if for some odd reason + //the user has configured the membership provider to not be hashed. This will actually never occur because the BackOfficeUserManager + //will throw if it's not hashed, but we should make sure to check anyways (i.e. in case we want to unit test!) + if (membershipPasswordHasher != null && membershipPasswordHasher.MembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed) + { + throw new InvalidOperationException("The membership provider cannot have a password format of " + membershipPasswordHasher.MembershipProvider.PasswordFormat + " and be configured with secured hashed passwords"); + } + + //Are we resetting the password?? + if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) + { + //ok, we should be able to reset it + var resetToken = await userMgr.GeneratePasswordResetTokenAsync(currentUser.Id); + var newPass = userMgr.GeneratePassword(); + var resetResult = await userMgr.ResetPasswordAsync(currentUser.Id, resetToken, newPass); + + if (resetResult.Succeeded == false) + { + var errors = string.Join(". ", resetResult.Errors); + _logger.Warn($"Could not reset member password {errors}"); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) }); + } + + return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); + } + + //we're not resetting it so we need to try to change it. + + if (passwordModel.NewPassword.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); + } + + //we cannot arbitrarily change the password without knowing the old one and no old password was supplied - need to return an error + //TODO: What if the current user is admin? We should allow manually changing then? + if (passwordModel.OldPassword.IsNullOrWhiteSpace()) + { + //if password retrieval is not enabled but there is no old password we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + + if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) + { + //if an old password is suplied try to change it + var changeResult = await userMgr.ChangePasswordAsync(currentUser.Id, passwordModel.OldPassword, passwordModel.NewPassword); + if (changeResult.Succeeded == false) + { + var errors = string.Join(". ", changeResult.Errors); + _logger.Warn($"Could not change member password {errors}"); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "value" }) }); + } + return Attempt.Succeed(new PasswordChangedModel()); + } + + //We shouldn't really get here + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid information supplied", new[] { "value" }) }); + } + + /// + /// Changes password for a member/user given the membership provider and the password change model + /// + /// + /// + /// + /// + public Attempt ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) + { + // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! + + if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); + if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); + + //Are we resetting the password?? + if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) + { + var canReset = membershipProvider.CanResetPassword(_userService); + if (canReset == false) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }) }); + } + if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset requires a password answer", new[] { "resetPassword" }) }); + } + //ok, we should be able to reset it + try + { + var newPass = membershipProvider.ResetPassword( + username, + membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); + + //return the generated pword + return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); + } + catch (Exception ex) + { + _logger.Warn("Could not reset member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) }); + } + } + + //we're not resetting it so we need to try to change it. + + if (passwordModel.NewPassword.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); + } + + //This is an edge case and is only necessary for backwards compatibility: + if (membershipProvider is MembershipProviderBase umbracoBaseProvider && umbracoBaseProvider.AllowManuallyChangingPassword) + { + //this provider allows manually changing the password without the old password, so we can just do it + try + { + var result = umbracoBaseProvider.ChangePassword(username, "", passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex) + { + _logger.Warn("Could not change member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + //The provider does not support manually chaning the password but no old password supplied - need to return an error + if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false) + { + //if password retrieval is not enabled but there is no old password we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + + if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) + { + //if an old password is suplied try to change it + + try + { + var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex) + { + _logger.Warn("Could not change member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + if (membershipProvider.EnablePasswordRetrieval == false) + { + //we cannot continue if we cannot get the current password + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) + { + //if the question answer is required but there isn't one, we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) }); + } + + //lets try to get the old one so we can change it + try + { + var oldPassword = membershipProvider.GetPassword( + username, + membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); + + try + { + var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex1) + { + _logger.Warn("Could not change member password", ex1); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); + } + + } + catch (Exception ex2) + { + _logger.Warn("Could not retrieve member password", ex2); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs index bd5ddb5062..d63eaa2b48 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.Editors var userIsAdmin = Umbraco.UmbracoContext.Security.CurrentUser.IsAdmin(); if (userIsAdmin == false) { - var errorMessage = $"User of type {Umbraco.UmbracoContext.Security.CurrentUser.UserType.Alias} is not allowed to toggle the URL tracker."; + var errorMessage = "User is not a member of the administrators group and so is not allowed to toggle the URL tracker"; _logger.Debug(errorMessage); throw new SecurityException(errorMessage); } diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index c414b41fdf..d527724abf 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -17,5 +17,12 @@ namespace Umbraco.Web.Editors var sections = Services.SectionService.GetAllowedSections(Security.GetUserId()); return sections.Select(Mapper.Map); } + + public IEnumerable
GetAllSections() + { + var sections = Services.SectionService.GetSections(); + return sections.Select(Mapper.Map); + } + } } diff --git a/src/Umbraco.Web/Editors/UserController.cs b/src/Umbraco.Web/Editors/UserController.cs deleted file mode 100644 index 4454709227..0000000000 --- a/src/Umbraco.Web/Editors/UserController.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Net; -using System.Web.Http; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Editors -{ - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Users)] - public class UserController : UmbracoAuthorizedJsonController - { - /// - /// Disables the user with the given user id - /// - /// - public bool PostDisableUser([FromUri]int userId) - { - var user = Services.UserService.GetUserById(userId); - if (user == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - //without the permanent flag, this will just disable - Services.UserService.Delete(user); - return true; - } - } -} diff --git a/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs new file mode 100644 index 0000000000..35cf3930ab --- /dev/null +++ b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs @@ -0,0 +1,81 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using AutoMapper; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.Editors +{ + internal sealed class UserGroupValidateAttribute : ActionFilterAttribute + { + private readonly IUserService _userService; + + public UserGroupValidateAttribute() + { + } + + public UserGroupValidateAttribute(IUserService userService) + { + if (_userService == null) throw new ArgumentNullException(nameof(userService)); + _userService = userService; + } + + private IUserService UserService => _userService ?? Current.Services.UserService; // fixme inject + + public override void OnActionExecuting(HttpActionContext actionContext) + { + var userGroupSave = (UserGroupSave) actionContext.ActionArguments["userGroupSave"]; + + userGroupSave.Name = userGroupSave.Name.CleanForXss('[', ']', '(', ')', ':'); + userGroupSave.Alias = userGroupSave.Alias.CleanForXss('[', ']', '(', ')', ':'); + + //Validate the usergroup exists or create one if required + IUserGroup persisted; + switch (userGroupSave.Action) + { + case ContentSaveAction.Save: + persisted = UserService.GetUserGroupById(Convert.ToInt32(userGroupSave.Id)); + if (persisted == null) + { + var message = $"User group with id: {userGroupSave.Id} was not found"; + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + return; + } + //map the model to the persisted instance + Mapper.Map(userGroupSave, persisted); + break; + case ContentSaveAction.SaveNew: + //create the persisted model from mapping the saved model + persisted = Mapper.Map(userGroupSave); + ((UserGroup)persisted).ResetIdentity(); + break; + default: + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); + return; + } + + //now assign the persisted entity to the model so we can use it in the action + userGroupSave.PersistedUserGroup = persisted; + + var existing = UserService.GetUserGroupByAlias(userGroupSave.Alias); + if (existing != null && existing.Id != userGroupSave.PersistedUserGroup.Id) + { + actionContext.ModelState.AddModelError("Alias", "A user group with this alias already exists"); + } + + //TODO: Validate the name is unique? + + if (actionContext.ModelState.IsValid == false) + { + //if it is not valid, do not continue and return the model state + actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs new file mode 100644 index 0000000000..ad896fe500 --- /dev/null +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Users)] + [PrefixlessBodyModelValidator] + public class UserGroupsController : UmbracoAuthorizedJsonController + { + [UserGroupValidate] + public UserGroupDisplay PostSaveUserGroup(UserGroupSave userGroupSave) + { + if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave)); + + //save the group + Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); + + //deal with permissions + + //remove ones that have been removed + var existing = Services.UserService.GetPermissions(userGroupSave.PersistedUserGroup, true) + .ToDictionary(x => x.EntityId, x => x); + var toRemove = existing.Keys.Except(userGroupSave.AssignedPermissions.Select(x => x.Key)); + foreach (var contentId in toRemove) + { + Services.UserService.RemoveUserGroupPermissions(userGroupSave.PersistedUserGroup.Id, contentId); + } + + //update existing + foreach (var assignedPermission in userGroupSave.AssignedPermissions) + { + Services.UserService.ReplaceUserGroupPermissions( + userGroupSave.PersistedUserGroup.Id, + assignedPermission.Value.Select(x => x[0]), + assignedPermission.Key); + } + + var display = Mapper.Map(userGroupSave.PersistedUserGroup); + + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserGroupSaved")); + return display; + } + + /// + /// Returns the scaffold for creating a new user group + /// + /// + public UserGroupDisplay GetEmptyUserGroup() + { + return Mapper.Map(new UserGroup()); + } + + /// + /// Returns all user groups + /// + /// + public IEnumerable GetUserGroups() + { + return Mapper.Map, IEnumerable>(Services.UserService.GetAllUserGroups()); + } + + /// + /// Return a user group + /// + /// + public UserGroupDisplay GetUserGroup(int id) + { + var found = Services.UserService.GetUserGroupById(id); + if (found == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var display = Mapper.Map(found); + + return display; + } + + [HttpPost] + [HttpDelete] + public HttpResponseMessage PostDeleteUserGroups([FromUri] int[] userGroupIds) + { + var userGroups = Services.UserService.GetAllUserGroups(userGroupIds).ToArray(); + foreach (var userGroup in userGroups) + { + Services.UserService.DeleteUserGroup(userGroup); + } + if (userGroups.Length > 1) + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/deleteUserGroupsSuccess", new[] {userGroups.Length.ToString()})); + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/deleteUserGroupSuccess", new[] {userGroups[0].Name})); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs new file mode 100644 index 0000000000..236c669db4 --- /dev/null +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -0,0 +1,593 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.Security; +using System.Web.WebPages; +using AutoMapper; +using ClientDependency.Core; +using Microsoft.AspNet.Identity; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; +using IUser = Umbraco.Core.Models.Membership.IUser; +using Task = System.Threading.Tasks.Task; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Users)] + [PrefixlessBodyModelValidator] + [IsCurrentUserModelFilter] + public class UsersController : UmbracoAuthorizedJsonController + { + /// + /// Constructor + /// + public UsersController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public UsersController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + public UsersController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager backOfficeUserManager) + : base(umbracoContext, umbracoHelper, backOfficeUserManager) + { + } + + + /// + /// Returns a list of the sizes of gravatar urls for the user or null if the gravatar server cannot be reached + /// + /// + public string[] GetCurrentUserAvatarUrls() + { + var urls = UmbracoContext.Security.CurrentUser.GetCurrentUserAvatarUrls(Services.UserService, ApplicationContext.ApplicationCache.StaticCache); + if (urls == null) + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint")); + + return urls; + } + + [AppendUserModifiedHeader("id")] + [FileUploadCleanupFilter(false)] + public async Task PostSetAvatar(int id) + { + return await PostSetAvatarInternal(Request, Services.UserService, ApplicationContext.ApplicationCache.StaticCache, id); + } + + internal static async Task PostSetAvatarInternal(HttpRequestMessage request, IUserService userService, ICacheProvider staticCache, int id) + { + if (request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return request.CreateResponse(HttpStatusCode.NotFound); + } + + var user = userService.GetUserById(id); + if (user == null) + return request.CreateResponse(HttpStatusCode.NotFound); + + var tempFiles = new PostedFiles(); + + if (result.FileData.Count > 1) + return request.CreateValidationErrorResponse("The request was not formatted correctly, only one file can be attached to the request"); + + //get the file info + var file = result.FileData[0]; + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) + { + //generate a path of known data, we don't want this path to be guessable + user.Avatar = "UserAvatars/" + (user.Id + safeFileName).ToSHA1() + "." + ext; + + using (var fs = System.IO.File.OpenRead(file.LocalFileName)) + { + FileSystemProviderManager.Current.MediaFileSystem.AddFile(user.Avatar, fs, true); + } + + userService.Save(user); + + //track the temp file so the cleanup filter removes it + tempFiles.UploadedFiles.Add(new ContentItemFile + { + TempFilePath = file.LocalFileName + }); + } + + return request.CreateResponse(HttpStatusCode.OK, user.GetCurrentUserAvatarUrls(userService, staticCache)); + } + + [AppendUserModifiedHeader("id")] + public HttpResponseMessage PostClearAvatar(int id) + { + var found = Services.UserService.GetUserById(id); + if (found == null) + return Request.CreateResponse(HttpStatusCode.NotFound); + + var filePath = found.Avatar; + + //if the filePath is already null it will mean that the user doesn't have a custom avatar and their gravatar is currently + //being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value + //for the avatar. + if (filePath.IsNullOrWhiteSpace() == false) + { + found.Avatar = null; + } + else + { + //set a special value to indicate to not have any avatar + found.Avatar = "none"; + } + + Services.UserService.Save(found); + + if (filePath.IsNullOrWhiteSpace() == false) + { + if (FileSystemProviderManager.Current.MediaFileSystem.FileExists(filePath)) + FileSystemProviderManager.Current.MediaFileSystem.DeleteFile(filePath); + } + + return Request.CreateResponse(HttpStatusCode.OK, found.GetCurrentUserAvatarUrls(Services.UserService, ApplicationContext.ApplicationCache.StaticCache)); + } + + /// + /// Gets a user by Id + /// + /// + /// + public UserDisplay GetById(int id) + { + var user = Services.UserService.GetUserById(id); + if (user == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(user); + } + + + + /// + /// Returns a paged users collection + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedUserResult GetPagedUsers( + int pageNumber = 1, + int pageSize = 10, + string orderBy = "username", + Direction orderDirection = Direction.Ascending, + [FromUri]string[] userGroups = null, + [FromUri]UserState[] userStates = null, + string filter = "") + { + long pageIndex = pageNumber - 1; + long total; + var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, filter); + + var paged = new PagedUserResult(total, pageNumber, pageSize) + { + Items = Mapper.Map>(result), + UserStates = Services.UserService.GetUserStates() + }; + + return paged; + } + + /// + /// Creates a new user + /// + /// + /// + public async Task PostCreateUser(UserInvite userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + //we want to create the user with the UserManager, this ensures the 'empty' (special) password + //format is applied without us having to duplicate that logic + var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage); + identityUser.Name = userSave.Name; + + var created = await UserManager.CreateAsync(identityUser); + if (created.Succeeded == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + } + + //we need to generate a password, however we can only do that if the user manager has a password validator that + //we can read values from + var passwordValidator = UserManager.PasswordValidator as PasswordValidator; + var resetPassword = string.Empty; + if (passwordValidator != null) + { + var password = UserManager.GeneratePassword(); + + var result = await UserManager.AddPasswordAsync(identityUser.Id, password); + if (result.Succeeded == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + } + resetPassword = password; + } + + //now re-look the user back up which will now exist + var user = Services.UserService.GetByEmail(userSave.Email); + + //map the save info over onto the user + user = Mapper.Map(userSave, user); + + //since the back office user is creating this user, they will be set to approved + user.IsApproved = true; + + Services.UserService.Save(user); + + var display = Mapper.Map(user); + display.ResetPasswordValue = resetPassword; + return display; + } + + /// + /// Invites a user + /// + /// + /// + /// + /// This will email the user an invite and generate a token that will be validated in the email + /// + public async Task PostInviteUser(UserInvite userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (userSave.Message.IsNullOrWhiteSpace()) + ModelState.AddModelError("Message", "Message cannot be empty"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var hasSmtp = GlobalSettings.HasSmtpServerConfigured(RequestContext.VirtualPathRoot); + if (hasSmtp == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse("No Email server is configured")); + } + + var user = Services.UserService.GetByEmail(userSave.Email); + if (user != null && (user.LastLoginDate != default(DateTime) || user.EmailConfirmedDate.HasValue)) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + if (user == null) + { + //we want to create the user with the UserManager, this ensures the 'empty' (special) password + //format is applied without us having to duplicate that logic + var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage); + identityUser.Name = userSave.Name; + + var created = await UserManager.CreateAsync(identityUser); + if (created.Succeeded == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + } + + //now re-look the user back up + user = Services.UserService.GetByEmail(userSave.Email); + } + + //map the save info over onto the user + user = Mapper.Map(userSave, user); + + //ensure the invited date is set + user.InvitedDate = DateTime.Now; + + //Save the updated user + Services.UserService.Save(user); + var display = Mapper.Map(user); + + //send the email + + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, user, userSave.Message); + + return display; + } + + + + private HttpContextBase EnsureHttpContext() + { + var attempt = this.TryGetHttpContext(); + if (attempt.Success == false) + throw new InvalidOperationException("This method requires that an HttpContext be active"); + return attempt.Result; + } + + private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, IUser to, string message) + { + var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id); + + var inviteToken = string.Format("{0}{1}{2}", + (int)userDisplay.Id, + WebUtility.UrlEncode("|"), + token.ToUrlBase64()); + + // Get an mvc helper to get the url + var http = EnsureHttpContext(); + var urlHelper = new UrlHelper(http.Request.RequestContext); + var action = urlHelper.Action("VerifyInvite", "BackOffice", + new + { + area = GlobalSettings.UmbracoMvcArea, + invite = inviteToken + }); + + // Construct full URL using configured application URL (which will fall back to request) + var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl); + var inviteUri = new Uri(applicationUri, action); + + var emailSubject = Services.TextService.Localize("user/inviteEmailCopySubject", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(to.Language, Services.TextService)); + var emailBody = Services.TextService.Localize("user/inviteEmailCopyFormat", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(to.Language, Services.TextService), + new[] { userDisplay.Name, from, message, inviteUri.ToString() }); + + await UserManager.EmailService.SendAsync(new IdentityMessage + { + Body = emailBody, + Destination = userDisplay.Email, + Subject = emailSubject + }); + + } + + /// + /// Saves a user + /// + /// + /// + public async Task PostSaveUser(UserSave userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var intId = userSave.Id.TryConvertTo(); + if (intId.Success == false) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var found = Services.UserService.GetUserById(intId.Result); + if (found == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var hasErrors = false; + + var existing = Services.UserService.GetByEmail(userSave.Email); + if (existing != null && existing.Id != userSave.Id) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + hasErrors = true; + } + existing = Services.UserService.GetByUsername(userSave.Username); + if (existing != null && existing.Id != userSave.Id) + { + ModelState.AddModelError("Username", "A user with the username already exists"); + hasErrors = true; + } + // going forward we prefer to align usernames with email, so we should cross-check to make sure + // the email or username isn't somehow being used by anyone. + existing = Services.UserService.GetByEmail(userSave.Username); + if (existing != null && existing.Id != userSave.Id) + { + ModelState.AddModelError("Username", "A user using this as their email already exists"); + hasErrors = true; + } + existing = Services.UserService.GetByUsername(userSave.Email); + if (existing != null && existing.Id != userSave.Id) + { + ModelState.AddModelError("Email", "A user using this as their username already exists"); + hasErrors = true; + } + + // if the found user has his email for username, we want to keep this synced when changing the email. + // we have already cross-checked above that the email isn't colliding with anything, so we can safely assign it here. + if (found.Username == found.Email && userSave.Username != userSave.Email) + { + userSave.Username = userSave.Email; + } + + var resetPasswordValue = string.Empty; + if (userSave.ChangePassword != null) + { + var passwordChanger = new PasswordChanger(Logger, Services.UserService); + + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(found, userSave.ChangePassword, ModelState, UserManager); + if (passwordChangeResult.Success) + { + //depending on how the provider is configured, the password may be reset so let's store that for later + resetPasswordValue = passwordChangeResult.Result.ResetPassword; + + //need to re-get the user + found = Services.UserService.GetUserById(intId.Result); + } + else + { + hasErrors = true; + } + } + + if (hasErrors) + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + + //merge the save data onto the user + var user = Mapper.Map(userSave, found); + + Services.UserService.Save(user); + + var display = Mapper.Map(user); + + //re-map the password reset value (if any) + if (resetPasswordValue.IsNullOrWhiteSpace() == false) + display.ResetPasswordValue = resetPasswordValue; + + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved")); + return display; + } + + /// + /// Disables the users with the given user ids + /// + /// + public HttpResponseMessage PostDisableUsers([FromUri]int[] userIds) + { + if (userIds.Contains(Security.GetUserId())) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse("The current user cannot disable itself")); + } + + var users = Services.UserService.GetUsersById(userIds).ToArray(); + foreach (var u in users) + { + u.IsApproved = false; + u.InvitedDate = null; + } + Services.UserService.Save(users); + + if (users.Length > 1) + { + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/disableUsersSuccess", new[] {userIds.Length.ToString()})); + } + + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/disableUserSuccess", new[] { users[0].Name })); + } + + /// + /// Enables the users with the given user ids + /// + /// + public HttpResponseMessage PostEnableUsers([FromUri]int[] userIds) + { + var users = Services.UserService.GetUsersById(userIds).ToArray(); + foreach (var u in users) + { + u.IsApproved = true; + } + Services.UserService.Save(users); + + if (users.Length > 1) + { + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/enableUsersSuccess", new[] { userIds.Length.ToString() })); + } + + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name })); + } + + public HttpResponseMessage PostSetUserGroupsOnUsers([FromUri]string[] userGroupAliases, [FromUri]int[] userIds) + { + var users = Services.UserService.GetUsersById(userIds).ToArray(); + var userGroups = Services.UserService.GetUserGroupsByAlias(userGroupAliases).Select(x => x.ToReadOnlyGroup()).ToArray(); + foreach (var u in users) + { + u.ClearGroups(); + foreach (var userGroup in userGroups) + { + u.AddGroup(userGroup); + } + } + Services.UserService.Save(users); + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/setUserGroupOnUsersSuccess")); + } + + public class PagedUserResult : PagedResult + { + public PagedUserResult(long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize) + { + UserStates = new Dictionary(); + } + + /// + /// This is basically facets of UserStates key = state, value = count + /// + [DataMember(Name = "userStates")] + public IDictionary UserStates { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 782cf8b3c4..7d60777b86 100644 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -525,9 +525,11 @@ namespace Umbraco.Web.Macros attributeValue = context?.Request.GetCookieValue(name); break; case '#': + if (pageElements == null) pageElements = GetPageElements(); attributeValue = pageElements[name]?.ToString(); break; case '$': + if (pageElements == null) pageElements = GetPageElements(); attributeValue = pageElements[name]?.ToString(); if (string.IsNullOrEmpty(attributeValue)) attributeValue = ParseAttributeOnParents(pageElements, name); @@ -561,6 +563,14 @@ namespace Umbraco.Web.Macros return value; } + private static IDictionary GetPageElements() + { + IDictionary pageElements = null; + if (HttpContext.Current.Items["pageElements"] != null) + pageElements = (IDictionary)HttpContext.Current.Items["pageElements"]; + return pageElements; + } + #endregion #region RTE macros diff --git a/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs b/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs new file mode 100644 index 0000000000..dc071052cc --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// The permissions assigned to a content node + /// + /// + /// The underlying data such as Name, etc... is that of the Content item + /// + [DataContract(Name = "contentPermissions", Namespace = "")] + public class AssignedContentPermissions : EntityBasic + { + /// + /// The assigned permissions to the content item organized by permission group name + /// + [DataMember(Name = "permissions")] + public IDictionary> AssignedPermissions { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs b/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs new file mode 100644 index 0000000000..5428de883f --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// The user group permissions assigned to a content node + /// + /// + /// The underlying data such as Name, etc... is that of the User Group + /// + [DataContract(Name = "userGroupPermissions", Namespace = "")] + public class AssignedUserGroupPermissions : EntityBasic + { + /// + /// The assigned permissions for the user group organized by permission group name + /// + [DataMember(Name = "permissions")] + public IDictionary> AssignedPermissions { get; set; } + + /// + /// The default permissions for the user group organized by permission group name + /// + [DataMember(Name = "defaultPermissions")] + public IDictionary> DefaultPermissions { get; set; } + + public static IDictionary> ClonePermissions(IDictionary> permissions) + { + var result = new Dictionary>(); + foreach (var permission in permissions) + { + result[permission.Key] = new List(permission.Value.Select(x => (Permission)x.Clone())); + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index d097065c88..a1d08eca2c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -29,10 +29,10 @@ namespace Umbraco.Web.Models.ContentEditing public bool HasPublishedVersion { get; set; } [DataMember(Name = "owner")] - public UserBasic Owner { get; set; } + public UserProfile Owner { get; set; } [DataMember(Name = "updater")] - public UserBasic Updater { get; set; } + public UserProfile Updater { get; set; } [DataMember(Name = "contentTypeAlias", IsRequired = true)] [Required(AllowEmptyStrings = false)] @@ -89,7 +89,7 @@ namespace Umbraco.Web.Models.ContentEditing /// /// This is not used for outgoing model information. /// - [JsonIgnore] + [IgnoreDataMember] internal TPersisted PersistedContent { get; set; } /// @@ -100,7 +100,7 @@ namespace Umbraco.Web.Models.ContentEditing /// instead of having to look up all the data individually. /// This is not used for outgoing model information. /// - [JsonIgnore] + [IgnoreDataMember] internal ContentItemDto ContentDto { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 6aaed3fa64..4870bd1cc6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Each char represents a button which we can then map on the front-end to the correct actions /// [DataMember(Name = "allowedActions")] - public IEnumerable AllowedActions { get; set; } + public IEnumerable AllowedActions { get; set; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs index a6a0caf67a..f1514afbac 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; @@ -18,6 +19,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "contentType", Namespace = "")] public class ContentTypeBasic : EntityBasic { + public ContentTypeBasic() + { + Blueprints = new Dictionary(); + } + /// /// Overridden to apply our own validation attributes since this is not always required for other classes /// @@ -105,5 +111,9 @@ namespace Umbraco.Web.Models.ContentEditing : IOHelper.ResolveUrl("~/umbraco/images/thumbnails/" + Thumbnail); } } + + [DataMember(Name = "blueprints")] + [ReadOnly(true)] + public IDictionary Blueprints { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs index 7e003175ab..1045d1227a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs @@ -31,13 +31,13 @@ namespace Umbraco.Web.Models.ContentEditing /// /// The real persisted data type /// - [JsonIgnore] + [IgnoreDataMember] internal IDataTypeDefinition PersistedDataType { get; set; } /// /// The PropertyEditor assigned /// - [JsonIgnore] + [IgnoreDataMember] internal PropertyEditor PropertyEditor { get; set; } } diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityTypeSearchResult.cs b/src/Umbraco.Web/Models/ContentEditing/EntityTypeSearchResult.cs deleted file mode 100644 index 27ddb18406..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/EntityTypeSearchResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; -using Examine; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents a search result by entity type - /// - [DataContract(Name = "searchResult", Namespace = "")] - public class EntityTypeSearchResult - { - [DataMember(Name = "type")] - public string EntityType { get; set; } - - [DataMember(Name = "results")] - public IEnumerable Results { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/ContentEditing/Permission.cs b/src/Umbraco.Web/Models/ContentEditing/Permission.cs new file mode 100644 index 0000000000..9c40dc0aa6 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/Permission.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "permission", Namespace = "")] + public class Permission : ICloneable + { + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "description")] + public string Description { get; set; } + + [DataMember(Name = "checked")] + public bool Checked { get; set; } + + [DataMember(Name = "icon")] + public string Icon { get; set; } + + /// + /// We'll use this to map the categories but it wont' be returned in the json + /// + [IgnoreDataMember] + internal string Category { get; set; } + + /// + /// The letter from the IAction + /// + [DataMember(Name = "permissionCode")] + public string PermissionCode { get; set; } + + public object Clone() + { + return this.MemberwiseClone(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs b/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs index d55e80b1f9..f7062e679f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs @@ -1,25 +1,15 @@ -namespace Umbraco.Web.Models.ContentEditing +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing { - public class SearchResultItem + [DataContract(Name = "searchResult", Namespace = "")] + public class SearchResultItem : EntityBasic { /// - /// The string representation of the ID, used for Web responses + /// The score of the search result /// - public string Id { get; set; } - - /// - /// The name/title of the search result item - /// - public string Title { get; set; } - - /// - /// The rank of the search result - /// - public int Rank { get; set; } - - /// - /// Description/Synopsis of the item - /// - public string Description { get; set; } + [DataMember(Name = "score")] + public float Score { get; set; } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs index c4575a78ca..8e523574c2 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Models.ContentEditing /// /// This property cannot be set /// - [JsonIgnore] + [IgnoreDataMember] public override IEnumerable Properties { get { return Tabs.SelectMany(x => x.Properties); } diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs index 91c1aefdb0..ffa4e4e100 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing { @@ -24,6 +20,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "alias")] public string Alias { get; set; } + [DataMember(Name = "key")] + public Guid Key { get; set; } + [DataMember(Name = "content")] public string Content { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs b/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs new file mode 100644 index 0000000000..1da9d61c98 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Examine; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a search result by entity type + /// + [DataContract(Name = "searchResult", Namespace = "")] + public class TreeSearchResult + { + [DataMember(Name = "appAlias")] + public string AppAlias { get; set; } + + [DataMember(Name = "treeAlias")] + public string TreeAlias { get; set; } + + /// + /// This is optional but if specified should be the name of an angular service to format the search result. + /// + [DataMember(Name = "jsSvc")] + public string JsFormatterService { get; set; } + + /// + /// This is optional but if specified should be the name of a method on the jsSvc angular service to use, if not + /// specfied than it will expect the method to be called `format(searchResult, appAlias, treeAlias)` + /// + [DataMember(Name = "jsMethod")] + public string JsFormatterMethod { get; set; } + + [DataMember(Name = "results")] + public IEnumerable Results { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs index 19da210840..581df571ab 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs @@ -1,27 +1,68 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing { /// - /// A basic structure the represents a user + /// The user model used for paging and listing users in the UI /// [DataContract(Name = "user", Namespace = "")] - public class UserBasic : System.IComparable + [ReadOnly(true)] + public class UserBasic : EntityBasic, INotificationModel { - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int UserId { get; set; } - - [DataMember(Name = "name", IsRequired = true)] - [Required] - public string Name { get; set; } - - - int System.IComparable.CompareTo(object obj) + public UserBasic() { - return Name.CompareTo(((UserBasic)obj).Name); - } + Notifications = new List(); + UserGroups = new List(); + } + + [DataMember(Name = "username")] + public string Username { get; set; } + + /// + /// The MD5 lowercase hash of the email which can be used by gravatar + /// + [DataMember(Name = "emailHash")] + public string EmailHash { get; set; } + + [DataMember(Name = "lastLoginDate")] + public DateTime? LastLoginDate { get; set; } + + /// + /// Returns a list of different size avatars + /// + [DataMember(Name = "avatars")] + public string[] Avatars { get; set; } + + [DataMember(Name = "userState")] + public UserState UserState { get; set; } + + [DataMember(Name = "culture", IsRequired = true)] + public string Culture { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + public string Email { get; set; } + + /// + /// The list of group aliases assigned to the user + /// + [DataMember(Name = "userGroups")] + public IEnumerable UserGroups { get; set; } + + /// + /// This is an info flag to denote if this object is the equivalent of the currently logged in user + /// + [DataMember(Name = "isCurrentUser")] + [ReadOnly(true)] + public bool IsCurrentUser { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index 56f54832d6..e29b58fd6f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -1,11 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing -{ +{ + /// + /// Represents information for the current user + /// [DataContract(Name = "user", Namespace = "")] - public class UserDetail : UserBasic + public class UserDetail : UserProfile { [DataMember(Name = "email", IsRequired = true)] [Required] @@ -21,21 +26,35 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "emailHash")] public string EmailHash { get; set; } - [DataMember(Name = "userType", IsRequired = true)] - [Required] - public string UserType { get; set; } + [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [ReadOnly(true)] + [DataMember(Name = "userType")] + public string UserType { get; set; } /// /// Gets/sets the number of seconds for the user's auth ticket to expire /// [DataMember(Name = "remainingAuthSeconds")] public double SecondsUntilTimeout { get; set; } + + /// + /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to + /// + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } + + /// + /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to + /// + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } - [DataMember(Name = "startContentId")] - public int StartContentId { get; set; } - - [DataMember(Name = "startMediaId")] - public int StartMediaId { get; set; } + /// + /// Returns a list of different size avatars + /// + [DataMember(Name = "avatars")] + public string[] Avatars { get; set; } /// /// A list of sections the user is allowed to view. diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs new file mode 100644 index 0000000000..4cff43e3b8 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a user that is being edited + /// + [DataContract(Name = "user", Namespace = "")] + [ReadOnly(true)] + public class UserDisplay : UserBasic + { + public UserDisplay() + { + AvailableCultures = new Dictionary(); + StartContentIds = new List(); + StartMediaIds = new List(); + } + + /// + /// Gets the available cultures (i.e. to populate a drop down) + /// The key is the culture stored in the database, the value is the Name + /// + [DataMember(Name = "availableCultures")] + public IDictionary AvailableCultures { get; set; } + + [DataMember(Name = "startContentIds")] + public IEnumerable StartContentIds { get; set; } + + [DataMember(Name = "startMediaIds")] + public IEnumerable StartMediaIds { get; set; } + + /// + /// If the password is reset on save, this value will be populated + /// + [DataMember(Name = "resetPasswordValue")] + [ReadOnly(true)] + public string ResetPasswordValue { get; set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs new file mode 100644 index 0000000000..b52a653497 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "userGroup", Namespace = "")] + public class UserGroupBasic : EntityBasic, INotificationModel + { + public UserGroupBasic() + { + Notifications = new List(); + Sections = Enumerable.Empty
(); + } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + + [DataMember(Name = "sections")] + public IEnumerable
Sections { get; set; } + + [DataMember(Name = "contentStartNode")] + public EntityBasic ContentStartNode { get; set; } + + [DataMember(Name = "mediaStartNode")] + public EntityBasic MediaStartNode { get; set; } + + /// + /// The number of users assigned to this group + /// + [DataMember(Name = "userCount")] + public int UserCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs new file mode 100644 index 0000000000..ea8eaa4b6c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "userGroup", Namespace = "")] + public class UserGroupDisplay : UserGroupBasic + { + public UserGroupDisplay() + { + Users = Enumerable.Empty(); + AssignedPermissions = Enumerable.Empty(); + } + + [DataMember(Name = "users")] + public IEnumerable Users { get; set; } + + /// + /// The default permissions for the user group organized by permission group name + /// + [DataMember(Name = "defaultPermissions")] + public IDictionary> DefaultPermissions { get; set; } + + /// + /// The assigned permissions for the user group organized by permission group name + /// + [DataMember(Name = "assignedPermissions")] + public IEnumerable AssignedPermissions { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs new file mode 100644 index 0000000000..b38a6d75e3 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Used to assign user group permissions to a content node + /// + [DataContract(Name = "contentPermission", Namespace = "")] + public class UserGroupPermissionsSave : IValidatableObject + { + public UserGroupPermissionsSave() + { + AssignedPermissions = new Dictionary>(); + } + + //TODO: we should have an option to clear the permissions assigned to this node and instead just have them inherit - yes once we actually have inheritance! + + [DataMember(Name = "contentId", IsRequired = true)] + [Required] + public int ContentId { get; set; } + + /// + /// A dictionary of permissions to assign, the key is the user group id + /// + [DataMember(Name = "permissions")] + public IDictionary> AssignedPermissions { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (AssignedPermissions.SelectMany(x => x.Value).Any(x => x.IsNullOrWhiteSpace())) + { + yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs new file mode 100644 index 0000000000..872834988d --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "userGroup", Namespace = "")] + public class UserGroupSave : EntityBasic, IValidatableObject + { + /// + /// The action to perform when saving this user group + /// + /// + /// If either of the Publish actions are specified an exception will be thrown. + /// + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required] + public override string Alias { get; set; } + + [DataMember(Name = "sections")] + public IEnumerable Sections { get; set; } + + [DataMember(Name = "users")] + public IEnumerable Users { get; set; } + + [DataMember(Name = "startContentId")] + public int? StartContentId { get; set; } + + [DataMember(Name = "startMediaId")] + public int? StartMediaId { get; set; } + + /// + /// The list of letters (permission codes) to assign as the default for the user group + /// + [DataMember(Name = "defaultPermissions")] + public IEnumerable DefaultPermissions { get; set; } + + /// + /// The assigned permissions for content + /// + /// + /// The key is the content id and the list is the list of letters (permission codes) to assign + /// + [DataMember(Name = "assignedPermissions")] + public IDictionary> AssignedPermissions { get; set; } + + /// + /// The real persisted user group + /// + [IgnoreDataMember] + internal IUserGroup PersistedUserGroup { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (DefaultPermissions.Any(x => x.IsNullOrWhiteSpace())) + { + yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); + } + + foreach (var assignedPermission in AssignedPermissions) + { + foreach (var permission in assignedPermission.Value) + { + if (permission.IsNullOrWhiteSpace()) + yield return new ValidationResult("A permission value cannot be null or empty", new[] { "AssignedPermissions" }); + } + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs new file mode 100644 index 0000000000..06895ccc68 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the data used to invite a user + /// + [DataContract(Name = "user", Namespace = "")] + public class UserInvite : EntityBasic, IValidatableObject + { + [DataMember(Name = "userGroups")] + [Required] + public IEnumerable UserGroups { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } + + [DataMember(Name = "message")] + public string Message { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (UserGroups.Any() == false) + yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs b/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs new file mode 100644 index 0000000000..eca28e1408 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// A bare minimum structure that represents a user, usually attached to other objects + /// + [DataContract(Name = "user", Namespace = "")] + public class UserProfile : IComparable + { + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int UserId { get; set; } + + [DataMember(Name = "name", IsRequired = true)] + [Required] + public string Name { get; set; } + + + int IComparable.CompareTo(object obj) + { + return String.Compare(Name, ((UserProfile)obj).Name, StringComparison.Ordinal); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs new file mode 100644 index 0000000000..1b6a76ae99 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the data used to persist a user + /// + /// + /// This will be different from the model used to display a user and we don't want to "Overpost" data back to the server, + /// and there will most likely be different bits of data required for updating passwords which will be different from the + /// data used to display vs save + /// + [DataContract(Name = "user", Namespace = "")] + public class UserSave : EntityBasic, IValidatableObject + { + [DataMember(Name = "changePassword", IsRequired = true)] + public ChangingPasswordModel ChangePassword { get; set; } + + [DataMember(Name = "id", IsRequired = true)] + [Required] + public new int Id { get; set; } + + [DataMember(Name = "username", IsRequired = true)] + [Required] + public string Username { get; set; } + + [DataMember(Name = "culture", IsRequired = true)] + [Required] + public string Culture { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } + + [DataMember(Name = "userGroups")] + [Required] + public IEnumerable UserGroups { get; set; } + + [DataMember(Name = "startContentIds")] + public int[] StartContentIds { get; set; } + + [DataMember(Name = "startMediaIds")] + public int[] StartMediaIds { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (UserGroups.Any() == false) + yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/DetachedPublishedContent.cs b/src/Umbraco.Web/Models/DetachedPublishedContent.cs new file mode 100644 index 0000000000..5f2ee5e457 --- /dev/null +++ b/src/Umbraco.Web/Models/DetachedPublishedContent.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public class DetachedPublishedContent : PublishedContentBase + { + private readonly PublishedContentType _contentType; + private readonly IEnumerable _properties; + private readonly IPublishedContent _containerNode; + + public DetachedPublishedContent( + Guid key, + string name, + PublishedContentType contentType, + IEnumerable properties, + IPublishedContent containerNode = null, + int sortOrder = 0, + bool isPreviewing = false) + { + Key = key; + Name = name; + _contentType = contentType; + _properties = properties; + SortOrder = sortOrder; + IsDraft = isPreviewing; + _containerNode = containerNode; + } + + public override Guid Key { get; } + + public override int Id => 0; + + public override string Name { get; } + + public override bool IsDraft { get; } + + public override PublishedItemType ItemType => PublishedItemType.Content; + + public override PublishedContentType ContentType => _contentType; + + public override string DocumentTypeAlias => _contentType.Alias; + + public override int DocumentTypeId => _contentType.Id; + + public override IEnumerable Properties => _properties.ToArray(); + + public override IPublishedProperty GetProperty(string alias) + => _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse) + throw new NotSupportedException(); + + return GetProperty(alias); + } + + public override IPublishedContent Parent => null; + + public override IEnumerable Children => Enumerable.Empty(); + + public override int TemplateId => 0; + + public override int SortOrder { get; } + + public override string UrlName => null; + + public override string WriterName => _containerNode?.WriterName; + + public override string CreatorName => _containerNode?.CreatorName; + + public override int WriterId => _containerNode?.WriterId ?? 0; + + public override int CreatorId => _containerNode?.CreatorId ?? 0; + + public override string Path => null; + + public override DateTime CreateDate => _containerNode?.CreateDate ?? DateTime.MinValue; + + public override DateTime UpdateDate => _containerNode?.UpdateDate ?? DateTime.MinValue; + + public override Guid Version => _containerNode?.Version ?? Guid.Empty; + + public override int Level => 0; + } +} diff --git a/src/Umbraco.Web/Models/DetachedPublishedProperty.cs b/src/Umbraco.Web/Models/DetachedPublishedProperty.cs new file mode 100644 index 0000000000..4a76942dd6 --- /dev/null +++ b/src/Umbraco.Web/Models/DetachedPublishedProperty.cs @@ -0,0 +1,52 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + internal class DetachedPublishedProperty : IPublishedProperty + { + private readonly PublishedPropertyType _propertyType; + private readonly object _rawValue; + private readonly Lazy _sourceValue; + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + private readonly bool _isPreview; + + public DetachedPublishedProperty(PublishedPropertyType propertyType, object value) + : this(propertyType, value, false) + { + } + + public DetachedPublishedProperty(PublishedPropertyType propertyType, object value, bool isPreview) + { + _propertyType = propertyType; + _isPreview = isPreview; + + _rawValue = value; + + _sourceValue = new Lazy(() => _propertyType.ConvertDataToSource(_rawValue, _isPreview)); + _objectValue = new Lazy(() => _propertyType.ConvertSourceToObject(_sourceValue.Value, _isPreview)); + _xpathValue = new Lazy(() => _propertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreview)); + } + + public string PropertyTypeAlias + { + get + { + return _propertyType.PropertyTypeAlias; + } + } + + public bool HasValue + { + get { return DataValue != null && DataValue.ToString().Trim().Length > 0; } + } + + public object DataValue { get { return _rawValue; } } + + public object Value { get { return _objectValue.Value; } } + + public object XPathValue { get { return _xpathValue.Value; } } + } +} diff --git a/src/Umbraco.Web/Models/ImageCropDataSet.cs b/src/Umbraco.Web/Models/ImageCropDataSet.cs index fc53704aae..517f56d4ec 100644 --- a/src/Umbraco.Web/Models/ImageCropDataSet.cs +++ b/src/Umbraco.Web/Models/ImageCropDataSet.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.Models public bool HasImage() { - return string.IsNullOrEmpty(Src); + return ! string.IsNullOrEmpty(Src); } public string ToHtmlString() diff --git a/src/Umbraco.Web/Models/LoginModel.cs b/src/Umbraco.Web/Models/LoginModel.cs index 650fa067a3..5e6b2e5c18 100644 --- a/src/Umbraco.Web/Models/LoginModel.cs +++ b/src/Umbraco.Web/Models/LoginModel.cs @@ -10,7 +10,8 @@ namespace Umbraco.Web.Models public string Username { get; set; } [Required] - [DataMember(Name = "password", IsRequired = true)] + [DataMember(Name = "password", IsRequired = true)] + [StringLength(maximumLength:256)] public string Password { get; set; } } diff --git a/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs b/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs index b9857abee2..609f4fa32a 100644 --- a/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs @@ -7,7 +7,6 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Models.Mapping { /// - //TODO: This is horribly inneficient /// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter' /// internal class ActionButtonsResolver @@ -19,12 +18,12 @@ namespace Umbraco.Web.Models.Mapping _userService = userService; } - public IEnumerable Resolve(IContent source) + public IEnumerable Resolve(IContent source) { if (UmbracoContext.Current == null) { //cannot check permissions without a context - return Enumerable.Empty(); + return Enumerable.Empty(); } var svc = _userService.Value; @@ -36,11 +35,9 @@ namespace Umbraco.Web.Models.Mapping // Here we need to do a special check since this could be new content, in which case we need to get the permissions // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0. source.HasIdentity ? source.Id : source.ParentId) - .FirstOrDefault(); + .GetAllPermissions(); - return permissions == null - ? Enumerable.Empty() - : permissions.AssignedPermissions.Where(x => x.Length == 1).Select(x => x.ToUpperInvariant()[0]); + return permissions; } } } diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileProfile.cs b/src/Umbraco.Web/Models/Mapping/CodeFileProfile.cs index fbbdffc304..b165bc3e35 100644 --- a/src/Umbraco.Web/Models/Mapping/CodeFileProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/CodeFileProfile.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; -using Umbraco.Core; +using AutoMapper; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; @@ -15,42 +9,38 @@ namespace Umbraco.Web.Models.Mapping public CodeFileProfile() { CreateMap() - .ForMember(x => x.FileType, exp => exp.Ignore()) - .ForMember(x => x.Notifications, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Snippet, exp => exp.Ignore()); + .ForMember(dest => dest.FileType, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Snippet, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.FileType, exp => exp.Ignore()) - .ForMember(x => x.Notifications, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Snippet, exp => exp.Ignore()); + .ForMember(dest => dest.FileType, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Snippet, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.DeletedDate, exp => exp.Ignore()) - .ForMember(x => x.Id, exp => exp.Ignore()) - .ForMember(x => x.Key, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.CreateDate, exp => exp.Ignore()) - .ForMember(x => x.UpdateDate, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Alias, exp => exp.Ignore()) - .ForMember(x => x.Name, exp => exp.Ignore()) - .ForMember(x => x.OriginalPath, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.OriginalPath, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.DeletedDate, exp => exp.Ignore()) - .ForMember(x => x.Id, exp => exp.Ignore()) - .ForMember(x => x.Key, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.CreateDate, exp => exp.Ignore()) - .ForMember(x => x.UpdateDate, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Alias, exp => exp.Ignore()) - .ForMember(x => x.Name, exp => exp.Ignore()) - .ForMember(x => x.OriginalPath, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.OriginalPath, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentProfile.cs index 645718ab94..ab38258c28 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentProfile.cs @@ -109,7 +109,7 @@ namespace Umbraco.Web.Models.Mapping } //fill in the template config to be passed to the template drop down. - var templateItemConfig = new Dictionary {{"", "Choose..."}}; + var templateItemConfig = new Dictionary {{"", localizedText.Localize("general/choose") } }; foreach (var t in content.ContentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)) { @@ -125,16 +125,16 @@ namespace Umbraco.Web.Models.Mapping { new ContentPropertyDisplay { - Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", Label = localizedText.Localize("content/documentType"), Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), View = Current.PropertyEditors[Constants.PropertyEditors.NoEditAlias].ValueEditor.View }, new ContentPropertyDisplay { - Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}releasedate", Label = localizedText.Localize("content/releaseDate"), - Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, + Value = display.ReleaseDate?.ToIsoString(), //Not editible for people without publish permission (U4-287) View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.NoEditAlias].ValueEditor.View, Config = new Dictionary diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfile.cs index f3d1d9fed3..624cec675f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeProfile.cs @@ -33,26 +33,26 @@ namespace Umbraco.Web.Models.Mapping /* CreateMap() .ConstructUsing(basic => new PropertyType(_dataTypeService.GetDataTypeDefinitionById(basic.DataTypeId))) - .ForMember(type => type.ValidationRegExp, expression => expression.ResolveUsing(basic => basic.Validation.Pattern)) - .ForMember(type => type.Mandatory, expression => expression.ResolveUsing(basic => basic.Validation.Mandatory)) - .ForMember(type => type.Name, expression => expression.ResolveUsing(basic => basic.Label)) - .ForMember(type => type.DataTypeDefinitionId, expression => expression.ResolveUsing(basic => basic.DataTypeId)) - .ForMember(type => type.DataTypeId, expression => expression.Ignore()) - .ForMember(type => type.PropertyEditorAlias, expression => expression.Ignore()) - .ForMember(type => type.HelpText, expression => expression.Ignore()) - .ForMember(type => type.Key, expression => expression.Ignore()) - .ForMember(type => type.CreateDate, expression => expression.Ignore()) - .ForMember(type => type.UpdateDate, expression => expression.Ignore()) - .ForMember(type => type.DeletedDate, expression => expression.Ignore()) - .ForMember(type => type.HasIdentity, expression => expression.Ignore()); + .ForMember(type => type.ValidationRegExp, opt => opt.ResolveUsing(basic => basic.Validation.Pattern)) + .ForMember(type => type.Mandatory, opt => opt.ResolveUsing(basic => basic.Validation.Mandatory)) + .ForMember(type => type.Name, opt => opt.ResolveUsing(basic => basic.Label)) + .ForMember(type => type.DataTypeDefinitionId, opt => opt.ResolveUsing(basic => basic.DataTypeId)) + .ForMember(type => type.DataTypeId, opt => opt.Ignore()) + .ForMember(type => type.PropertyEditorAlias, opt => opt.Ignore()) + .ForMember(type => type.HelpText, opt => opt.Ignore()) + .ForMember(type => type.Key, opt => opt.Ignore()) + .ForMember(type => type.CreateDate, opt => opt.Ignore()) + .ForMember(type => type.UpdateDate, opt => opt.Ignore()) + .ForMember(type => type.DeletedDate, opt => opt.Ignore()) + .ForMember(type => type.HasIdentity, opt => opt.Ignore()); */ CreateMap() //do the base mapping .MapBaseContentTypeSaveToEntity() .ConstructUsing((source) => new ContentType(source.ParentId)) - .ForMember(source => source.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) + .ForMember(source => source.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) .AfterMap((source, dest) => { dest.AllowedTemplates = source.AllowedTemplates @@ -63,7 +63,7 @@ namespace Umbraco.Web.Models.Mapping if (source.DefaultTemplate != null) dest.SetDefaultTemplate(_fileService.GetTemplate(source.DefaultTemplate)); - ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService); + ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService); }); CreateMap() @@ -72,7 +72,7 @@ namespace Umbraco.Web.Models.Mapping .ConstructUsing((source) => new MediaType(source.ParentId)) .AfterMap((source, dest) => { - ContentTypeModelMapperExtensions.AfterMapMediaTypeSaveToEntity(source, dest, _mediaTypeService); + ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, _mediaTypeService); }); CreateMap() @@ -81,13 +81,13 @@ namespace Umbraco.Web.Models.Mapping .ConstructUsing((source) => new MemberType(source.ParentId)) .AfterMap((source, dest) => { - ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService); + ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService); //map the MemberCanEditProperty,MemberCanViewProperty - foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) + foreach (var propertyType in source.Groups.SelectMany(dest => dest.Properties)) { var localCopy = propertyType; - var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + var destProp = dest.PropertyTypes.SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); if (destProp != null) { dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); @@ -96,7 +96,7 @@ namespace Umbraco.Web.Models.Mapping } }); - CreateMap().ConvertUsing(x => x.Alias); + CreateMap().ConvertUsing(dest => dest.Alias); CreateMap() //map base logic @@ -107,7 +107,7 @@ namespace Umbraco.Web.Models.Mapping foreach (var propertyType in memberType.PropertyTypes) { var localCopy = propertyType; - var displayProp = display.Groups.SelectMany(x => x.Properties).SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + var displayProp = display.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); if (displayProp != null) { displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); @@ -135,9 +135,9 @@ namespace Umbraco.Web.Models.Mapping CreateMap() //map base logic .MapBaseContentTypeEntityToDisplay(_propertyEditors, _dataTypeService, _contentTypeService) - .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) - .ForMember(display => display.Notifications, expression => expression.Ignore()) + .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) + .ForMember(display => display.Notifications, opt => opt.Ignore()) .AfterMap((source, dest) => { //sync templates @@ -159,11 +159,14 @@ namespace Umbraco.Web.Models.Mapping }); CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key))); + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key))); + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MediaType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key))); + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.DocumentType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()); CreateMap() @@ -174,29 +177,29 @@ namespace Umbraco.Web.Models.Mapping return new PropertyType(dataType, propertyTypeBasic.Alias); }) + .IgnoreEntityCommonProperties() + // see note above - have to do this here? - .ForMember(type => type.PropertyEditorAlias, expression => expression.Ignore()) - .ForMember(type => type.DeletedDate, expression => expression.Ignore()) + .ForMember(dest => dest.PropertyEditorAlias, opt => opt.Ignore()) + .ForMember(dest => dest.DeletedDate, opt => opt.Ignore()) //only map if it is actually set - .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) - .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) - .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Condition(source => source.Id > 0)) //only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the // IContentType.AddPropertyType - .ForMember(dest => dest.PropertyGroupId, expression => expression.Condition(source => source.GroupId > 0)) - .ForMember(type => type.PropertyGroupId, expression => expression.MapFrom(display => new Lazy(() => display.GroupId, false))) - .ForMember(type => type.Key, expression => expression.Ignore()) - .ForMember(type => type.HelpText, expression => expression.Ignore()) - .ForMember(type => type.HasIdentity, expression => expression.Ignore()) + .ForMember(dest => dest.PropertyGroupId, opt => opt.Condition(source => source.GroupId > 0)) + .ForMember(dest => dest.PropertyGroupId, opt => opt.MapFrom(display => new Lazy(() => display.GroupId, false))) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.HelpText, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()) //ignore because this is set in the ctor NOT ON UPDATE, STUPID! - //.ForMember(type => type.Alias, expression => expression.Ignore()) + //.ForMember(type => type.Alias, opt => opt.Ignore()) //ignore because this is obsolete and shouldn't be used - .ForMember(type => type.DataTypeId, expression => expression.Ignore()) - .ForMember(type => type.Mandatory, expression => expression.MapFrom(display => display.Validation.Mandatory)) - .ForMember(type => type.ValidationRegExp, expression => expression.MapFrom(display => display.Validation.Pattern)) - .ForMember(type => type.DataTypeDefinitionId, expression => expression.MapFrom(display => display.DataTypeId)) - .ForMember(type => type.Name, expression => expression.MapFrom(display => display.Label)); + .ForMember(dest => dest.DataTypeId, opt => opt.Ignore()) + .ForMember(dest => dest.Mandatory, opt => opt.MapFrom(source => source.Validation.Mandatory)) + .ForMember(dest => dest.ValidationRegExp, opt => opt.MapFrom(source => source.Validation.Pattern)) + .ForMember(dest => dest.DataTypeDefinitionId, opt => opt.MapFrom(source => source.DataTypeId)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(source => source.Label)); #region *** Used for mapping on top of an existing display object from a save object *** @@ -208,12 +211,12 @@ namespace Umbraco.Web.Models.Mapping CreateMap() .MapBaseContentTypeSaveToDisplay() - .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) + .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) .AfterMap((source, dest) => { //sync templates - var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias); + var destAllowedTemplateAliases = dest.AllowedTemplates.Select(dest => dest.Alias); //if the dest is set and it's the same as the source, then don't change if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) { @@ -256,28 +259,23 @@ namespace Umbraco.Web.Models.Mapping .MapPropertyGroupBasicToPropertyGroupDisplay, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(); CreateMap() - .ForMember(g => g.Editor, expression => expression.Ignore()) - .ForMember(g => g.View, expression => expression.Ignore()) - .ForMember(g => g.Config, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeName, expression => expression.Ignore()) + .ForMember(g => g.Editor, opt => opt.Ignore()) + .ForMember(g => g.View, opt => opt.Ignore()) + .ForMember(g => g.Config, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) .ForMember(g => g.Locked, exp => exp.Ignore()); CreateMap() - .ForMember(g => g.Editor, expression => expression.Ignore()) - .ForMember(g => g.View, expression => expression.Ignore()) - .ForMember(g => g.Config, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeName, expression => expression.Ignore()) + .ForMember(g => g.Editor, opt => opt.Ignore()) + .ForMember(g => g.View, opt => opt.Ignore()) + .ForMember(g => g.Config, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) .ForMember(g => g.Locked, exp => exp.Ignore()); #endregion - - - } - - } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs similarity index 97% rename from src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs rename to src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs index e845f680f7..c03e556ad9 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -18,7 +17,7 @@ namespace Umbraco.Web.Models.Mapping /// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings /// to re-use mappings. /// - internal static class ContentTypeModelMapperExtensions + internal static class ContentTypeProfileExtensions { public static IMappingExpression MapPropertyGroupBasicToPropertyGroupPersistence( this IMappingExpression mapping) @@ -26,11 +25,10 @@ namespace Umbraco.Web.Models.Mapping where TPropertyTypeBasic : PropertyTypeBasic { return mapping + .IgnoreEntityCommonProperties() .ForMember(dest => dest.Id, map => map.Condition(src => src.Id > 0)) .ForMember(dest => dest.Key, map => map.Ignore()) - .ForMember(dest => dest.HasIdentity, map => map.Ignore()) - .ForMember(dest => dest.CreateDate, map => map.Ignore()) - .ForMember(dest => dest.UpdateDate, map => map.Ignore()) + .ForMember(dest => dest.HasIdentity, map => map.Ignore()) .ForMember(dest => dest.DeletedDate, map => map.Ignore()) .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); } @@ -132,6 +130,7 @@ namespace Umbraco.Web.Models.Mapping return mapping .ForMember(dest => dest.Udi, opt => opt.ResolveUsing(src => contentTypeUdiResolver.Resolve(src))) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) .ForMember(dest => dest.Errors, opt => opt.Ignore()) .ForMember(dest => dest.AllowAsRoot, opt => opt.MapFrom(src => src.AllowedAsRoot)) .ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore()) @@ -165,9 +164,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) //These get persisted as part of the saving procedure, nothing to do with the display model - .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) - .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()) - .ForMember(dest => dest.DeletedDate, opt => opt.Ignore()) + .IgnoreDeletableEntityCommonProperties() .ForMember(dest => dest.AllowedAsRoot, opt => opt.MapFrom(src => src.AllowAsRoot)) .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) @@ -256,7 +253,7 @@ namespace Umbraco.Web.Models.Mapping } private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) - where TPropertyType: PropertyTypeBasic + where TPropertyType : PropertyTypeBasic { PropertyGroup destGroup; if (sourceGroup.Id > 0) diff --git a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs index 781b93cc2a..4c9eff3434 100644 --- a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping { @@ -18,9 +19,9 @@ namespace Umbraco.Web.Models.Mapping _userService = userService; } - public UserBasic Resolve(IContent source) + public UserProfile Resolve(IContent source) { - return Mapper.Map(source.GetWriterProfile(_userService)); + return Mapper.Map(source.GetWriterProfile(_userService)); } } } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeProfile.cs index c46498ecdd..a6c64834ef 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeProfile.cs @@ -95,6 +95,7 @@ namespace Umbraco.Web.Models.Mapping CreateMap() .ConstructUsing(src => new DataTypeDefinition(src.SelectedEditor) {CreateDate = DateTime.Now}) + .IgnoreDeletableEntityCommonProperties() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) //we have to ignore the Key otherwise this will reset the UniqueId field which should never change! // http://issues.umbraco.org/issue/U4-3911 @@ -104,10 +105,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.DatabaseType, opt => opt.ResolveUsing(src => databaseTypeResolver.Resolve(src))) .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) .ForMember(dest => dest.Level, opt => opt.Ignore()) - .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) - .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) - .ForMember(dest => dest.DeletedDate, opt => opt.Ignore()) - .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()); + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()); //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals CreateMap>() diff --git a/src/Umbraco.Web/Models/Mapping/EntityProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityProfile.cs index 3a8a9a0e93..ff5999f883 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityProfile.cs @@ -68,19 +68,6 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Trashed, opt => opt.Ignore()) .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - //CreateMap() - // .ConstructUsing(basic => new Template(basic.Name, basic.Alias) - // { - // Id = Convert.ToInt32(basic.Id), - // Key = basic.Key - // }) - // .ForMember(t => t.Path, opt => opt.Ignore()) - // .ForMember(t => t.Id, opt => opt.MapFrom(template => Convert.ToInt32(template.Id))) - // .ForMember(dest => dest.VirtualPath, opt => opt.Ignore()) - // .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) - // .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()) - // .ForMember(dest => dest.Content, opt => opt.Ignore()); - CreateMap() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => new Lazy(() => Convert.ToInt32(src.Id)))) .ForMember(dest => dest.SortOrder, opt => opt.Ignore()); @@ -92,8 +79,32 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Trashed, opt => opt.Ignore()) .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - CreateMap() + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(src.NodeObjectTypeId), src.Key))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src=> src.ContentTypeIcon)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Score, opt => opt.Ignore()) + .AfterMap((entity, basic) => + { + if (basic.Icon.IsNullOrWhiteSpace()) + { + if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid) + basic.Icon = "icon-user"; + else if (entity.NodeObjectTypeId == Constants.ObjectTypes.DataTypeGuid) + basic.Icon = "icon-autofill"; + else if (entity.NodeObjectTypeId == Constants.ObjectTypes.DocumentTypeGuid) + basic.Icon = "icon-item-arrangement"; + else if (entity.NodeObjectTypeId == Constants.ObjectTypes.MediaTypeGuid) + basic.Icon = "icon-thumbnails"; + else if (entity.NodeObjectTypeId == Constants.ObjectTypes.TemplateTypeGuid) + basic.Icon = "icon-newspaper-alt"; + } + }); + + CreateMap() //default to document icon + .ForMember(dest => dest.Score, opt => opt.MapFrom(result => result.Score)) .ForMember(dest => dest.Udi, opt => opt.Ignore()) .ForMember(dest => dest.Icon, opt => opt.Ignore()) .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) @@ -158,11 +169,11 @@ namespace Umbraco.Web.Models.Mapping } }); - CreateMap>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + CreateMap>() + .ConvertUsing(results => results.Select(Mapper.Map).ToList()); - CreateMap, IEnumerable>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + CreateMap, IEnumerable>() + .ConvertUsing(results => results.Select(Mapper.Map).ToList()); } } } diff --git a/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs new file mode 100644 index 0000000000..c2a29f0d60 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Mapping extension methods for re-use with other mappers (saves code duplication) + /// + internal static class EntityProfileExtensions + { + /// + /// Ignores readonly properties and the date values + /// + /// + /// + public static IMappingExpression IgnoreDeletableEntityCommonProperties(this IMappingExpression mapping) + where TDest: IDeletableEntity + { + return mapping + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.DeletedDate, opt => opt.Ignore()); + } + + /// + /// Ignores readonly properties and the date values + /// + /// + /// + public static IMappingExpression IgnoreEntityCommonProperties(this IMappingExpression mapping) + where TDest : IEntity + { + return mapping + .IgnoreAllPropertiesWithAnInaccessibleSetter() + .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) + .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MemberProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberProfile.cs index 46587590a8..12bf18b6c9 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberProfile.cs @@ -13,6 +13,7 @@ using System.Linq; using Umbraco.Core.Security; using Umbraco.Web.Composing; using Umbraco.Web.Trees; +using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping { @@ -110,7 +111,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate)) .ForMember(dest => dest.UpdateDate, opt => opt.MapFrom(src => src.LastActivityDate)) .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - .ForMember(dest => dest.Owner, opt => opt.UseValue(new UserBasic {Name = "Admin", UserId = 0})) + .ForMember(dest => dest.Owner, opt => opt.UseValue(new UserProfile {Name = "Admin", UserId = 0})) .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-user")) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.UserName)) .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs index facaa91377..64521bae3f 100644 --- a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping { @@ -20,9 +21,9 @@ namespace Umbraco.Web.Models.Mapping _userService = userService; } - public UserBasic Resolve(TPersisted source) + public UserProfile Resolve(TPersisted source) { - return Mapper.Map(source.GetCreatorProfile(_userService)); + return Mapper.Map(source.GetCreatorProfile(_userService)); } } } diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 321cef3ecc..e3b8b2dfca 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -20,16 +20,14 @@ namespace Umbraco.Web.Models.Mapping public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) { - if (localizedTextService == null) throw new ArgumentNullException("localizedTextService"); - _localizedTextService = localizedTextService; + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); IgnoreProperties = new List(); } public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) : this(localizedTextService) { - if (ignoreProperties == null) throw new ArgumentNullException("ignoreProperties"); - IgnoreProperties = ignoreProperties; + IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); } /// @@ -65,14 +63,14 @@ namespace Umbraco.Web.Models.Mapping { new ContentPropertyDisplay { - Alias = string.Format("{0}id", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id", Label = "Id", Value = Convert.ToInt32(display.Id).ToInvariantString() + "
" + display.Key + "", View = labelEditor }, new ContentPropertyDisplay { - Alias = string.Format("{0}creator", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}creator", Label = localizedTextService.Localize("content/createBy"), Description = localizedTextService.Localize("content/createByDesc"), Value = display.Owner.Name, @@ -80,7 +78,7 @@ namespace Umbraco.Web.Models.Mapping }, new ContentPropertyDisplay { - Alias = string.Format("{0}createdate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}createdate", Label = localizedTextService.Localize("content/createDate"), Description = localizedTextService.Localize("content/createDateDesc"), Value = display.CreateDate.ToIsoString(), @@ -88,7 +86,7 @@ namespace Umbraco.Web.Models.Mapping }, new ContentPropertyDisplay { - Alias = string.Format("{0}updatedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}updatedate", Label = localizedTextService.Localize("content/updateDate"), Description = localizedTextService.Localize("content/updateDateDesc"), Value = display.UpdateDate.ToIsoString(), @@ -103,13 +101,10 @@ namespace Umbraco.Web.Models.Mapping } //now add the user props - contentProps.AddRange(currProps); - - //callback - if (onGenericPropertiesMapped != null) - { - onGenericPropertiesMapped(contentProps); - } + contentProps.AddRange(currProps); + + //callback + onGenericPropertiesMapped?.Invoke(contentProps); //re-assign genericProps.Properties = contentProps; @@ -161,20 +156,30 @@ namespace Umbraco.Web.Models.Mapping throw new NullReferenceException("The property editor with alias " + dt.PropertyEditorAlias + " does not exist"); } - var listViewTab = new Tab(); - listViewTab.Alias = Constants.Conventions.PropertyGroups.ListViewGroupName; - listViewTab.Label = localizedTextService.Localize("content/childItems"); - listViewTab.Id = 25; - listViewTab.IsActive = true; + var listViewTab = new Tab + { + Alias = Constants.Conventions.PropertyGroups.ListViewGroupName, + Label = localizedTextService.Localize("content/childItems"), + Id = display.Tabs.Count() + 1, + IsActive = true + }; var listViewConfig = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals); //add the entity type to the config - listViewConfig["entityType"] = entityType; + listViewConfig["entityType"] = entityType; + + //Override Tab Label if tabName is provided + if (listViewConfig.ContainsKey("tabName")) + { + var configTabName = listViewConfig["tabName"]; + if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) + listViewTab.Label = configTabName.ToString(); + } var listViewProperties = new List(); listViewProperties.Add(new ContentPropertyDisplay { - Alias = string.Format("{0}containerView", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", Label = "", Value = null, View = editor.ValueEditor.View, diff --git a/src/Umbraco.Web/Models/Mapping/TemplateProfile.cs b/src/Umbraco.Web/Models/Mapping/TemplateProfile.cs index 6647a1d548..fe2ee465d0 100644 --- a/src/Umbraco.Web/Models/Mapping/TemplateProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/TemplateProfile.cs @@ -9,19 +9,16 @@ namespace Umbraco.Web.Models.Mapping public TemplateProfile() { CreateMap() - .ForMember(x => x.Notifications, exp => exp.Ignore()); + .ForMember(dest => dest.Notifications, opt => opt.Ignore()); CreateMap() - .ForMember(x => x.DeletedDate, exp => exp.Ignore()) - .ForMember(x => x.Key, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.CreateDate, exp => exp.Ignore()) - .ForMember(x => x.UpdateDate, exp => exp.Ignore()) - .ForMember(x => x.VirtualPath, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.MasterTemplateId, exp => exp.Ignore()) // ok, assigned when creating the template - .ForMember(x => x.IsMasterTemplate, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.VirtualPath, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.MasterTemplateId, opt => opt.Ignore()) // ok, assigned when creating the template + .ForMember(dest => dest.IsMasterTemplate, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); } } } diff --git a/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs new file mode 100644 index 0000000000..b240100011 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Converts an IUserGroup instance into a dictionary of permissions by category + /// + internal class UserGroupDefaultPermissionsResolver + { + private readonly ILocalizedTextService _textService; + private readonly ActionCollection _actions; + + public UserGroupDefaultPermissionsResolver(ILocalizedTextService textService, ActionCollection actions) + { + _actions = actions; + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + } + + public IDictionary> Resolve(IUserGroup source) + { + return _actions + .Where(x => x.CanBePermissionAssigned) + .Select(x => GetPermission(x, source)) + .GroupBy(x => x.Category) + .ToDictionary(x => x.Key, x => (IEnumerable) x.ToArray()); + } + + private Permission GetPermission(IAction action, IUserGroup source) + { + var result = new Permission(); + var attribute = action.GetType().GetCustomAttribute(false); + result.Category = attribute == null + ? _textService.Localize($"actionCategories/{Constants.Conventions.PermissionCategories.OtherCategory}") + : _textService.Localize($"actionCategories/{attribute.Category}"); + result.Name = attribute == null || attribute.Name.IsNullOrWhiteSpace() + ? _textService.Localize($"actions/{action.Alias}") + : attribute.Name; + result.Description = _textService.Localize($"actionDescriptions/{action.Alias}"); + result.Icon = action.Icon; + result.Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)); + result.PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture); + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/UserProfile.cs b/src/Umbraco.Web/Models/Mapping/UserProfile.cs index d0b1dd6d87..b223f7ad40 100644 --- a/src/Umbraco.Web/Models/Mapping/UserProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/UserProfile.cs @@ -1,61 +1,410 @@ using System; +using System.Collections.Generic; +using System.Linq; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Models; -using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Models.Mapping { internal class UserProfile : Profile { - private readonly ILocalizedTextService _textService; - - public UserProfile(ILocalizedTextService textService) + public UserProfile(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService, + IRuntimeCacheProvider runtimeCache, + ActionCollection actions) { - _textService = textService; + var userGroupDefaultPermissionsResolver = new UserGroupDefaultPermissionsResolver(textService, actions); + + CreateMap() + .ConstructUsing((UserGroupSave save) => new UserGroup { CreateDate = DateTime.Now }) + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Condition(source => GetIntId(source.Id) > 0)) + .ForMember(dest => dest.Id, opt => opt.MapFrom(source => GetIntId(source.Id))) + .ForMember(dest => dest.Permissions, opt => opt.MapFrom(source => source.DefaultPermissions)) + .AfterMap((save, userGroup) => + { + userGroup.ClearAllowedSections(); + foreach (var section in save.Sections) + { + userGroup.AddAllowedSection(section); + } + }); + + //Used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! + CreateMap() + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Condition(src => GetIntId(src.Id) > 0)) + .ForMember(dest => dest.SessionTimeout, opt => opt.Ignore()) + .ForMember(dest => dest.EmailConfirmedDate, opt => opt.Ignore()) + .ForMember(dest => dest.UserType, opt => opt.Ignore()) + .ForMember(dest => dest.InvitedDate, opt => opt.Ignore()) + .ForMember(dest => dest.SecurityStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Avatar, opt => opt.Ignore()) + .ForMember(dest => dest.ProviderUserKey, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordAnswerValue, opt => opt.Ignore()) + .ForMember(dest => dest.PasswordQuestion, opt => opt.Ignore()) + .ForMember(dest => dest.Comments, opt => opt.Ignore()) + .ForMember(dest => dest.IsApproved, opt => opt.Ignore()) + .ForMember(dest => dest.IsLockedOut, opt => opt.Ignore()) + .ForMember(dest => dest.LastLoginDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastPasswordChangeDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastLockoutDate, opt => opt.Ignore()) + .ForMember(dest => dest.FailedPasswordAttempts, opt => opt.Ignore()) + .ForMember(user => user.Language, opt => opt.MapFrom(save => save.Culture)) + .AfterMap((save, user) => + { + user.ClearGroups(); + var foundGroups = userService.GetUserGroupsByAlias(save.UserGroups.ToArray()); + foreach (var group in foundGroups) + { + user.AddGroup(group.ToReadOnlyGroup()); + } + }); + + CreateMap() + .IgnoreDeletableEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.StartContentIds, opt => opt.Ignore()) + .ForMember(dest => dest.StartMediaIds, opt => opt.Ignore()) + .ForMember(dest => dest.UserType, opt => opt.Ignore()) + .ForMember(dest => dest.Language, opt => opt.Ignore()) + .ForMember(dest => dest.Username, opt => opt.Ignore()) + .ForMember(dest => dest.PasswordQuestion, opt => opt.Ignore()) + .ForMember(dest => dest.SessionTimeout, opt => opt.Ignore()) + .ForMember(dest => dest.EmailConfirmedDate, opt => opt.Ignore()) + .ForMember(dest => dest.InvitedDate, opt => opt.Ignore()) + .ForMember(dest => dest.SecurityStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Avatar, opt => opt.Ignore()) + .ForMember(dest => dest.ProviderUserKey, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordAnswerValue, opt => opt.Ignore()) + .ForMember(dest => dest.Comments, opt => opt.Ignore()) + .ForMember(dest => dest.IsApproved, opt => opt.Ignore()) + .ForMember(dest => dest.IsLockedOut, opt => opt.Ignore()) + .ForMember(dest => dest.LastLoginDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastPasswordChangeDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastLockoutDate, opt => opt.Ignore()) + .ForMember(dest => dest.FailedPasswordAttempts, opt => opt.Ignore()) + //all invited users will not be approved, completing the invite will approve the user + .ForMember(user => user.IsApproved, opt => opt.UseValue(false)) + .AfterMap((invite, user) => + { + user.ClearGroups(); + var foundGroups = userService.GetUserGroupsByAlias(invite.UserGroups.ToArray()); + foreach (var group in foundGroups) + { + user.AddGroup(group.ToReadOnlyGroup()); + } + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.UserCount, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + }); + + //create a map to assign a user group's default permissions to the AssignedUserGroupPermissions instance + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.MapFrom(group => group.Id)) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.DefaultPermissions, opt => opt.ResolveUsing(src => userGroupDefaultPermissionsResolver.Resolve(src))) + //these will be manually mapped and by default they are null + .ForMember(dest => dest.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((group, display) => + { + if (display.Icon.IsNullOrWhiteSpace()) + { + display.Icon = "icon-users"; + } + }); + + CreateMap() + .ForMember(x => x.Udi, opt => opt.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) + .ForMember(basic => basic.Icon, opt => opt.MapFrom(entity => entity.ContentTypeIcon)) + .ForMember(dto => dto.Trashed, opt => opt.Ignore()) + .ForMember(x => x.Alias, opt => opt.Ignore()) + .ForMember(x => x.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((entity, basic) => + { + if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid && basic.Icon.IsNullOrWhiteSpace()) + { + basic.Icon = "icon-user"; + } + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.Users, opt => opt.Ignore()) + .ForMember(dest => dest.DefaultPermissions, opt => opt.ResolveUsing(src => userGroupDefaultPermissionsResolver.Resolve(src))) + .ForMember(dest => dest.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + + //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + var users = userService.GetAllInGroup(group.Id); + display.Users = Mapper.Map>(users); + + //Deal with assigned permissions: + + var allContentPermissions = userService.GetPermissions(@group, true) + .ToDictionary(x => x.EntityId, x => x); + + var contentEntities = allContentPermissions.Keys.Count == 0 + ? new IUmbracoEntity[0] + : entityService.GetAll(UmbracoObjectTypes.Document, allContentPermissions.Keys.ToArray()); + + var allAssignedPermissions = new List(); + foreach (var entity in contentEntities) + { + var contentPermissions = allContentPermissions[entity.Id]; + + var assignedContentPermissions = Mapper.Map(entity); + assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(display.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + allAssignedPermissions.Add(assignedContentPermissions); + } + + display.AssignedPermissions = allAssignedPermissions; + }); + + CreateMap() + .ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache))) + .ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate)) + .ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups)) + .ForMember(dest => dest.StartContentIds, opt => opt.UseValue(Enumerable.Empty())) + .ForMember(dest => dest.StartMediaIds, opt => opt.UseValue(Enumerable.Empty())) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService))) + .ForMember( + dest => dest.AvailableCultures, + opt => opt.MapFrom(user => textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) + .ForMember( + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.IsCurrentUser, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ResetPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((user, display) => + { + //Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + + var startContentIds = user.StartContentIds.ToArray(); + if (startContentIds.Length > 0) + { + //TODO: Update GetAll to be able to pass in a parameter like on the normal Get to NOT load in the entire object! + var startNodes = new List(); + if (startContentIds.Contains(-1)) + { + startNodes.Add(RootNode(textService.Localize("content/contentRoot"))); + } + var contentItems = entityService.GetAll(UmbracoObjectTypes.Document, startContentIds); + startNodes.AddRange(Mapper.Map, IEnumerable>(contentItems)); + display.StartContentIds = startNodes; + + + } + var startMediaIds = user.StartMediaIds.ToArray(); + if (startMediaIds.Length > 0) + { + var startNodes = new List(); + if (startContentIds.Contains(-1)) + { + startNodes.Add(RootNode(textService.Localize("media/mediaRoot"))); + } + var mediaItems = entityService.GetAll(UmbracoObjectTypes.Media, startMediaIds); + startNodes.AddRange(Mapper.Map, IEnumerable>(mediaItems)); + display.StartMediaIds = startNodes; + } + }); + + CreateMap() + //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost + //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look + //like the load time is waiting. + .ForMember(detail => + detail.Avatars, + opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache))) + .ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups)) + .ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate)) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService))) + .ForMember( + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.IsCurrentUser, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); CreateMap() - .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) - .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(_textService))) + .ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache))) + .ForMember(dest => dest.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) + .ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(entityService))) + .ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(entityService))) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService))) .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) - .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) + .ForMember(dest => dest.SecondsUntilTimeout, opt => opt.Ignore()) + .AfterMap((user, detail) => + { + //we need to map the legacy UserType + //the best we can do here is to return the user's first user group as a IUserType object + //but we should attempt to return any group that is the built in ones first + var groups = user.Groups.ToArray(); + if (groups.Length == 0) + { + //In backwards compatibility land, a user type cannot be null! so we need to return a fake one. + detail.UserType = "temp"; + } + else + { + var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", "translator" }; + var foundBuiltIn = groups.FirstOrDefault(x => builtIns.Contains(x.Alias)); + if (foundBuiltIn != null) + { + detail.UserType = foundBuiltIn.Alias; + } + else + { + //otherwise return the first + detail.UserType = groups[0].Alias; + } + } - CreateMap() - .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserTypeAlias)) - .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture)) - .ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections)) - .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) - .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); + }); - CreateMap() - .ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); + CreateMap() + .ForMember(dest => dest.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); CreateMap() .ConstructUsing((IUser user) => new UserData()) - .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) - .ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections)) - .ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name)) - .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] {user.UserType.Alias})) - .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) - .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) - .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(_textService))) - .ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); + .ForMember(dest => dest.Id, opt => opt.MapFrom(user => user.Id)) + .ForMember(dest => dest.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections.ToArray())) + .ForMember(dest => dest.RealName, opt => opt.MapFrom(user => user.Name)) + .ForMember(dest => dest.Roles, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray())) + .ForMember(dest => dest.StartContentNodes, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(entityService))) + .ForMember(dest => dest.StartMediaNodes, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(entityService))) + .ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService))) + .ForMember(dest => dest.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp)); + } + private void MapUserGroupBasic(ISectionService sectionService, IEntityService entityService, ILocalizedTextService textService, dynamic group, UserGroupBasic display) + { + var allSections = sectionService.GetSections(); + display.Sections = allSections.Where(x => Enumerable.Contains(group.AllowedSections, x.Alias)).Select(Mapper.Map); + + if (group.StartMediaId > 0) + { + display.MediaStartNode = Mapper.Map( + entityService.Get(group.StartMediaId, UmbracoObjectTypes.Media)); + } + else if (group.StartMediaId == -1) + { + //create the root node + display.MediaStartNode = RootNode(textService.Localize("media/mediaRoot")); + } + + if (group.StartContentId > 0) + { + display.ContentStartNode = Mapper.Map( + entityService.Get(group.StartContentId, UmbracoObjectTypes.Document)); + } + else if (group.StartContentId == -1) + { + //create the root node + display.ContentStartNode = RootNode(textService.Localize("content/contentRoot")); + } + + if (display.Icon.IsNullOrWhiteSpace()) + { + display.Icon = "icon-users"; + } + } + + private EntityBasic RootNode(string name) + { + return new EntityBasic + { + Name = name, + Path = "-1", + Icon = "icon-folder", + Id = -1, + Trashed = false, + ParentId = -1 + }; } private static int GetIntId(object id) @@ -68,6 +417,5 @@ namespace Umbraco.Web.Models.Mapping } return result.Result; } - } } diff --git a/src/Umbraco.Web/Models/PackageInstallModel.cs b/src/Umbraco.Web/Models/PackageInstallModel.cs index 729610e6bb..7748129a40 100644 --- a/src/Umbraco.Web/Models/PackageInstallModel.cs +++ b/src/Umbraco.Web/Models/PackageInstallModel.cs @@ -24,6 +24,10 @@ namespace Umbraco.Web.Models [DataMember(Name = "zipFilePath")] public string ZipFilePath { get; set; } - + /// + /// During installation this can be used to track any pending appdomain restarts + /// + [DataMember(Name = "isRestarting")] + public bool IsRestarting { get; set; } } } diff --git a/src/Umbraco.Web/Models/RelatedLink.cs b/src/Umbraco.Web/Models/RelatedLink.cs index 82c95f2987..2dcb63dd5c 100644 --- a/src/Umbraco.Web/Models/RelatedLink.cs +++ b/src/Umbraco.Web/Models/RelatedLink.cs @@ -1,8 +1,11 @@ -namespace Umbraco.Web.Models +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models { public class RelatedLink : RelatedLinkBase { public int? Id { get; internal set; } internal bool IsDeleted { get; set; } + public IPublishedContent Content { get; set; } } } diff --git a/src/Umbraco.Web/Models/RelatedLinkBase.cs b/src/Umbraco.Web/Models/RelatedLinkBase.cs index c10c220bc7..c2077ce4a9 100644 --- a/src/Umbraco.Web/Models/RelatedLinkBase.cs +++ b/src/Umbraco.Web/Models/RelatedLinkBase.cs @@ -1,6 +1,4 @@ using Newtonsoft.Json; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { @@ -16,7 +14,5 @@ namespace Umbraco.Web.Models public bool IsInternal { get; set; } [JsonProperty("type")] public RelatedLinkType Type { get; set; } - [JsonIgnore] - public IPublishedContent Content { get; set; } } } diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index b23a9a18d7..1f06bc6ca7 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -2,6 +2,7 @@ using System.IO; using System.Threading; using System.Web.Mvc; +using System.Web.Routing; namespace Umbraco.Web.Mvc { @@ -101,9 +102,11 @@ namespace Umbraco.Web.Mvc using (var sw = new StringWriter()) { - var viewResult = !isPartial + var viewResult = isPartial == false ? ViewEngines.Engines.FindView(controller.ControllerContext, viewName, null) : ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName); + if (viewResult.View == null) + throw new InvalidOperationException("No view could be found by name " + viewName); var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); viewResult.View.Render(viewContext, sw); viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View); @@ -111,6 +114,53 @@ namespace Umbraco.Web.Mvc } } + /// + /// Renders the partial view to string. + /// + /// The request context. + /// + /// + /// Name of the view. + /// The model. + /// true if it is a Partial view, otherwise false for a normal view + /// + internal static string RenderViewToString( + this RequestContext requestContext, + ViewDataDictionary viewData, + TempDataDictionary tempData, + string viewName, object model, bool isPartial = false) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (viewData == null) throw new ArgumentNullException("viewData"); + if (tempData == null) throw new ArgumentNullException("tempData"); + + var routeData = requestContext.RouteData; + if (routeData.Values.ContainsKey("controller") == false) + routeData.Values.Add("controller", "Fake"); + viewData.Model = model; + var controllerContext = new ControllerContext( + requestContext.HttpContext, routeData, + new FakeController + { + ViewData = viewData + }); + + using (var sw = new StringWriter()) + { + var viewResult = isPartial == false + ? ViewEngines.Engines.FindView(controllerContext, viewName, null) + : ViewEngines.Engines.FindPartialView(controllerContext, viewName); + if (viewResult.View == null) + throw new InvalidOperationException("No view could be found by name " + viewName); + var viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw); + viewResult.View.Render(viewContext, sw); + viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View); + return sw.GetStringBuilder().ToString(); + } + } + + private class FakeController : ControllerBase { protected override void ExecuteCore() { } } + /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however /// will write to the Response output stream which isn't what we want. Instead, this method will use the same logic inside diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index be1ea75518..98d113f737 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.PropertyEditors allowBulkPublish = true, allowBulkUnpublish = true, allowBulkCopy = true, - allowBulkMove = true, + allowBulkMove = false, allowBulkDelete = true }} }; @@ -58,7 +58,10 @@ namespace Umbraco.Web.PropertyEditors } internal class ListViewPreValueEditor : PreValueEditor - { + { + [PreValueField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")] + public int TabName { get; set; } + [PreValueField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] public int DisplayAtTabNumber { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentController.cs b/src/Umbraco.Web/PropertyEditors/NestedContentController.cs new file mode 100644 index 0000000000..103967d20a --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/NestedContentController.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Web.Editors; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.PropertyEditors +{ + [PluginController("UmbracoApi")] + public class NestedContentController : UmbracoAuthorizedJsonController + { + [System.Web.Http.HttpGet] + public IEnumerable GetContentTypes() + { + return Services.ContentTypeService.GetAll() + .OrderBy(x => x.SortOrder) + .Select(x => new + { + id = x.Id, + guid = x.Key, + name = x.Name, + alias = x.Alias, + icon = x.Icon, + tabs = x.CompositionPropertyGroups.Select(y => y.Name).Distinct() + }); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs b/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs new file mode 100644 index 0000000000..87780fee12 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; + +namespace Umbraco.Web.PropertyEditors +{ + internal static class NestedContentHelper + { + private const string CacheKeyPrefix = "Umbraco.Web.PropertyEditors.NestedContent.GetPreValuesCollectionByDataTypeId_"; + + public static PreValueCollection GetPreValuesCollectionByDataTypeId(int dtdId) + { + var preValueCollection = (PreValueCollection) Current.ApplicationCache.RuntimeCache.GetCacheItem( + string.Concat(CacheKeyPrefix, dtdId), + () => Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtdId)); + + return preValueCollection; + } + + public static void ClearCache(int id) + { + Current.ApplicationCache.RuntimeCache.ClearCacheItem( + string.Concat(CacheKeyPrefix, id)); + } + + public static string GetContentTypeAliasFromItem(JObject item) + { + var contentTypeAliasProperty = item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]; + if (contentTypeAliasProperty == null) + { + return null; + } + + return contentTypeAliasProperty.ToObject(); + } + + public static IContentType GetContentTypeFromItem(JObject item) + { + var contentTypeAlias = GetContentTypeAliasFromItem(item); + if (string.IsNullOrEmpty(contentTypeAlias)) + { + return null; + } + + return Current.Services.ContentTypeService.Get(contentTypeAlias); + } + + #region Conversion from v0.1.1 data formats + + public static void ConvertItemValueFromV011(JObject item, int dtdId, ref PreValueCollection preValues) + { + var contentTypeAlias = GetContentTypeAliasFromItem(item); + if (contentTypeAlias != null) + { + // the item is already in >v0.1.1 format + return; + } + + // old style (v0.1.1) data, let's attempt a conversion + // - get the prevalues (if they're not loaded already) + preValues = preValues ?? GetPreValuesCollectionByDataTypeId(dtdId); + + // - convert the prevalues (if necessary) + ConvertPreValueCollectionFromV011(preValues); + + // - get the content types prevalue as JArray + var preValuesAsDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); + if (!preValuesAsDictionary.ContainsKey(ContentTypesPreValueKey) || string.IsNullOrEmpty(preValuesAsDictionary[ContentTypesPreValueKey]) != false) + { + return; + } + + var preValueContentTypes = JArray.Parse(preValuesAsDictionary[ContentTypesPreValueKey]); + if (preValueContentTypes.Any()) + { + // the only thing we can really do is assume that the item is the first available content type + item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey] = preValueContentTypes.First().Value("ncAlias"); + } + } + + public static void ConvertPreValueCollectionFromV011(PreValueCollection preValueCollection) + { + if (preValueCollection == null) + { + return; + } + + var persistedPreValuesAsDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); + + // do we have a "docTypeGuid" prevalue and no "contentTypes" prevalue? + if (persistedPreValuesAsDictionary.ContainsKey("docTypeGuid") == false || persistedPreValuesAsDictionary.ContainsKey(ContentTypesPreValueKey)) + { + // the prevalues are already in >v0.1.1 format + return; + } + + // attempt to parse the doc type guid + Guid guid; + if (Guid.TryParse(persistedPreValuesAsDictionary["docTypeGuid"], out guid) == false) + { + // this shouldn't happen... but just in case. + return; + } + + // find the content type + var contentType = Current.Services.ContentTypeService.GetAllContentTypes().FirstOrDefault(c => c.Key == guid); + if (contentType == null) + { + return; + } + + // add a prevalue in the format expected by the new (>0.1.1) content type picker/configurator + preValueCollection.PreValuesAsDictionary[ContentTypesPreValueKey] = new PreValue( + string.Format(@"[{{""ncAlias"": ""{0}"", ""ncTabAlias"": ""{1}"", ""nameTemplate"": ""{2}"", }}]", + contentType.Alias, + persistedPreValuesAsDictionary["tabAlias"], + persistedPreValuesAsDictionary["nameTemplate"] + ) + ); + } + + private static string ContentTypesPreValueKey + { + get { return NestedContentPropertyEditor.NestedContentPreValueEditor.ContentTypesPreValueKey; } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs new file mode 100644 index 0000000000..f34980f992 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -0,0 +1,416 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + [PropertyEditor(Constants.PropertyEditors.NestedContentAlias, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list")] + public class NestedContentPropertyEditor : PropertyEditor + { + internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; + + private IDictionary _defaultPreValues; + public override IDictionary DefaultPreValues + { + get => _defaultPreValues; + set => _defaultPreValues = value; + } + + public NestedContentPropertyEditor(ILogger logger) + : base (logger) + { + // Setup default values + _defaultPreValues = new Dictionary + { + {NestedContentPreValueEditor.ContentTypesPreValueKey, ""}, + {"minItems", 0}, + {"maxItems", 0}, + {"confirmDeletes", "1"}, + {"showIcons", "1"} + }; + } + + #region Pre Value Editor + + protected override PreValueEditor CreatePreValueEditor() + { + return new NestedContentPreValueEditor(); + } + + internal class NestedContentPreValueEditor : PreValueEditor + { + internal const string ContentTypesPreValueKey = "contentTypes"; + + [PreValueField(ContentTypesPreValueKey, "Doc Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the doc types to use as the data blueprint.")] + public string[] ContentTypes { get; set; } + + [PreValueField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")] + public string MinItems { get; set; } + + [PreValueField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")] + public string MaxItems { get; set; } + + [PreValueField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] + public string ConfirmDeletes { get; set; } + + [PreValueField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] + public string ShowIcons { get; set; } + + [PreValueField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] + public string HideLabel { get; set; } + + public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) + { + // re-format old style (v0.1.1) pre values if necessary + NestedContentHelper.ConvertPreValueCollectionFromV011(persistedPreVals); + + return base.ConvertDbToEditor(defaultPreVals, persistedPreVals); + } + } + + #endregion + + #region Value Editor + + protected override PropertyValueEditor CreateValueEditor() + { + return new NestedContentPropertyValueEditor(base.CreateValueEditor()); + } + + internal class NestedContentPropertyValueEditor : PropertyValueEditorWrapper + { + public NestedContentPropertyValueEditor(PropertyValueEditor wrapped) + : base(wrapped) + { + Validators.Add(new NestedContentValidator()); + } + + internal ServiceContext Services + { + get { return Current.Services; } + } + + public override void ConfigureForDisplay(PreValueCollection preValues) + { + base.ConfigureForDisplay(preValues); + + var asDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); + if (asDictionary.ContainsKey("hideLabel")) + { + var boolAttempt = asDictionary["hideLabel"].TryConvertTo(); + if (boolAttempt.Success) + { + HideLabel = boolAttempt.Result; + } + } + } + + #region DB to String + + public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + { + // Convert / validate value + if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) + return string.Empty; + + var value = JsonConvert.DeserializeObject>(property.Value.ToString()); + if (value == null) + return string.Empty; + + // Process value + PreValueCollection preValues = null; + for (var i = 0; i < value.Count; i++) + { + var o = value[i]; + var propValues = ((JObject)o); + + // convert from old style (v0.1.1) data format if necessary + NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); + + var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + if (contentType == null) + { + continue; + } + + var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); + + foreach (var propKey in propValueKeys) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + if (propType == null) + { + if (IsSystemPropertyKey(propKey) == false) + { + // Property missing so just delete the value + propValues[propKey] = null; + } + } + else + { + try + { + // Create a fake property using the property abd stored value + var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); + + // Lookup the property editor + var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + + // Get the editor to do it's conversion, and store it back + propValues[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService); + } + catch (InvalidOperationException) + { + // https://github.com/umco/umbraco-nested-content/issues/111 + // Catch any invalid cast operations as likely means courier failed due to missing + // or trashed item so couldn't convert a guid back to an int + + propValues[propKey] = null; + } + } + + } + } + + // Update the value on the property + property.Value = JsonConvert.SerializeObject(value); + + // Pass the call down + return base.ConvertDbToString(property, propertyType, dataTypeService); + } + + #endregion + + #region DB to Editor + + public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + { + if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) + return string.Empty; + + var value = JsonConvert.DeserializeObject>(property.Value.ToString()); + if (value == null) + return string.Empty; + + // Process value + PreValueCollection preValues = null; + for (var i = 0; i < value.Count; i++) + { + var o = value[i]; + var propValues = ((JObject)o); + + // convert from old style (v0.1.1) data format if necessary + NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); + + var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + if (contentType == null) + { + continue; + } + + var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); + + foreach (var propKey in propValueKeys) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + if (propType == null) + { + if (IsSystemPropertyKey(propKey) == false) + { + // Property missing so just delete the value + propValues[propKey] = null; + } + } + else + { + try + { + // Create a fake property using the property and stored value + var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); + + // Lookup the property editor + var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + + // Get the editor to do it's conversion + var newValue = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService); + + // Store the value back + propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); + } + catch (InvalidOperationException) + { + // https://github.com/umco/umbraco-nested-content/issues/111 + // Catch any invalid cast operations as likely means courier failed due to missing + // or trashed item so couldn't convert a guid back to an int + + propValues[propKey] = null; + } + } + + } + } + + // Update the value on the property + property.Value = JsonConvert.SerializeObject(value); + + // Pass the call down + return base.ConvertDbToEditor(property, propertyType, dataTypeService); + } + + #endregion + + #region Editor to DB + + public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + { + if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) + return null; + + var value = JsonConvert.DeserializeObject>(editorValue.Value.ToString()); + if (value == null) + return null; + + // Issue #38 - Keep recursive property lookups working + if (!value.Any()) + return null; + + // Process value + for (var i = 0; i < value.Count; i++) + { + var o = value[i]; + var propValues = ((JObject)o); + + var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + if (contentType == null) + { + continue; + } + + var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); + + foreach (var propKey in propValueKeys) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + if (propType == null) + { + if (IsSystemPropertyKey(propKey) == false) + { + // Property missing so just delete the value + propValues[propKey] = null; + } + } + else + { + // Fetch the property types prevalue + var propPreValues = Services.DataTypeService.GetPreValuesCollectionByDataTypeId( + propType.DataTypeDefinitionId); + + // Lookup the property editor + var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + + // Create a fake content property data object + var contentPropData = new ContentPropertyData( + propValues[propKey], propPreValues, + new Dictionary()); + + // Get the property editor to do it's conversion + var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, propValues[propKey]); + + // Store the value back + propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); + } + + } + } + + return JsonConvert.SerializeObject(value); + } + + #endregion + } + + internal class NestedContentValidator : IPropertyValidator + { + public IEnumerable Validate(object rawValue, PreValueCollection preValues, PropertyEditor editor) + { + var value = JsonConvert.DeserializeObject>(rawValue.ToString()); + if (value == null) + yield break; + + IDataTypeService dataTypeService = ApplicationContext.Current.Services.DataTypeService; + for (var i = 0; i < value.Count; i++) + { + var o = value[i]; + var propValues = ((JObject)o); + + var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + if (contentType == null) + { + continue; + } + + var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); + + foreach (var propKey in propValueKeys) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + if (propType != null) + { + PreValueCollection propPrevalues = dataTypeService.GetPreValuesCollectionByDataTypeId(propType.DataTypeDefinitionId); + PropertyEditor propertyEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + + foreach (IPropertyValidator validator in propertyEditor.ValueEditor.Validators) + { + foreach (ValidationResult result in validator.Validate(propValues[propKey], propPrevalues, propertyEditor)) + { + result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; + yield return result; + } + } + + // Check mandatory + if (propType.Mandatory) + { + if (propValues[propKey] == null) + yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey }); + else if (propValues[propKey].ToString().IsNullOrWhiteSpace()) + yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", new[] { propKey }); + } + + // Check regex + if (!propType.ValidationRegExp.IsNullOrWhiteSpace() + && propValues[propKey] != null && !propValues[propKey].ToString().IsNullOrWhiteSpace()) + { + var regex = new Regex(propType.ValidationRegExp); + if (!regex.IsMatch(propValues[propKey].ToString())) + { + yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey }); + } + } + } + } + } + } + } + + #endregion + + private static bool IsSystemPropertyKey(string propKey) + { + return propKey == "name" || propKey == "key" || propKey == ContentTypeAliasPropertyKey; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs index ae2e6ffdc0..c957a1da93 100644 --- a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs @@ -11,7 +11,17 @@ namespace Umbraco.Web.PropertyEditors /// The constructor will setup the property editor based on the attribute if one is found /// public TextAreaPropertyEditor(ILogger logger) : base(logger) + { } + + protected override PreValueEditor CreatePreValueEditor() { + return new TextAreaPreValueEditor(); + } + + internal class TextAreaPreValueEditor : PreValueEditor + { + [PreValueField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] + public bool MaxChars { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs index 8a22eb1ddd..4af57045b6 100644 --- a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs @@ -18,7 +18,17 @@ namespace Umbraco.Web.PropertyEditors /// The constructor will setup the property editor based on the attribute if one is found /// public TextboxPropertyEditor(ILogger logger) : base(logger) + { } + + protected override PreValueEditor CreatePreValueEditor() { + return new TextboxPreValueEditor(); + } + + internal class TextboxPreValueEditor : PreValueEditor + { + [PreValueField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] + public bool MaxChars { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index 2384532476..d8b7925b1a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -54,9 +54,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.Contains(propertyType.PropertyTypeAlias.ToLower(CultureInfo.InvariantCulture))) == false) { IPublishedContent content; - if (inter is int) + if (inter is int id) { - var id = (int) inter; content = _facadeAccessor.Facade.ContentCache.GetById(id); if (content != null) return content; @@ -64,6 +63,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters else { var udi = inter as GuidUdi; + if (udi == null) + return null; content = _facadeAccessor.Facade.ContentCache.GetById(udi.Guid); if (content != null) return content; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultipleMediaPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerLegacyValueConverter.cs similarity index 94% rename from src/Umbraco.Web/PropertyEditors/ValueConverters/MultipleMediaPickerPropertyConverter.cs rename to src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerLegacyValueConverter.cs index de9d068984..7a6a5147d1 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultipleMediaPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerLegacyValueConverter.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// The multiple media picker property value converter. /// [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] - public class MultipleMediaPickerPropertyConverter : PropertyValueConverterBase + public class MediaPickerLegacyValueConverter : PropertyValueConverterBase { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ServiceContext _services; @@ -24,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters private readonly PropertyEditorCollection _propertyEditors; private readonly ILogger _logger; - public MultipleMediaPickerPropertyConverter(ServiceContext services, CacheHelper appCache, IUmbracoContextAccessor umbracoContextAccessor, PropertyEditorCollection propertyEditors, ILogger logger) + public MediaPickerLegacyValueConverter(ServiceContext services, CacheHelper appCache, IUmbracoContextAccessor umbracoContextAccessor, PropertyEditorCollection propertyEditors, ILogger logger) { _services = services ?? throw new ArgumentNullException(nameof(services)); _appCache = appCache ?? throw new ArgumentNullException(nameof(appCache)); @@ -72,7 +72,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var error = $"Data type \"{_services.DataTypeService.GetDataTypeDefinitionById(propertyType.DataTypeId).Name}\" is not set to allow multiple items but appears to contain multiple items, check the setting and save the data type again"; - _logger.Warn(error); + _logger.Warn(error); throw new Exception(error); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs similarity index 78% rename from src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs rename to src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index 0e7544890b..26aab5e111 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -1,21 +1,12 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// The media picker 2 value converter -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -23,19 +14,17 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// The media picker property value converter. /// [DefaultPropertyValueConverter] - public class MediaPickerPropertyConverter : PropertyValueConverterBase + public class MediaPickerValueConverter : PropertyValueConverterBase { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ServiceContext _services; - private readonly CacheHelper _appCache; private readonly PropertyEditorCollection _propertyEditors; + private readonly IFacadeAccessor _facadeAccessor; - public MediaPickerPropertyConverter(ServiceContext services, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, CacheHelper appCache) + public MediaPickerValueConverter(ServiceContext services, PropertyEditorCollection propertyEditors, IFacadeAccessor facadeAccessor) { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _appCache = appCache ?? throw new ArgumentNullException(nameof(appCache)); _services = services ?? throw new ArgumentNullException(nameof(services)); _propertyEditors = propertyEditors ?? throw new ArgumentException(nameof(propertyEditors)); + _facadeAccessor = facadeAccessor ?? throw new ArgumentNullException(nameof(facadeAccessor)); } public override bool IsConverter(PublishedPropertyType propertyType) @@ -102,12 +91,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var udis = (Udi[])source; var mediaItems = new List(); - var helper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services, _appCache); if (udis.Any()) { foreach (var udi in udis) { - var item = helper.PublishedContent(udi); + var guidUdi = udi as GuidUdi; + if (guidUdi == null) continue; + var item = _facadeAccessor.Facade.MediaCache.GetById(guidUdi.Guid); if (item != null) mediaItems.Add(item); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs similarity index 74% rename from src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs rename to src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs index 820dbd1aee..881ee164bb 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -2,18 +2,19 @@ using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; namespace Umbraco.Web.PropertyEditors.ValueConverters { [DefaultPropertyValueConverter] - public class MemberPickerPropertyConverter : PropertyValueConverterBase + public class MemberPickerValueConverter : PropertyValueConverterBase { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IFacadeAccessor _facadeAccessor; - public MemberPickerPropertyConverter(IUmbracoContextAccessor umbracoContextAccessor) + public MemberPickerValueConverter(IFacadeAccessor facadeAccessor) { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _facadeAccessor = facadeAccessor ?? throw new ArgumentNullException(nameof(facadeAccessor)); } public override bool IsConverter(PublishedPropertyType propertyType) @@ -47,17 +48,17 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (UmbracoContext.Current != null) { IPublishedContent member; - var membershipHelper = new MembershipHelper(_umbracoContextAccessor.UmbracoContext); - if (source is int) + if (source is int id) { - member = membershipHelper.GetById((int)source); + member = _facadeAccessor.Facade.MemberCache.GetById(id); if (member != null) return member; } else { - var sourceUdi = source as Udi; - member = membershipHelper.Get(sourceUdi); + var sourceUdi = source as GuidUdi; + if (sourceUdi == null) return null; + member = _facadeAccessor.Facade.MemberCache.GetByProviderKey(sourceUdi.Guid); if (member != null) return member; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs similarity index 71% rename from src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs rename to src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index d67e87dbc9..ceff0cc206 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -17,11 +18,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// The multi node tree picker property editor value converter. /// [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] - public class MultiNodeTreePickerPropertyConverter : PropertyValueConverterBase + public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ServiceContext _services; - private readonly CacheHelper _appCache; + private readonly IFacadeAccessor _facadeAccessor; private static readonly List PropertiesToExclude = new List { @@ -29,11 +28,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) }; - public MultiNodeTreePickerPropertyConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, CacheHelper appCache) + public MultiNodeTreePickerValueConverter(IFacadeAccessor facadeAccessor) { - _services = services ?? throw new ArgumentNullException(nameof(services)); - _appCache = appCache ?? throw new ArgumentNullException(nameof(appCache)); - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _facadeAccessor = facadeAccessor ?? throw new ArgumentNullException(nameof(facadeAccessor)); } public override bool IsConverter(PublishedPropertyType propertyType) @@ -87,22 +84,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var multiNodeTreePicker = new List(); - if (nodeIds.Length > 0) + var objectType = UmbracoObjectTypes.Unknown; + + foreach (var nodeId in nodeIds) { - var umbHelper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services, _appCache); - var objectType = UmbracoObjectTypes.Unknown; + var multiNodeTreePickerItem = + GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, id => _facadeAccessor.Facade.ContentCache.GetById(id)) + ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, id => _facadeAccessor.Facade.MediaCache.GetById(id)) + ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, id => _facadeAccessor.Facade.MemberCache.GetById(id)); - foreach (var nodeId in nodeIds) + if (multiNodeTreePickerItem != null) { - var multiNodeTreePickerItem = - GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.Content) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.Media) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.Member); - - if (multiNodeTreePickerItem != null) - { - multiNodeTreePicker.Add(multiNodeTreePickerItem); - } + multiNodeTreePicker.Add(multiNodeTreePickerItem); } } @@ -120,17 +113,20 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) { var multiNodeTreePicker = new List(); - var umbHelper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services, _appCache); - if (udis.Length > 0) + var objectType = UmbracoObjectTypes.Unknown; + + foreach (var udi in udis) { - foreach (var udi in udis) + var guidUdi = udi as GuidUdi; + if (guidUdi == null) continue; + var multiNodeTreePickerItem = + GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, id => _facadeAccessor.Facade.ContentCache.GetById(guidUdi.Guid)) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => _facadeAccessor.Facade.MediaCache.GetById(guidUdi.Guid)) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => _facadeAccessor.Facade.MemberCache.GetByProviderKey(guidUdi.Guid)); + if (multiNodeTreePickerItem != null) { - var item = umbHelper.PublishedContent(udi); - if (item != null) - { - multiNodeTreePicker.Add(item); - } + multiNodeTreePicker.Add(multiNodeTreePickerItem); } } @@ -152,7 +148,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// The type of content expected/supported by /// A function to fetch content of type /// The requested content, or null if either it does not exist or does not match - private IPublishedContent GetPublishedContent(int nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) { // is the actual type supported by the content fetcher? if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs new file mode 100644 index 0000000000..571776b3ab --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + public class NestedContentManyValueConverter : PropertyValueConverterBase + { + private readonly ILogger _logger; + + public NestedContentManyValueConverter(ILogger logger) + { + _logger = logger; + } + + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.IsNestedContentProperty() && !propertyType.IsSingleNestedContentProperty(); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof(IEnumerable); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Content; + + public override object ConvertSourceToInter(IPropertySet owner, PublishedPropertyType propertyType, object source, bool preview) + { + try + { + return propertyType.ConvertPropertyToNestedContent(source, preview); + } + catch (Exception e) + { + _logger.Error("Error converting value", e); + } + + return null; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs new file mode 100644 index 0000000000..4b0621db76 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + internal static class NestedContentPublishedPropertyTypeExtensions + { + public static bool IsNestedContentProperty(this PublishedPropertyType publishedProperty) + { + return publishedProperty.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.NestedContentAlias); + } + + public static bool IsSingleNestedContentProperty(this PublishedPropertyType publishedProperty) + { + if (!publishedProperty.IsNestedContentProperty()) + { + return false; + } + + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId); + var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); + + return preValueDictionary.ContainsKey("minItems") && + int.TryParse(preValueDictionary["minItems"], out var minItems) && minItems == 1 + && preValueDictionary.ContainsKey("maxItems") && + int.TryParse(preValueDictionary["maxItems"], out var maxItems) && maxItems == 1; + } + + public static object ConvertPropertyToNestedContent(this PublishedPropertyType propertyType, object source, bool preview) + { + using (Current.ProfilingLogger.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataTypeId})")) + { + if (source != null && !source.ToString().IsNullOrWhiteSpace()) + { + var rawValue = JsonConvert.DeserializeObject>(source.ToString()); + var processedValue = new List(); + + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); + + for (var i = 0; i < rawValue.Count; i++) + { + var item = (JObject)rawValue[i]; + + // Convert from old style (v.0.1.1) data format if necessary + // - Please note: This call has virtually no impact on rendering performance for new style (>v0.1.1). + // Even so, this should be removed eventually, when it's safe to assume that there is + // no longer any need for conversion. + NestedContentHelper.ConvertItemValueFromV011(item, propertyType.DataTypeId, ref preValueCollection); + + var contentTypeAlias = NestedContentHelper.GetContentTypeAliasFromItem(item); + if (string.IsNullOrEmpty(contentTypeAlias)) + { + continue; + } + + var publishedContentType = Composing.Current.Facade.ContentCache.GetContentType(contentTypeAlias); + if (publishedContentType == null) + { + continue; + } + + var propValues = item.ToObject>(); + var properties = new List(); + + foreach (var jProp in propValues) + { + var propType = publishedContentType.GetPropertyType(jProp.Key); + if (propType != null) + { + properties.Add(new DetachedPublishedProperty(propType, jProp.Value, preview)); + } + } + + // Parse out the name manually + object nameObj = null; + if (propValues.TryGetValue("name", out nameObj)) + { + // Do nothing, we just want to parse out the name if we can + } + + object keyObj; + var key = Guid.Empty; + if (propValues.TryGetValue("key", out keyObj)) + { + key = Guid.Parse(keyObj.ToString()); + } + + // Get the current request node we are embedded in + var pcr = UmbracoContext.Current == null ? null : UmbracoContext.Current.PublishedContentRequest; + var containerNode = pcr != null && pcr.HasPublishedContent ? pcr.PublishedContent : null; + + // Create the model based on our implementation of IPublishedContent + IPublishedContent content = new DetachedPublishedContent( + key, + nameObj == null ? null : nameObj.ToString(), + publishedContentType, + properties.ToArray(), + containerNode, + i, + preview); + + if (Current.PublishedContentModelFactory != null) + { + // Let the current model factory create a typed model to wrap our model + content = (IPublishedContent) Current.PublishedContentModelFactory.CreateModel(content); + } + + // Add the (typed) model as a result + processedValue.Add(content); + } + + if (propertyType.IsSingleNestedContentProperty()) + { + return processedValue.FirstOrDefault(); + } + + return processedValue; + } + } + + return null; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs new file mode 100644 index 0000000000..9ec96a2cb8 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -0,0 +1,40 @@ +using System; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + public class NestedContentSingleValueConverter : PropertyValueConverterBase + { + private readonly ILogger _logger; + + public NestedContentSingleValueConverter(ILogger logger) + { + _logger = logger; + } + + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.IsSingleNestedContentProperty(); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof(IPublishedContent); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Content; + + public override object ConvertSourceToInter(IPropertySet owner, PublishedPropertyType propertyType, object source, bool preview) + { + try + { + return propertyType.ConvertPropertyToNestedContent(source, preview); + } + catch (Exception e) + { + _logger.Error("Error converting value", e); + } + + return null; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs index f9148c3610..86e124a4e4 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs @@ -78,6 +78,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (udiAttempt) { var content = helper.PublishedContent(udiAttempt.Result); + if (content == null) break; a["link"] = helper.NiceUrl(content.Id); } break; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs index 09f32fe9a7..5d4b5b0611 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs @@ -4,14 +4,12 @@ using System.Xml; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Services; using Umbraco.Web.Models; -using Umbraco.Web.Routing; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -21,17 +19,15 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter(typeof(RelatedLinksLegacyValueConverter), typeof(JsonValueConverter))] public class RelatedLinksValueConverter : PropertyValueConverterBase { + private readonly IFacadeAccessor _facadeAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ServiceContext _services; - private readonly CacheHelper _appCache; private readonly ILogger _logger; - public RelatedLinksValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, CacheHelper appCache, ILogger logger) + public RelatedLinksValueConverter(IFacadeAccessor facadeAccessor, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) { + _facadeAccessor = facadeAccessor; _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; - _services = services; - _appCache = appCache; } /// @@ -60,7 +56,6 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); var relatedLinks = new List(); - var helper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services, _appCache); foreach (var linkData in relatedLinksData) { @@ -82,10 +77,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters else { var strLinkId = linkData.Link; - var udiAttempt = strLinkId.TryConvertTo(); + var udiAttempt = strLinkId.TryConvertTo(); if (udiAttempt.Success) { - var content = helper.PublishedContent(udiAttempt.Result); + var content = _facadeAccessor.Facade.ContentCache.GetById(udiAttempt.Result.Guid); if (content != null) { relatedLink.Id = content.Id; diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index f2f99aeea3..9020935b14 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -43,6 +43,7 @@ namespace Umbraco.Web.Security.Identity options, services.UserService, services.MemberTypeService, + services.EntityService, services.ExternalLoginService, userMembershipProvider)); diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs index e8c2ade640..c2e7ff72e6 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs @@ -95,8 +95,8 @@ namespace Umbraco.Web.Security.Identity if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false; if (//check the explicit flag - (checkForceAuthTokens && owinContext.Get("umbraco-force-auth") != null) - || (checkForceAuthTokens && httpContext.Success && httpContext.Result.Items["umbraco-force-auth"] != null) + (checkForceAuthTokens && owinContext.Get(Constants.Security.ForceReAuthFlag) != null) + || (checkForceAuthTokens && httpContext.Success && httpContext.Result.Items[Constants.Security.ForceReAuthFlag] != null) //check back office || request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) //check installer diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs index 59fbc1aacc..a808f7eb62 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; @@ -11,8 +12,10 @@ namespace Umbraco.Web.Security.Identity /// /// Options used to configure auto-linking external OAuth providers /// - public sealed class ExternalSignInAutoLinkOptions + public class ExternalSignInAutoLinkOptions { + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the overload specifying user groups instead and defaultAllowedSections now serves no purpose")] public ExternalSignInAutoLinkOptions( bool autoLinkExternalAccount = false, string defaultUserType = "editor", @@ -21,35 +24,57 @@ namespace Umbraco.Web.Security.Identity { if (string.IsNullOrEmpty(defaultUserType)) throw new ArgumentNullOrEmptyException(nameof(defaultUserType)); - _defaultUserType = defaultUserType; - _defaultAllowedSections = defaultAllowedSections ?? new[] { "content", "media" }; + _defaultUserGroups = new[] {defaultUserType}; _autoLinkExternalAccount = autoLinkExternalAccount; _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; } - private readonly string _defaultUserType; + /// + /// Creates a new instance + /// + /// + /// If null, the default will be the 'editor' group + /// + public ExternalSignInAutoLinkOptions( + bool autoLinkExternalAccount = false, + string[] defaultUserGroups = null, + string defaultCulture = null) + { + _defaultUserGroups = defaultUserGroups ?? new[] { "editor" }; + _autoLinkExternalAccount = autoLinkExternalAccount; + _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; + } + + private readonly string[] _defaultUserGroups; /// /// A callback executed during account auto-linking and before the user is persisted /// public Action OnAutoLinking { get; set; } - /// - /// The default User Type alias to use for auto-linking users - /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the overload specifying user groups instead")] public string GetDefaultUserType(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) { - return _defaultUserType; + return _defaultUserGroups.Length == 0 ? "editor" : _defaultUserGroups[0]; } - private readonly string[] _defaultAllowedSections; - /// - /// The default allowed sections to use for auto-linking users + /// The default User group aliases to use for auto-linking users /// + /// + /// + /// + public string[] GetDefaultUserGroups(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) + { + return _defaultUserGroups; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("The default sections are based on the default user group, this is no longer used")] public string[] GetDefaultAllowedSections(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) { - return _defaultAllowedSections; + return new string[0]; } private readonly bool _autoLinkExternalAccount; diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs index e52e21e4b9..86b1a0522c 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.Security.Identity var httpCtx = Context.TryGetHttpContext(); //check for the special flag in either the owin or http context - var shouldRenew = Context.Get("umbraco-force-auth") != null || (httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null); + var shouldRenew = Context.Get(Constants.Security.ForceReAuthFlag) != null || (httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null); if (shouldRenew) { diff --git a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs index 5000528a1b..64da407021 100644 --- a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs +++ b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs @@ -63,7 +63,18 @@ namespace Umbraco.Web.Security.Identity return null; } - var identity = new UmbracoBackOfficeIdentity(decrypt); + UmbracoBackOfficeIdentity identity; + + try + { + identity = new UmbracoBackOfficeIdentity(decrypt); + } + catch (Exception) + { + //if it cannot be created return null, will be due to serialization errors in user data most likely due to corrupt cookies or cookies + //for previous versions of Umbraco + return null; + } var ticket = new AuthenticationTicket(identity, new AuthenticationProperties { diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 43467f06c9..4f712f013c 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -14,7 +14,6 @@ using Umbraco.Web.PublishedCache; using Umbraco.Core.Cache; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; -using Umbraco.Web.Composing; using Umbraco.Web.Security.Providers; using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions; @@ -27,7 +26,6 @@ namespace Umbraco.Web.Security { private readonly MembershipProvider _membershipProvider; private readonly RoleProvider _roleProvider; - //private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httpContext; private readonly IPublishedMemberCache _memberCache; @@ -643,130 +641,10 @@ namespace Umbraco.Web.Security /// public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { - // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! - - if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); - if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); - - //Are we resetting the password?? - if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) - { - var canReset = membershipProvider.CanResetPassword(_userService); - if (canReset == false) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }) }); - } - if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset requires a password answer", new[] { "resetPassword" }) }); - } - //ok, we should be able to reset it - try - { - var newPass = membershipProvider.ResetPassword( - username, - membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); - - //return the generated pword - return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); - } - catch (Exception ex) - { - Current.Logger.Warn(ex, "Could not reset member password"); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) }); - } - } - - //we're not resetting it so we need to try to change it. - - if (passwordModel.NewPassword.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); - } - - //This is an edge case and is only necessary for backwards compatibility: - var umbracoBaseProvider = membershipProvider as MembershipProviderBase; - if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword) - { - //this provider allows manually changing the password without the old password, so we can just do it - try - { - var result = umbracoBaseProvider.ChangePassword(username, "", passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex) - { - Current.Logger.Warn(ex, "Could not change member password"); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); - } - } - - //The provider does not support manually chaning the password but no old password supplied - need to return an error - if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false) - { - //if password retrieval is not enabled but there is no old password we cannot continue - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) }); - } - - if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) - { - //if an old password is suplied try to change it - - try - { - var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex) - { - Current.Logger.Warn(ex, "Could not change member password"); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); - } - } - - if (membershipProvider.EnablePasswordRetrieval == false) - { - //we cannot continue if we cannot get the current password - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) }); - } - if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) - { - //if the question answer is required but there isn't one, we cannot continue - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) }); - } - - //lets try to get the old one so we can change it - try - { - var oldPassword = membershipProvider.GetPassword( - username, - membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); - - try - { - var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex1) - { - Current.Logger.Warn(ex1, "Could not change member password"); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); - } - - } - catch (Exception ex2) - { - Current.Logger.Warn(ex2, "Could not retrieve member password"); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); - } + var passwordChanger = new PasswordChanger(_logger, _userService); + return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider); } - + /// /// Updates a membership user with all of it's writable properties /// diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 5d6723260a..e45f5e8d05 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -77,7 +77,7 @@ namespace Umbraco.Web.Security.Providers _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { - throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + throw new ProviderException("No default member type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); } _hasDefaultMember = true; } @@ -105,7 +105,7 @@ namespace Umbraco.Web.Security.Providers _defaultMemberTypeAlias = _memberTypeService.GetDefault(); if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { - throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + throw new ProviderException("No default member type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); } _hasDefaultMember = true; } diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 21433b2542..02312318a2 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -167,11 +167,11 @@ namespace Umbraco.Web.Security.Providers username, email, FormatPasswordForStorage(encodedPassword, salt), - memberTypeAlias); + memberTypeAlias, + isApproved); member.PasswordQuestion = passwordQuestion; member.RawPasswordAnswerValue = EncryptString(passwordAnswer); - member.IsApproved = isApproved; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; @@ -417,7 +417,6 @@ namespace Umbraco.Web.Security.Providers //if (answer == null && RequiresQuestionAndAnswer) //{ // UpdateFailureCount(username, "passwordAnswer"); - // throw new ProviderException("Password answer required for password reset."); //} diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 46370cebea..3c6d47be5d 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -50,14 +50,23 @@ namespace Umbraco.Web.Security.Providers base.Initialize(name, config); // test for membertype (if not specified, choose the first member type available) + // We'll support both names for legacy reasons: defaultUserTypeAlias & defaultUserGroupAlias + if (config["defaultUserTypeAlias"] != null) { - _defaultMemberTypeAlias = config["defaultUserTypeAlias"]; - if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) + if (config["defaultUserTypeAlias"].IsNullOrWhiteSpace() == false) { - throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + _defaultMemberTypeAlias = config["defaultUserTypeAlias"]; + _hasDefaultMember = true; + } + } + if (_hasDefaultMember == false && config["defaultUserGroupAlias"] != null) + { + if (config["defaultUserGroupAlias"].IsNullOrWhiteSpace() == false) + { + _defaultMemberTypeAlias = config["defaultUserGroupAlias"]; + _hasDefaultMember = true; } - _hasDefaultMember = true; } } @@ -74,7 +83,7 @@ namespace Umbraco.Web.Security.Providers _defaultMemberTypeAlias = _memberTypeService.GetDefault(); if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { - throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + throw new ProviderException("No default user group alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); } _hasDefaultMember = true; } diff --git a/src/Umbraco.Web/Security/ValidateRequestAttempt.cs b/src/Umbraco.Web/Security/ValidateRequestAttempt.cs index 475ba93576..8fc6ff225a 100644 --- a/src/Umbraco.Web/Security/ValidateRequestAttempt.cs +++ b/src/Umbraco.Web/Security/ValidateRequestAttempt.cs @@ -1,13 +1,14 @@ namespace Umbraco.Web.Security { - internal enum ValidateRequestAttempt + public enum ValidateRequestAttempt { - Success, - FailedNoPrivileges, + Success = 0, + + FailedNoPrivileges = 100, //FailedTimedOut, - FailedNoContextId, - FailedNoSsl + FailedNoContextId = 101, + FailedNoSsl = 102 } } diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 505ec33a73..46bcd0ec50 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.Security { get { - //only load it once per instance! + //only load it once per instance! (but make sure groups are loaded) if (_currentUser == null) { var id = GetUserId(); @@ -167,116 +167,24 @@ namespace Umbraco.Web.Security /// /// /// + /// + /// This uses ASP.NET Identity to perform the validation + /// public virtual bool ValidateBackOfficeCredentials(string username, string password) { - var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return membershipProvider != null && membershipProvider.ValidateUser(username, password); + //find the user by username + var user = UserManager.FindByNameAsync(username).Result; + return user != null && UserManager.CheckPasswordAsync(user, password).Result; } - /// - /// Returns the MembershipUser from the back office membership provider - /// - /// - /// - /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Back office users shouldn't be resolved from the membership provider, they should be resolved usign the BackOfficeUserManager or the IUserService")] 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 = _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; - - //we need to create an Umbraco IUser of a 'writer' type with access to only content - this was how v6 operates. - var writer = _userService.GetUserTypeByAlias("writer"); - - 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(writer) - { - 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, - StartContentId = -1, - StartMediaId = -1, - IsLockedOut = false, - IsApproved = true - }; - user.AddAllowedSection("content"); - - _userService.Save(user); - - return user; - } - - /// - /// Validates the user node tree permissions. - /// - /// - /// The path. - /// The action. - /// - internal bool ValidateUserNodeTreePermissions(IUser umbracoUser, string path, string action) - { - - //we only want permissions for the last node in the pat - var permission = _userService.GetPermissions(umbracoUser, path); - if (permission == null) throw new InvalidOperationException("No permissions found"); - - if (permission.AssignedPermissions.Contains(action, StringComparer.Ordinal) && (path.Contains("-20") || ("," + path + ",").Contains("," + umbracoUser.StartContentId + ","))) - return true; - - var user = umbracoUser; - Current.Logger.Info("User {0} has insufficient permissions in UmbracoEnsuredPage: '{1}', '{2}', '{3}'", () => user.Name, () => path, () => string.Join(",", permission.AssignedPermissions), () => action); - return false; - } - /// /// Validates the current user to see if they have access to the specified app /// @@ -344,16 +252,16 @@ namespace Umbraco.Web.Security /// public virtual bool ValidateCurrentUser() { - var result = ValidateCurrentUser(false); - return result == ValidateRequestAttempt.Success; - } + 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 /// - internal ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions) + 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 @@ -369,7 +277,7 @@ namespace Umbraco.Web.Security var user = CurrentUser; // Check for console access - if (user == null || user.IsApproved == false || (user.IsLockedOut && GlobalSettings.RequestIsInUmbracoApplication(_httpContext))) + 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; @@ -400,7 +308,7 @@ namespace Umbraco.Web.Security /// /// /// - internal bool UserHasAppAccess(string app, IUser user) + internal virtual bool UserHasAppAccess(string app, IUser user) { var apps = user.AllowedSections; return apps.Any(uApp => uApp.InvariantEquals(app)); diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index d691ca040c..3d24b7258a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -52,10 +52,11 @@ namespace Umbraco.Web.Trees queryStrings, application); - return result; + //this will be null if it cannot convert to ta single root section + if (result != null) + return result; } - var collection = new TreeNodeCollection(); foreach (var apptree in appTrees) { @@ -103,6 +104,7 @@ namespace Umbraco.Web.Trees /// /// /// + /// /// private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) { @@ -118,6 +120,16 @@ namespace Umbraco.Web.Trees throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias); } + //if the root node has a route path, we cannot create a single root section because by specifying the route path this would + //override the dashboard route and that means there can be no dashboard for that section which is a breaking change. + if (rootNode.Result.RoutePath.IsNullOrWhiteSpace() == false + && rootNode.Result.RoutePath != "#" + && rootNode.Result.RoutePath != application) + { + //null indicates this cannot be converted + return null; + } + var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( rootId, rootNode.Result.ChildNodesUrl, @@ -125,6 +137,10 @@ namespace Umbraco.Web.Trees rootNode.Result.Name, byControllerAttempt.Result); + //This can't be done currently because the root will default to routing to a dashboard and if we disable dashboards for a section + //that is really considered a breaking change. See above. + //sectionRoot.RoutePath = rootNode.Result.RoutePath; + foreach (var d in rootNode.Result.AdditionalData) { sectionRoot.AdditionalData[d.Key] = d.Value; diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index 60e022a1bf..e628ad77d9 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Linq; using System.Management.Instrumentation; using System.Net.Http; @@ -14,6 +15,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; +using Umbraco.Core.Services; using Current = Umbraco.Web.Composing.Current; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using UrlHelper = System.Web.Http.Routing.UrlHelper; @@ -22,6 +24,48 @@ namespace Umbraco.Web.Trees { internal static class ApplicationTreeExtensions { + private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); + + internal static TreeAttribute GetTreeAttribute(this Type treeControllerType) + { + return TreeAttributeCache.GetOrAdd(treeControllerType, type => + { + //Locate the tree attribute + var treeAttributes = type + .GetCustomAttributes(false) + .ToArray(); + + if (treeAttributes.Length == 0) + { + throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute"); + } + + //assign the properties of this object to those of the metadata attribute + return treeAttributes[0]; + }); + } + + internal static TreeAttribute GetTreeAttribute(this ApplicationTree tree) + { + return tree.GetRuntimeType().GetTreeAttribute(); + } + + internal static string GetRootNodeDisplayName(this TreeAttribute attribute, ILocalizedTextService textService) + { + //if title is defined, return that + if (string.IsNullOrEmpty(attribute.Title) == false) + return attribute.Title; + + + //try to look up a tree header matching the tree alias + var localizedLabel = textService.Localize("treeHeaders/" + attribute.Alias); + if (string.IsNullOrEmpty(localizedLabel) == false) + return localizedLabel; + + //is returned to signal that a label was not found + return "[" + attribute.Alias + "]"; + } + internal static Attempt TryGetControllerTree(this ApplicationTree appTree) { //get reference to all TreeApiControllers diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs new file mode 100644 index 0000000000..12fcff2f56 --- /dev/null +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -0,0 +1,127 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Http.Formatting; +using System.Web.Http; +using umbraco; +using umbraco.businesslogic.Actions; +using umbraco.BusinessLogic.Actions; +using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + /// + /// The content blueprint tree controller + /// + /// + /// This authorizes based on access to the content section even though it exists in the settings + /// + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 8)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class ContentBlueprintTreeController : TreeController + { + + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.ContentBlueprints, "intro"); + + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + //get all blueprints + var entities = Services.EntityService.GetChildren(Constants.System.Root, UmbracoObjectTypes.DocumentBlueprint).ToArray(); + + //check if we're rendering the root in which case we'll render the content types that have blueprints + if (id == Constants.System.Root.ToInvariantString()) + { + //get all blueprint content types + var contentTypeAliases = entities.Select(x => ((UmbracoEntity) x).ContentTypeAlias).Distinct(); + //get the ids + var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray(); + + //now get the entities ... it's a bit round about but still smaller queries than getting all document types + var docTypeEntities = contentTypeIds.Length == 0 + ? new IUmbracoEntity[0] + : Services.EntityService.GetAll(UmbracoObjectTypes.DocumentType, contentTypeIds).ToArray(); + + nodes.AddRange(docTypeEntities + .Select(entity => + { + var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-item-arrangement", true); + treeNode.Path = string.Format("-1,{0}", entity.Id); + treeNode.NodeType = "document-type-blueprints"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return treeNode; + })); + + return nodes; + } + else + { + var intId = id.TryConvertTo(); + //Get the content type + var ct = Services.ContentTypeService.GetContentType(intId.Result); + if (ct == null) return nodes; + + var blueprintsForDocType = entities.Where(x => ct.Alias == ((UmbracoEntity) x).ContentTypeAlias); + nodes.AddRange(blueprintsForDocType + .Select(entity => + { + var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-blueprint", false); + treeNode.Path = string.Format("-1,{0},{1}", ct.Id, entity.Id); + return treeNode; + })); + } + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // root actions + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + return menu; + } + var cte = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentType); + //only refresh & create if it's a content type + if (cte != null) + { + var ct = Services.ContentTypeService.GetContentType(cte.Id); + var createItem = menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCreateBlueprintFromContent.Instance.Alias))); + createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias); + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + + return menu; + } + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + + return menu; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 5330fcf9b1..e810d723ee 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -11,7 +11,10 @@ using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; +using umbraco.businesslogic.Actions; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -28,13 +31,17 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController("UmbracoTrees")] [CoreTree] - public class ContentTreeController : ContentTreeControllerBase + [SearchableTree("searchResultFormatter", "configureContentResult")] + public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var node = base.CreateRootNode(queryStrings); - //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartContentId != Constants.System.Root) + + // if the user's start node is not default, then ensure the root doesn't have a menu + if (UserStartNodes.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -46,7 +53,9 @@ namespace Umbraco.Web.Trees protected override bool RecycleBinSmells => Services.ContentService.RecycleBinSmells(); - protected override int UserStartNode => Security.CurrentUser.StartContentId; + private int[] _userStartNodes; + protected override int[] UserStartNodes + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -57,7 +66,7 @@ namespace Umbraco.Web.Trees /// protected override TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) { - var entity = (UmbracoEntity) e; + var entity = (UmbracoEntity)e; var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); if (CanUserAccessNode(e, allowedUserOptions)) @@ -102,9 +111,9 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { var menu = new MenuItemCollection(); - - //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartContentId != Constants.System.Root) + + // if the user's start node is not the root then ensure the root menu is empty/doesn't exist + if (UserStartNodes.Contains(Constants.System.Root) == false) { return menu; } @@ -155,7 +164,7 @@ namespace Umbraco.Web.Trees FilterUserAllowedMenuItems(nodeMenu, allowedMenuItems); //if the media item is in the recycle bin, don't have a default menu, just show the regular menu - if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) + if (item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { nodeMenu.DefaultMenuAlias = null; nodeMenu.Items.Insert(2, new MenuItem(ActionRestore.Instance, Services.TextService.Localize("actions", ActionRestore.Instance.Alias))); @@ -191,7 +200,7 @@ namespace Umbraco.Web.Trees if (content == null) return false; - return Security.CurrentUser.HasPathAccess(content); + return Security.CurrentUser.HasPathAccess(content, Services.EntityService); } /// @@ -205,6 +214,8 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu); AddActionNode(item, menu); + AddActionNode(item, menu); + //need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized. AddActionNode(item, menu, true); AddActionNode(item, menu); @@ -242,5 +253,10 @@ namespace Umbraco.Web.Trees var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Document, pageSize, pageIndex, out totalFound, searchFrom); + } } } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index f198f481fa..a0c22893b3 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Trees /// /// Returns the user's start node for this tree /// - protected abstract int UserStartNode { get; } + protected abstract int[] UserStartNodes { get; } /// /// Gets the tree nodes for the given id @@ -104,7 +104,7 @@ namespace Umbraco.Web.Trees // Therefore, in the latter case, we want to change the id to -1 since we want to render the current user's root node // and the GetChildEntities method will take care of rendering the correct root node. // If it is in dialog mode, then we don't need to change anything and the children will just render as per normal. - if (IsDialog(queryStrings) == false && UserStartNode != Constants.System.Root) + if (IsDialog(queryStrings) == false && UserStartNodes.Contains(Constants.System.Root) == false) { id = Constants.System.Root.ToString(CultureInfo.InvariantCulture); } @@ -132,16 +132,13 @@ namespace Umbraco.Web.Trees iid = idEntity.Id; } - //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start node data - if (iid == Constants.System.Root && UserStartNode != Constants.System.Root) + // if a request is made for the root node but user has no access to + // root node, return start nodes instead + if (iid == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false) { - //just return their single start node, it will show up under the 'Content' label - var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType); - if (startNode == null) - throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found"); - - return new[] { startNode }; + return UserStartNodes.Length > 0 + ? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes) + : Enumerable.Empty(); } return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray(); @@ -167,7 +164,7 @@ namespace Umbraco.Web.Trees protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { //check if we're rendering the root - if (id == Constants.System.Root.ToInvariantString() && UserStartNode == Constants.System.Root) + if (id == Constants.System.Root.ToInvariantString() && UserStartNodes.Contains(Constants.System.Root)) { var altStartId = string.Empty; diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 08624f0412..5252f36c1a 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -1,13 +1,15 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; -using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Trees { @@ -15,7 +17,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] - public class ContentTypeTreeController : TreeController + public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { @@ -135,5 +137,11 @@ namespace Umbraco.Web.Trees return menu; } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, filter: query); + return Mapper.Map>(results); + } } } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 771722778f..9d3f337152 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Search; using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; @@ -17,7 +20,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] - public class DataTypeTreeController : TreeController + public class DataTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { @@ -118,5 +121,11 @@ namespace Umbraco.Web.Trees return menu; } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, filter: query); + return Mapper.Map>(results); + } } } diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index 4cff6b69bc..ca19fe270e 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -106,7 +106,7 @@ namespace Umbraco.Web.Trees } var path = string.IsNullOrEmpty(id) == false && id != Constants.System.Root.ToInvariantString() - ? System.Web.HttpUtility.UrlDecode(id).TrimStart("/") + ? HttpUtility.UrlDecode(id).TrimStart("/") : ""; var isFile = FileSystem.FileExists(path); var isDirectory = FileSystem.DirectoryExists(path); diff --git a/src/Umbraco.Web/Trees/ISearchableTree.cs b/src/Umbraco.Web/Trees/ISearchableTree.cs deleted file mode 100644 index 93917c8270..0000000000 --- a/src/Umbraco.Web/Trees/ISearchableTree.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Trees -{ - public interface ISearchableTree - { - /// - /// The alias of the tree that the belongs to - /// - string TreeAlias { get; } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null); - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 7a06ac88c0..a055975fbd 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -225,11 +225,7 @@ namespace Umbraco.Web.Trees new LegacyUrlAction( "dialogs/sort.aspx?id=" + nodeId + "&nodeType=" + nodeType + "&app=" + currentSection + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/sort"))); - case "UmbClientMgr.appActions().actionRights()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/cruds.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/rights"))); + case "UmbClientMgr.appActions().actionProtect()": return Attempt.Succeed( new LegacyUrlAction( @@ -275,11 +271,7 @@ namespace Umbraco.Web.Trees new LegacyUrlAction( "dialogs/sendToTranslation.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/sendToTranslate"))); - case "UmbClientMgr.appActions().actionEmptyTranscan()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/emptyTrashcan.aspx?type=" + currentSection, - Current.Services.TextService.Localize("actions/emptyTrashcan"))); + case "UmbClientMgr.appActions().actionImport()": return Attempt.Succeed( new LegacyUrlAction( @@ -295,16 +287,7 @@ namespace Umbraco.Web.Trees new LegacyUrlAction( "dialogs/viewAuditTrail.aspx?nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/auditTrail"))); - case "UmbClientMgr.appActions().actionMove()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/moveOrCopy.aspx?app=" + currentSection + "&mode=cut&id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/move"))); - case "UmbClientMgr.appActions().actionCopy()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/moveOrCopy.aspx?app=" + currentSection + "&mode=copy&id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/copy"))); + } return Attempt.Fail(); } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 229bf29e17..9c77122613 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http.Formatting; @@ -11,6 +12,8 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -26,13 +29,17 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController("UmbracoTrees")] [CoreTree] - public class MediaTreeController : ContentTreeControllerBase + [SearchableTree("searchResultFormatter", "configureMediaResult")] + public class MediaTreeController : ContentTreeControllerBase, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var node = base.CreateRootNode(queryStrings); - //if the user's start node is not default, then ensure the root doesn't have a menu - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + + // if the user's start node is not default, then ensure the root doesn't have a menu + if (UserStartNodes.Contains(Constants.System.Root) == false) { node.MenuUrl = ""; } @@ -44,7 +51,9 @@ namespace Umbraco.Web.Trees protected override bool RecycleBinSmells => Services.MediaService.RecycleBinSmells(); - protected override int UserStartNode => Security.CurrentUser.StartMediaId; + private int[] _userStartNodes; + protected override int[] UserStartNodes + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -88,7 +97,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { //if the user's start node is not the root then ensure the root menu is empty/doesn't exist - if (Security.CurrentUser.StartMediaId != Constants.System.Root) + if (UserStartNodes.Contains(Constants.System.Root) == false) { return menu; } @@ -144,7 +153,12 @@ namespace Umbraco.Web.Trees if (media == null) return false; - return Security.CurrentUser.HasPathAccess(media); + return Security.CurrentUser.HasPathAccess(media, Services.EntityService); + } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } } } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index d58c3484b0..086f3437b9 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -1,13 +1,17 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Search; namespace Umbraco.Web.Trees { @@ -15,7 +19,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:8)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] - public class MediaTypeTreeController : TreeController + public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { @@ -48,7 +52,7 @@ namespace Umbraco.Web.Trees // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where childs have already been created. var hasChildren = dt.HasChildren(); - var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaTypeGuid, id, queryStrings, "icon-item-arrangement", hasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaTypeGuid, id, queryStrings, "icon-thumbnails", hasChildren); node.Path = dt.Path; return node; @@ -122,5 +126,11 @@ namespace Umbraco.Web.Trees return menu; } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, filter: query); + return Mapper.Map>(results); + } } } diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index 67c2023865..d724e07e56 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -13,6 +14,8 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -26,7 +29,8 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Members, Constants.Trees.Members, null, sortOrder: 0)] [PluginController("UmbracoTrees")] [CoreTree] - public class MemberTreeController : TreeController + [SearchableTree("searchResultFormatter", "configureMemberResult")] + public class MemberTreeController : TreeController, ISearchableTree { public MemberTreeController() { @@ -34,6 +38,7 @@ namespace Umbraco.Web.Trees _isUmbracoProvider = _provider.IsUmbracoMembershipProvider(); } + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); private readonly MembershipProvider _provider; private readonly bool _isUmbracoProvider; @@ -174,5 +179,10 @@ namespace Umbraco.Web.Trees return menu; } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Member, pageSize, pageIndex, out totalFound, searchFrom); + } } } diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 1f785b2382..db97d5c3a4 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -1,19 +1,24 @@ using Umbraco.Core.IO; -using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { /// /// Tree for displaying partial view macros in the developer app /// - [Tree(Constants.Applications.Developer, Constants.Trees.PartialViewMacros, "Partial View Macro Files", sortOrder: 6)] + [Tree(Constants.Applications.Developer, Constants.Trees.PartialViewMacros, null, sortOrder: 6)] + [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] + [PluginController("UmbracoTrees")] + [CoreTree] public class PartialViewMacrosTreeController : PartialViewsTreeController { protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem; - private static readonly string[] ExtensionsStatic = { "cshtml" }; + private static readonly string[] ExtensionsStatic = {"cshtml"}; protected override string[] Extensions => ExtensionsStatic; diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index 965537fccd..2aa52a563e 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -1,19 +1,25 @@ -using Umbraco.Core; +using umbraco; using Umbraco.Core.IO; using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { /// /// Tree for displaying partial views in the settings app /// - [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, "Partial Views", sortOrder: 2)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 2)] + [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] + [PluginController("UmbracoTrees")] + [CoreTree] public class PartialViewsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; - private static readonly string[] ExtensionsStatic = { "cshtml" }; + private static readonly string[] ExtensionsStatic = {"cshtml"}; protected override string[] Extensions => ExtensionsStatic; diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index a109d81512..b096591f2f 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -2,11 +2,14 @@ using System; using System.Globalization; using System.Linq; using System.Net.Http.Formatting; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; +using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; @@ -17,7 +20,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] - public class TemplatesTreeController : TreeController + public class TemplatesTreeController : TreeController, ISearchableTree { /// /// The method called to render the contents of the tree structure @@ -121,5 +124,11 @@ namespace Umbraco.Web.Trees Uri.EscapeDataString("settings/editTemplate.aspx?templateID=" + template.Id) : null; } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, filter: query); + return Mapper.Map>(results); + } } } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index b358154a0e..ab5717235a 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -21,22 +21,7 @@ namespace Umbraco.Web.Trees /// public override string RootNodeDisplayName { - get - { - - //if title is defined, return that - if(string.IsNullOrEmpty(_attribute.Title) == false) - return _attribute.Title; - - - //try to look up a tree header matching the tree alias - var localizedLabel = Services.TextService.Localize("treeHeaders/" + _attribute.Alias); - if (string.IsNullOrEmpty(localizedLabel) == false) - return localizedLabel; - - //is returned to signal that a label was not found - return "[" + _attribute.Alias + "]"; - } + get { return _attribute.GetRootNodeDisplayName(Services.TextService); } } /// @@ -49,19 +34,7 @@ namespace Umbraco.Web.Trees private void Initialize() { - //Locate the tree attribute - var treeAttributes = GetType() - .GetCustomAttributes(typeof(TreeAttribute), false) - .OfType() - .ToArray(); - - if (treeAttributes.Any() == false) - { - throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute"); - } - - //assign the properties of this object to those of the metadata attribute - _attribute = treeAttributes.First(); + _attribute = GetType().GetTreeAttribute(); } } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index ff67dce4ed..1cc3e32f5e 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Search; namespace Umbraco.Web.Trees { diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs new file mode 100644 index 0000000000..8b70be9c95 --- /dev/null +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -0,0 +1,59 @@ +using System.Net.Http.Formatting; +using umbraco; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Users)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0)] + [PluginController("UmbracoTrees")] + [LegacyBaseTree(typeof(loadUsers))] + [CoreTree] + public class UserTreeController : TreeController + { + public UserTreeController() + { + } + + public UserTreeController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public UserTreeController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) + { + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Users, Constants.Trees.Users, "overview"); + root.Icon = "icon-users"; + + root.HasChildren = false; + return root; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var baseUrl = Constants.Applications.Users + "/users/"; + + var nodes = new TreeNodeCollection(); + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 23cdb32dd7..43ae28f4ab 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -123,14 +123,22 @@ + + + + + + + + @@ -156,7 +164,6 @@ - @@ -198,14 +205,18 @@ + + + + @@ -218,13 +229,19 @@ + + + + + + - - - - + + + + @@ -381,7 +398,7 @@ - + @@ -469,8 +486,6 @@ - - @@ -486,7 +501,6 @@ - @@ -698,9 +712,7 @@ ASPXCodeBehind - - ASPXCodeBehind - + ASPXCodeBehind @@ -711,9 +723,7 @@ ASPXCodeBehind - - ASPXCodeBehind - + @@ -887,9 +897,7 @@ - - ASPXCodeBehind - + ASPXCodeBehind @@ -909,9 +917,7 @@ AssignDomain2.aspx - - ASPXCodeBehind - + ASPXCodeBehind @@ -1115,9 +1121,7 @@ ASPXCodeBehind - - ASPXCodeBehind - + ASPXCodeBehind @@ -1338,7 +1342,6 @@ autoDoc.aspx - ASPXCodeBehind autoDoc.aspx @@ -1370,7 +1373,6 @@ about.aspx - ASPXCodeBehind about.aspx @@ -1433,7 +1435,6 @@ editScript.aspx - ASPXCodeBehind Code @@ -1493,28 +1494,24 @@ EditUserType.aspx - ASPXCodeBehind EditUserType.aspx NodePermissions.ascx - ASPXCodeBehind NodePermissions.ascx PermissionEditor.aspx - ASPXCodeBehind PermissionEditor.aspx PermissionsHandler.asmx - Component @@ -1533,7 +1530,6 @@ Developer.asmx - Component legacyAjaxCalls.asmx @@ -1545,7 +1541,6 @@ templates.asmx - Component ASPXCodeBehind diff --git a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs index 053887cafc..dfa3622795 100644 --- a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs +++ b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs @@ -21,6 +21,8 @@ namespace Umbraco.Web.WebApi public class AngularJsonMediaTypeFormatter : JsonMediaTypeFormatter { + public const string XsrfPrefix = ")]}',\n"; + /// /// This will prepend the special chars to the stream output that angular will strip /// @@ -30,24 +32,25 @@ namespace Umbraco.Web.WebApi /// /// /// - public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) + public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { - if (type == null) throw new ArgumentNullException("type"); if (writeStream == null) throw new ArgumentNullException("writeStream"); var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers); - using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding)) + using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding, + //we are only writing a few chars so we don't need to allocate a large buffer + 128, + //this is important! We don't want to close the stream, the base class is in charge of stream management, we just want to write to it. + leaveOpen:true)) { //write the special encoding for angular json to the start // (see: http://docs.angularjs.org/api/ng.$http) - streamWriter.Write(")]}',\n"); + streamWriter.Write(XsrfPrefix); streamWriter.Flush(); - return base.WriteToStreamAsync(type, value, writeStream, content, transportContext); - } - - + await base.WriteToStreamAsync(type, value, writeStream, content, transportContext); + } } } diff --git a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs new file mode 100644 index 0000000000..77fee75693 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs @@ -0,0 +1,76 @@ +using System; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Appends a custom response header to notify the UI that the current user data has been modified + /// + public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute + { + private readonly string _userIdParameter; + + /// + /// An empty constructor which will always set the header + /// + public AppendUserModifiedHeaderAttribute() + { + } + + /// + /// A constructor specifying the action parameter name containing the user id to match against the current user and if they match the header will be appended + /// + /// + public AppendUserModifiedHeaderAttribute(string userIdParameter) + { + if (userIdParameter == null) throw new ArgumentNullException("userIdParameter"); + _userIdParameter = userIdParameter; + } + + public static void AppendHeader(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext.Response.Headers.Contains("X-Umb-User-Modified") == false) + { + actionExecutedContext.Response.Headers.Add("X-Umb-User-Modified", "1"); + } + } + + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + base.OnActionExecuted(actionExecutedContext); + + if (_userIdParameter.IsNullOrWhiteSpace()) + { + AppendHeader(actionExecutedContext); + } + else + { + var actionContext = actionExecutedContext.ActionContext; + if (actionContext.ActionArguments[_userIdParameter] == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _userIdParameter); + } + + var user = UmbracoContext.Current.Security.CurrentUser; + if (user == null) return; + + var userId = GetUserIdFromParameter(actionContext.ActionArguments[_userIdParameter]); + + if (userId == user.Id) + AppendHeader(actionExecutedContext); + } + + } + + private int GetUserIdFromParameter(object parameterValue) + { + if (parameterValue is int) + { + return (int)parameterValue; + } + throw new InvalidOperationException("The id type: " + parameterValue.GetType() + " is not a supported id"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs new file mode 100644 index 0000000000..46c36e9acd --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Web.Security; +using UserExtensions = Umbraco.Core.Models.UserExtensions; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// This filter will check if the current Principal/Identity assigned to the request has stale data in it compared + /// to what is persisted for the current user and will update the current auth ticket with the correct data if required and output + /// a custom response header for the UI to be notified of it. + /// + public sealed class CheckIfUserTicketDataIsStaleAttribute : ActionFilterAttribute + { + public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) + { + await CheckStaleData(actionContext); + } + + public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) + { + await CheckStaleData(actionExecutedContext.ActionContext); + + //we need new tokens and append the custom header if changes have been made + if (actionExecutedContext.ActionContext.Request.Properties.ContainsKey(typeof(CheckIfUserTicketDataIsStaleAttribute).Name)) + { + var tokenFilter = new SetAngularAntiForgeryTokensAttribute(); + tokenFilter.OnActionExecuted(actionExecutedContext); + + //add the header + AppendUserModifiedHeaderAttribute.AppendHeader(actionExecutedContext); + } + } + + private async Task CheckStaleData(HttpActionContext actionContext) + { + if (actionContext == null + || actionContext.Request == null + || actionContext.RequestContext == null + || actionContext.RequestContext.Principal == null + || actionContext.RequestContext.Principal.Identity == null) + { + return; + } + + //don't execute if it's already been done + if (actionContext.Request.Properties.ContainsKey(typeof(CheckIfUserTicketDataIsStaleAttribute).Name)) + return; + + var identity = actionContext.RequestContext.Principal.Identity as UmbracoBackOfficeIdentity; + if (identity == null) return; + + var userId = identity.Id.TryConvertTo(); + if (userId == false) return; + + var user = ApplicationContext.Current.Services.UserService.GetUserById(userId.Result); + if (user == null) return; + + //a list of checks to execute, if any of them pass then we resync + var checks = new Func[] + { + () => user.Username != identity.Username, + () => + { + var culture = UserExtensions.GetUserCulture(user, ApplicationContext.Current.Services.TextService); + return culture != null && culture.ToString() != identity.Culture; + }, + () => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false, + () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false, + () => + { + var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, ApplicationContext.Current.Services.EntityService); + return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false; + }, + () => + { + var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, ApplicationContext.Current.Services.EntityService); + return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false; + } + }; + + if (checks.Any(check => check())) + { + await ReSync(user, actionContext); + } + } + + /// + /// This will update the current request IPrincipal to be correct and re-create the auth ticket + /// + /// + /// + /// + private async Task ReSync(IUser user, HttpActionContext actionContext) + { + var owinCtx = actionContext.Request.TryGetOwinContext(); + if (owinCtx) + { + var signInManager = owinCtx.Result.GetBackOfficeSignInManager(); + + //ensure the remainder of the request has the correct principal set + actionContext.Request.SetPrincipalForRequest(user); + + var backOfficeIdentityUser = Mapper.Map(user); + await signInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true, rememberBrowser: false); + + //flag that we've made changes + actionContext.Request.Properties[typeof(CheckIfUserTicketDataIsStaleAttribute).Name] = true; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs index f7d2b4f929..ef230bea49 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs @@ -11,6 +11,6 @@ namespace Umbraco.Web.WebApi.Filters [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] public sealed class EnableOverrideAuthorizationAttribute : Attribute { - + //TODO: Can we remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead? } } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index ec4c4d0f08..7a9d2c8775 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web.WebApi.Filters _paramName = paramName; _permissionToCheck = ActionBrowse.Instance.Letter; } + public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) : this(paramName) { @@ -90,7 +91,9 @@ namespace Umbraco.Web.WebApi.Filters actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, Current.Services.UserService, - Current.Services.ContentService, nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) + Current.Services.ContentService, + Current.Services.EntityService, + nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index d79167eb53..2e3c075f50 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -119,7 +119,9 @@ namespace Umbraco.Web.WebApi.Filters if (MediaController.CheckPermissions( actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, - Current.Services.MediaService, nodeId)) + Current.Services.MediaService, + Current.Services.EntityService, + nodeId)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 500b28eb67..9c083a9c09 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core; using Umbraco.Web.Composing; +using Umbraco.Core.Models; using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.WebApi.Filters @@ -18,36 +19,70 @@ namespace Umbraco.Web.WebApi.Filters /// internal sealed class FilterAllowedOutgoingContentAttribute : FilterAllowedOutgoingMediaAttribute { + private readonly IUserService _userService; + private readonly IEntityService _entityService; private readonly char _permissionToCheck; - public FilterAllowedOutgoingContentAttribute(Type outgoingType) - : base(outgoingType) + public FilterAllowedOutgoingContentAttribute(Type outgoingType) + : this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) { _permissionToCheck = ActionBrowse.Instance.Letter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck) - : base(outgoingType) + : this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) { _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName) - : base(outgoingType, propertyName) + : this(outgoingType, propertyName, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) { _permissionToCheck = ActionBrowse.Instance.Letter; } + + public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService) + : base(outgoingType) + { + if (userService == null) throw new ArgumentNullException("userService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + _userService = userService; + _entityService = entityService; + _permissionToCheck = ActionBrowse.Instance.Letter; + } + + public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService) + : base(outgoingType) + { + if (userService == null) throw new ArgumentNullException("userService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + _userService = userService; + _entityService = entityService; + _permissionToCheck = permissionToCheck; + } + + public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService) + : base(outgoingType, propertyName) + { + if (userService == null) throw new ArgumentNullException("userService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + _userService = userService; + _entityService = entityService; + _permissionToCheck = ActionBrowse.Instance.Letter; + } + + protected override void FilterItems(IUser user, IList items) { base.FilterItems(user, items); - FilterBasedOnPermissions(items, user, Current.Services.UserService); + FilterBasedOnPermissions(items, user); } - protected override int GetUserStartNode(IUser user) + protected override int[] GetUserStartNodes(IUser user) { - return user.StartContentId; + return user.CalculateContentStartNodeIds(_entityService); } protected override int RecycleBinId @@ -55,7 +90,7 @@ namespace Umbraco.Web.WebApi.Filters get { return Constants.System.RecycleBinContent; } } - internal void FilterBasedOnPermissions(IList items, IUser user, IUserService userService) + internal void FilterBasedOnPermissions(IList items, IUser user) { var length = items.Count; @@ -67,14 +102,14 @@ namespace Umbraco.Web.WebApi.Filters ids.Add(((dynamic)items[i]).Id); } //get all the permissions for these nodes in one call - var permissions = userService.GetPermissions(user, ids.ToArray()).ToArray(); + var permissions = _userService.GetPermissions(user, ids.ToArray()).ToArray(); var toRemove = new List(); foreach (dynamic item in items) { var nodePermission = permissions.Where(x => x.EntityId == Convert.ToInt32(item.Id)).ToArray(); //if there are no permissions for this id then we need to check what the user's default // permissions are. - if (nodePermission.Any() == false) + if (nodePermission.Length == 0) { //var defaultP = user.DefaultPermissions diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 3c2e108ba1..c404617188 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -40,9 +40,9 @@ namespace Umbraco.Web.WebApi.Filters get { return true; } } - protected virtual int GetUserStartNode(IUser user) + protected virtual int[] GetUserStartNodes(IUser user) { - return user.StartMediaId; + return user.CalculateMediaStartNodeIds(ApplicationContext.Current.Services.EntityService); } protected virtual int RecycleBinId @@ -86,8 +86,8 @@ namespace Umbraco.Web.WebApi.Filters var toRemove = new List(); foreach (dynamic item in items) { - var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNode(user), RecycleBinId)); - if (!hasPathAccess) + var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNodes(user), RecycleBinId)); + if (hasPathAccess == false) { toRemove.Add(item); } diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs index fc691e736a..5dbd36d630 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -35,9 +35,11 @@ namespace Umbraco.Web.WebApi.Filters return true; } - return Current.UmbracoContext.Security.CurrentUser != null + var authorized = Current.UmbracoContext.Security.CurrentUser != null && _appNames.Any(app => Current.UmbracoContext.Security.UserHasAppAccess( app, Current.UmbracoContext.Security.CurrentUser)); + + return authorized; } } } diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index dabad3bca5..876e968bf9 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -28,9 +28,17 @@ namespace Umbraco.Web.WebApi internal static Attempt TryGetOwinContext(this HttpRequestMessage request) { var httpContext = request.TryGetHttpContext(); - return httpContext - ? Attempt.Succeed(httpContext.Result.GetOwinContext()) - : Attempt.Fail(); + try + { + return httpContext + ? Attempt.Succeed(httpContext.Result.GetOwinContext()) + : Attempt.Fail(); + } + catch (InvalidOperationException) + { + //this will occur if there is no OWIN environment which generally would only be in things like unit tests + return Attempt.Fail(); + } } /// diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs index c18c89b7e8..a183314086 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs @@ -2,6 +2,7 @@ using System.Web.Http; using Umbraco.Core; using Umbraco.Web.Composing; +using Umbraco.Web.Security; namespace Umbraco.Web.WebApi { @@ -11,6 +12,8 @@ namespace Umbraco.Web.WebApi /// public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute { + private readonly bool _requireApproval; + /// /// Can be used by unit tests to enable/disable this filter /// @@ -37,8 +40,13 @@ namespace Umbraco.Web.WebApi _runtimeState = runtimeState; } - public UmbracoAuthorizeAttribute() + public UmbracoAuthorizeAttribute() : this(true) { } + + public UmbracoAuthorizeAttribute(bool requireApproval) + { + _requireApproval = requireApproval; + } protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext) { @@ -53,7 +61,7 @@ namespace Umbraco.Web.WebApi // otherwise we need to ensure that a user is logged in return RuntimeState.Level == RuntimeLevel.Install || RuntimeState.Level == RuntimeLevel.Upgrade - || UmbracoContext.Security.ValidateCurrentUser(); + || UmbracoContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success; } catch (Exception) { diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 03c69640ea..52830689bd 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -1,4 +1,6 @@ using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; namespace Umbraco.Web.WebApi { @@ -14,6 +16,53 @@ namespace Umbraco.Web.WebApi [UmbracoAuthorize] [DisableBrowserCache] [UmbracoWebApiRequireHttps] + [CheckIfUserTicketDataIsStale] public abstract class UmbracoAuthorizedApiController : UmbracoApiController - { } + { + private BackOfficeUserManager _userManager; + private bool _userisValidated = false; + + protected UmbracoAuthorizedApiController() + { } + + protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext) + : base(umbracoContext) + { } + + protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) + : base(umbracoContext, umbracoHelper) + { } + + protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager backOfficeUserManager) + : base(umbracoContext, umbracoHelper) + { + _userManager = backOfficeUserManager; + } + + protected BackOfficeUserManager UserManager + { + get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } + } + + /// + /// Returns the currently logged in Umbraco User + /// + /* + [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object, or Security.GetUserId() if you want to just get the user id")] + protected User UmbracoUser + { + get + { + //throw exceptions if not valid (true) + if (!_userisValidated) + { + Security.ValidateCurrentUser(true); + _userisValidated = true; + } + + return new User(Security.CurrentUser); + } + } + */ // fixme v8 remove this code + } } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 938193fbc8..e768d70238 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; @@ -403,12 +404,25 @@ namespace umbraco /// Returns an MD5 hash of the string specified /// /// The text to create a hash from - /// Md5 has of the string + /// Md5 hash of the string + [Obsolete("Please use the CreateHash method instead. This may be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public static string md5(string text) { return text.ToMd5(); } + /// + /// Generates a hash based on the text string passed in. This method will detect the + /// security requirements (is FIPS enabled) and return an appropriate hash. + /// + /// The text to create a hash from + /// hash of the string + public static string CreateHash(string text) + { + return text.GenerateHash(); + } + /// /// Compare two dates /// @@ -1077,15 +1091,22 @@ namespace umbraco /// An XpathNodeIterator containing the current page as Xml. public static XPathNodeIterator GetXmlNodeCurrent() { + var pageId = ""; + try { var nav = Umbraco.Web.UmbracoContext.Current.ContentCache.CreateNavigator(); - nav.MoveToId(HttpContext.Current.Items["pageID"].ToString()); + var pageId = HttpContext.Current.Items["pageID"]?.ToString(); + + if (pageId == null) + throw new NullReferenceException("pageID not found in the current HTTP context"); + + nav.MoveToId(pageId); return nav.Select("."); } catch (Exception ex) { - Current.Logger.Error("Could not retrieve current xml node", ex); + Current.Logger.Error($"Could not retrieve current xml node for page Id {pageId}.", ex); } XmlDocument xd = new XmlDocument(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/passwordChanger.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/passwordChanger.ascx.cs deleted file mode 100644 index e409629767..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/passwordChanger.ascx.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Configuration.Provider; -using System.Data; -using System.Configuration; -using System.Collections; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Core; -using Umbraco.Core.Security; -using Umbraco.Web.Models; -using Umbraco.Web.UI.Controls; - -namespace umbraco.controls -{ - public partial class passwordChanger : UmbracoUserControl - { - public string MembershipProviderName { get; set; } - - protected MembershipProvider Provider - { - get { return Membership.Providers[MembershipProviderName]; } - } - - private bool? _showOldPassword; - - /// - /// Determines whether to show the old password field or not - /// - internal protected bool ShowOldPassword - { - get - { - if (_showOldPassword.HasValue == false) - { - var umbProvider = Provider as MembershipProviderBase; - if (umbProvider != null && umbProvider.AllowManuallyChangingPassword) - { - _showOldPassword = false; - } - else - { - _showOldPassword = Provider.EnablePasswordRetrieval == false; - } - } - return _showOldPassword.Value; - } - internal set { _showOldPassword = value; } - } - - public bool IsChangingPassword - { - get - { - var convertAttempt = IsChangingPasswordField.Value.TryConvertTo(); - return convertAttempt.Success && convertAttempt.Result; - } - } - - private readonly ChangingPasswordModel _model = new ChangingPasswordModel(); - - public ChangingPasswordModel ChangingPasswordModel - { - get - { - _model.NewPassword = umbPasswordChanger_passwordNew.Text; - _model.OldPassword = umbPasswordChanger_passwordCurrent.Text; - _model.Reset = ResetPasswordCheckBox.Checked; - return _model; - } - } - - [Obsolete("Use the ChangingPasswordModel instead")] - public string Password - { - get { return ChangingPasswordModel.NewPassword; } - } - - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - if (Membership.Providers[MembershipProviderName] == null) - { - throw new ProviderException("The membership provider " + MembershipProviderName + " was not found"); - } - - //TODO: WE need to support this! - requires UI updates, etc... - if (Provider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); - } - } - - /// - /// umbPasswordChanger_passwordNew control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox umbPasswordChanger_passwordNew; - - /// - /// umbPasswordChanger_passwordNewConfirm control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox umbPasswordChanger_passwordNewConfirm; - - /// - /// CompareValidator1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CompareValidator ConfirmPasswordValidator; - - /// - /// IsChangingPassword control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.HiddenField IsChangingPasswordField; - - /// - /// ResetPasswordCheckBox control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CheckBox ResetPasswordCheckBox; - - /// - /// umbPasswordChanger_passwordCurrent control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox umbPasswordChanger_passwordCurrent; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs deleted file mode 100644 index 4a976b146c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewMacrosTasks.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core; - -namespace umbraco -{ - /// - /// The UI 'tasks' for the create dialog and delete processes - /// - [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] - public class PartialViewMacroTasks : PartialViewTasksBase - { - public override string AssignedApp - { - get { return Constants.Applications.Developer.ToString(); } - } - - protected override bool IsPartialViewMacro - { - get { return true; } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs deleted file mode 100644 index 7142bbab24..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core; - -namespace umbraco -{ - /// - /// The UI 'tasks' for the create dialog and delete processes - /// - [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] - public class PartialViewTasks : PartialViewTasksBase - { - public override string AssignedApp - { - get { return Constants.Applications.Settings.ToString(); } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs deleted file mode 100644 index 07ac27332b..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Web; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.UI; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; - -namespace umbraco -{ - /// - /// The base UI 'tasks' for the create dialog and delete processes - /// - [UmbracoWillObsolete("http://issues.umbraco.org/issue/U4-1373", "This will one day be removed when we overhaul the create process")] - public abstract class PartialViewTasksBase : LegacyDialogTask - { - private string _returnUrl = string.Empty; - - public override string ReturnUrl - { - get { return _returnUrl; } - } - - protected virtual string EditViewFile - { - get { return "Settings/Views/EditView.aspx"; } - } - - protected virtual bool IsPartialViewMacro - { - get { return false; } - } - - public override bool PerformSave() - { - var pipesIndex = Alias.IndexOf("|||", StringComparison.Ordinal); - var snippetName = Alias.Substring(0, pipesIndex).Trim(); - var fileName = Alias.Substring(pipesIndex + 3, Alias.Length - pipesIndex - 3); - if (fileName.ToLowerInvariant().EndsWith(".cshtml") == false) - { - fileName += ".cshtml"; - } - - var model = new PartialView(IsPartialViewMacro ? PartialViewType.PartialViewMacro : PartialViewType.PartialView, fileName); - var fileService = (FileService)Current.Services.FileService; - var macroService = Current.Services.MacroService; - - if (IsPartialViewMacro == false) - { - var attempt = fileService.CreatePartialView(model, snippetName, User.Id); - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViews&file={0}", HttpUtility.UrlEncode(model.Path.TrimStart('/').Replace("\\", "/"))); - return attempt.Success; - } - else - { - - var attempt = fileService.CreatePartialViewMacro(model, /*ParentID == 1,*/ snippetName, User.Id); - // if ParentId = 0 then that means that the "Create macro" checkbox was OFF, so don't try to create an actual macro - // See PartialViewMacro.ascx.cs and PartialView.ascx.cs: SubmitButton_Click - if (attempt && ParentID != 0) - { - //The partial view path to be saved with the macro must be a fully qualified virtual path - var virtualPath = string.Format("{0}/{1}/{2}", SystemDirectories.MvcViews, "MacroPartials", attempt.Result.Path); - macroService.Save(new Macro(attempt.Result.Alias, attempt.Result.Alias) { ScriptPath = virtualPath }); - } - - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViewMacros&file={0}", HttpUtility.UrlEncode(model.Path.TrimStart('/').Replace("\\", "/"))); - return attempt.Success; - } - - } - - public override bool PerformDelete() - { - var fileService = (FileService)Current.Services.FileService; - - if (IsPartialViewMacro == false) - { - if (Alias.Contains(".") == false) - { - //there is no extension so we'll assume it's a folder - fileService.DeletePartialViewFolder(Alias.TrimStart('/')); - return true; - } - return fileService.DeletePartialView(Alias.TrimStart('/'), User.Id); - } - - if (Alias.Contains(".") == false) - { - //there is no extension so we'll assume it's a folder - fileService.DeletePartialViewMacroFolder(Alias.TrimStart('/')); - return true; - } - return fileService.DeletePartialViewMacro(Alias.TrimStart('/'), User.Id); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/ScriptTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/ScriptTasks.cs deleted file mode 100644 index a2a0322369..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/ScriptTasks.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Umbraco.Web.UI; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; - -namespace umbraco -{ - public class ScriptTasks : LegacyDialogTask - { - - public override bool PerformSave() - { - var scriptFileAr = Alias.Split('\u00A4'); - - var fileName = scriptFileAr[0]; - var fileType = scriptFileAr[1]; - - var createFolder = ParentID; - - if (createFolder == 1) - { - Current.Services.FileService.CreateScriptFolder(fileName); - return true; - } - - // remove file extension - if (fileName.ToLowerInvariant().EndsWith(fileType.ToLowerInvariant())) - { - fileName = fileName.Substring(0, - fileName.ToLowerInvariant().LastIndexOf(fileType.ToLowerInvariant(), System.StringComparison.Ordinal) - 1); - } - - var scriptPath = fileName + "." + fileType; - var found = Current.Services.FileService.GetScriptByName(scriptPath); - if (found != null) - { - _returnUrl = string.Format("settings/scripts/editScript.aspx?file={0}", scriptPath.TrimStart('/')); - return true; - } - var script = new Script(fileName + "." + fileType); - Current.Services.FileService.SaveScript(script); - _returnUrl = string.Format("settings/scripts/editScript.aspx?file={0}", scriptPath.TrimStart('/')); - return true; - } - - public override bool PerformDelete() - { - if (Alias.Contains(".") == false) - { - //there is no extension so we'll assume it's a folder - Current.Services.FileService.DeleteScriptFolder(Alias.TrimStart('/')); - } - else - { - Current.Services.FileService.DeleteScript(Alias.TrimStart('/'), User.Id); - } - - return true; - } - - private string _returnUrl = ""; - - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return Constants.Applications.Settings; } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/script.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/script.ascx.cs deleted file mode 100644 index 9f55b0e27f..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/script.ascx.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Linq; -using System.Web; -using System.Web.UI.WebControls; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Web.UI; -using Umbraco.Web; -using Umbraco.Web.UI.Controls; -using Umbraco.Web._Legacy.UI; - -namespace umbraco.presentation.umbraco.create -{ - public partial class script : UmbracoUserControl - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - sbmt.Text = Services.TextService.Localize("create"); - - // Enable new item in folders to place items in that folder. - if (Request["nodeType"] == "scriptsFolder") - rename.Text = Request["nodeId"].EnsureEndsWith('/'); - } - - protected void SubmitClick(object sender, System.EventArgs e) - { - int createFolder = 0; - if (scriptType.SelectedValue == "") - { - createFolder = 1; - ContainsValidator.Enabled = true; - Page.Validate(); - } - - if (Page.IsValid) - { - string returnUrl = LegacyDialogHandler.Create( - new HttpContextWrapper(Context), - Security.CurrentUser, - Request.GetItemAsString("nodeType"), - createFolder, - rename.Text + '\u00A4' + scriptType.SelectedValue); - - ClientTools - .ChangeContentFrameUrl(returnUrl) - .ReloadActionNode(false, true) - .CloseModalWindow(); - - } - } - - override protected void OnInit(EventArgs e) - { - base.OnInit(e); - - ContainsValidator.Enabled = false; - - string[] fileTypes = UmbracoConfig.For.UmbracoSettings().Content.ScriptFileTypes.ToArray(); - - scriptType.Items.Add(new ListItem(Services.TextService.Localize("folder"), "")); - scriptType.Items.FindByText(Services.TextService.Localize("folder")).Selected = true; - - foreach (string str in fileTypes) - { - scriptType.Items.Add(new ListItem("." + str + " file", str)); - } - } - - /// - /// rename control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox rename; - - /// - /// RequiredFieldValidator1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidator1; - protected global::System.Web.UI.WebControls.RegularExpressionValidator EndsWithValidator; - protected global::System.Web.UI.WebControls.RegularExpressionValidator ContainsValidator; - - /// - /// scriptType control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.ListBox scriptType; - - /// - /// CreateMacroCheckBox control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CheckBox CreateMacroCheckBox; - - /// - /// sbmt control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button sbmt; - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs deleted file mode 100644 index 29e6f8b2c3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Web.Composing; -using Umbraco.Web.UI; -using Umbraco.Web._Legacy.UI; - -namespace umbraco -{ - public class templateTasks : LegacyDialogTask - { - public override bool PerformSave() - { - var masterId = ParentID; - - var editor = "settings/editTemplate.aspx"; - if (UmbracoConfig.For.UmbracoSettings().Templates.DefaultRenderingEngine == RenderingEngine.Mvc) - editor = "settings/views/editView.aspx"; - - if (masterId > 0) - { - //var id = cms.businesslogic.template.Template.MakeNew(Alias, User, new cms.businesslogic.template.Template(masterId)).Id; - var master = Current.Services.FileService.GetTemplate(masterId); - var t = Current.Services.FileService.CreateTemplateWithIdentity(Alias, null, master, User.Id); - _returnUrl = string.Format("{1}?treeType=templates&templateID={0}", t.Id, editor); - } - else - { - //var id = cms.businesslogic.template.Template.MakeNew(Alias, User).Id; - var t = Current.Services.FileService.CreateTemplateWithIdentity(Alias, null, null, User.Id); - _returnUrl = string.Format("{1}?treeType=templates&templateID={0}", t.Id, editor); - - } - return true; - } - - public override bool PerformDelete() - { - //new cms.businesslogic.template.Template(ParentID).delete(); - var t = Current.Services.FileService.GetTemplate(ParentID); - if (t != null) Current.Services.FileService.DeleteTemplate(t.Alias); - return false; - } - - private string _returnUrl = ""; - - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return Constants.Applications.Settings.ToString(); } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/userTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/userTasks.cs deleted file mode 100644 index 1e4fae9e88..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/userTasks.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Web.Security; -using Umbraco.Core.Logging; -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; -using MembershipProviderExtensions = Umbraco.Core.Security.MembershipProviderExtensions; - -namespace umbraco -{ - public class userTasks : LegacyDialogTask - { - private string _returnUrl = ""; - - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return Constants.Applications.Users.ToString(); } - } - - public override bool PerformSave() - { - // Hot damn HACK > user is allways UserType with id = 1 = administrator ??? - // temp password deleted by NH - //BusinessLogic.User.MakeNew(Alias, Alias, "", BusinessLogic.UserType.GetUserType(1)); - //return true; - - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - - var status = MembershipCreateStatus.ProviderError; - try - { - // Password is auto-generated. They are they required to change the password by editing the user information. - - var password = Membership.GeneratePassword( - provider.MinRequiredPasswordLength, - provider.MinRequiredNonAlphanumericCharacters); - - var parts = Alias.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - { - return false; - } - var login = parts[0]; - var email = parts[1]; - - var u = provider.CreateUser( - login, password, email.Trim().ToLower(), "", "", true, null, out status); - - if (u == null) - { - return false; - } - - _returnUrl = string.Format("users/EditUser.aspx?id={0}", u.ProviderUserKey); - - return status == MembershipCreateStatus.Success; - } - catch (Exception ex) - { - Current.Logger.Error(string.Format("Failed to create the user. Error from provider: {0}", status.ToString()), ex); - return false; - } - } - - public override bool PerformDelete() - { - var u = Current.Services.UserService.GetUserById(ParentID); - u.IsApproved = false; - Current.Services.UserService.Save(u); - return true; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/assemblyBrowser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/assemblyBrowser.aspx.cs index d133caa951..6309b1a5cd 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/assemblyBrowser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/assemblyBrowser.aspx.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.UI.Pages; +using UserControl = System.Web.UI.UserControl; namespace umbraco.developer { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index dacc8d0f18..8477585116 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -88,6 +88,7 @@ namespace umbraco.cms.presentation.developer { macroName.Text = macro.Name; macroAlias.Text = macro.Alias; + macroKey.Text = macro.Key.ToString(); macroXslt.Text = macro.XsltPath; cachePeriod.Text = macro.CacheDuration.ToInvariantString(); macroRenderContent.Checked = macro.DontRender == false; @@ -402,6 +403,15 @@ namespace umbraco.cms.presentation.developer /// protected global::System.Web.UI.WebControls.TextBox macroAlias; + /// + /// macroAlias control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label macroKey; + /// /// Pane1_2 control. /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs index a05a4f1fdf..77b0a15da1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs @@ -125,7 +125,7 @@ namespace umbraco.presentation.developer.packages protected void fetchProtectedPackage(object sender, EventArgs e) { //we auth against the webservice. This key will be used to fetch the protected package. - string memberGuid = _repo.Webservice.authenticate(tb_email.Text, library.md5(tb_password.Text)); + string memberGuid = _repo.Webservice.authenticate(tb_email.Text, library.CreateHash(tb_password.Text)); //if we auth correctly and get a valid key back, we will fetch the file from the repo webservice. if (string.IsNullOrEmpty(memberGuid) == false) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs index 47969c600c..6fdd3b54a3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/RelationTypesWebService.asmx.cs @@ -1,5 +1,6 @@ using System.Web.Services; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Web; using Umbraco.Web.Composing; @@ -23,7 +24,7 @@ namespace umbraco.cms.presentation.developer.RelationTypes { var user = UmbracoContext.Current.Security.CurrentUser; - if (user.UserType.Name == "Administrators") + if (user.IsAdmin()) { var relationService = Current.Services.RelationService; var relationType = relationService.GetRelationTypeById(relationTypeId); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx deleted file mode 100644 index 5fb02ef834..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx +++ /dev/null @@ -1,23 +0,0 @@ -<%@ Page language="c#" Codebehind="autoDoc.aspx.cs" AutoEventWireup="True" Inherits="umbraco.developer.autoDoc" %> - - - - autoDoc - - - - - - - -
- -
- - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.cs deleted file mode 100644 index 2c4534859e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.UI.Pages; - -namespace umbraco.developer -{ - /// - /// Summary description for autoDoc. - /// - public partial class autoDoc : UmbracoEnsuredPage - { - public autoDoc() - { - CurrentApp = Constants.Applications.Developer.ToString(); - } - - protected void Page_Load(object sender, EventArgs e) - { - var sb = new StringBuilder(); - AppendTypes(sb, Services.ContentTypeService.GetAll()); - AppendTypes(sb, Services.MediaTypeService.GetAll()); - AppendTypes(sb, Services.MemberTypeService.GetAll()); - LabelDoc.Text = sb.ToString(); - } - - private void AppendTypes(StringBuilder text, IEnumerable types) - { - foreach (var type in types) - { - text.Append( - "

" + type.Name + "

Id: " + type.Id + ", Alias: " + type.Alias + ")

"); - if (type.PropertyTypes.Any()) - text.Append("

Property Types:

"); - foreach (var pt in type.PropertyTypes) - text.Append( - "

" + pt.Id + ", " + pt.Alias + ", " + pt.Name + "

"); - if (type.PropertyGroups.Count > 0) - text.Append("

Tabs:

"); - foreach (var t in type.PropertyGroups) - text.Append( - "

" + t.Id + ", " + t.Name + "

"); - if (type.AllowedContentTypes.Any()) - text.Append("

Allowed children:

"); - foreach (var child in type.AllowedContentTypes) - { - var contentType = types.First(x => x.Id == child.Id.Value); - text.Append( - "

" + child.Id + ", " + contentType.Name + "

"); - } - - text.Append("
"); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.designer.cs deleted file mode 100644 index e597a9b8a1..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/autoDoc.aspx.designer.cs +++ /dev/null @@ -1,17 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.42 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.developer { - - public partial class autoDoc { - protected System.Web.UI.HtmlControls.HtmlForm Form1; - protected System.Web.UI.WebControls.Label LabelDoc; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx deleted file mode 100644 index 63e967296d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx +++ /dev/null @@ -1,33 +0,0 @@ -<%@ Register Namespace="umbraco" TagPrefix="umb" Assembly="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" Codebehind="about.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.about" %> - - - - -
- - - -

- Umbraco v
-
- Copyright © 2001 - - - Umbraco / Niels Hartvig
- Developed by the Umbraco Core - Team
-
- - Umbraco is licensed under the open source license MIT
-
- Visit umbraco.org - for more information.
-
- Dedicated to Gry, August, Villum and Oliver!
-

-
-
- diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs deleted file mode 100644 index 6e2cfe6633..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Web.UI.Pages; - -namespace umbraco.dialogs -{ - /// - /// Summary description for about. - /// - public partial class about : UmbracoEnsuredPage - { - - protected void Page_Load(object sender, EventArgs e) - { - // Put user code to initialize the page here - thisYear.Text = DateTime.Now.Year.ToString(CultureInfo.InvariantCulture); - version.Text = UmbracoVersion.SemanticVersion.ToSemanticString(); - } - - #region Web Form Designer generated code - override protected void OnInit(EventArgs e) - { - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - InitializeComponent(); - base.OnInit(e); - } - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - - } - #endregion - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.designer.cs deleted file mode 100644 index 9acd219cb3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/about.aspx.designer.cs +++ /dev/null @@ -1,33 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.dialogs { - - - public partial class about { - - /// - /// version control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal version; - - /// - /// thisYear control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal thisYear; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs deleted file mode 100644 index 60155c6747..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Linq; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Web; -using umbraco.cms.businesslogic; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Web.Composing; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.dialogs -{ - /// - /// Summary description for cruds. - /// - public partial class cruds : UmbracoEnsuredPage - { - - public cruds() - { - CurrentApp = Constants.Applications.Content.ToString(); - - } - - private readonly Dictionary _permissions = new Dictionary(); - private IUmbracoEntity _node; - - protected void Page_Load(object sender, EventArgs e) - { - Button1.Text = Services.TextService.Localize("update"); - pane_form.Text = Services.TextService.Localize("actions/SetPermissionsForThePage",_node.Name); - } - - override protected void OnInit(EventArgs e) - { - base.OnInit(e); - - _node = Services.EntityService.Get(Request.GetItemAs("id")); - - var ht = new HtmlTable(); - ht.Attributes.Add("class", "table"); - - var names = new HtmlTableRow(); - - var corner = new HtmlTableCell("th"); - corner.Style.Add("border", "none"); - names.Cells.Add(corner); - - foreach (var a in Current.Actions) - { - if (a.CanBePermissionAssigned == false) continue; - - var permissionRow = new HtmlTableRow(); - var label = new HtmlTableCell - { - InnerText = Services.TextService.Localize("actions", a.Alias) - }; - permissionRow.Cells.Add(label); - _permissions.Add(a.Alias, permissionRow); - } - - ht.Rows.Add(names); - - long totalUsers; - foreach (var u in Services.UserService.GetAll(0, int.MaxValue, out totalUsers)) - { - // Not disabled users and not system account - if (u.IsApproved && u.Id > 0) - { - var hc = new HtmlTableCell("th") - { - InnerText = u.Name - }; - hc.Style.Add("text-align", "center"); - hc.Style.Add("border", "none"); - names.Cells.Add(hc); - - foreach (var a in Current.Actions) - { - var chk = new CheckBox - { - //Each checkbox is named with the user _ permission alias so we can parse - ID = u.Id + "_" + a.Letter - }; - - if (a.CanBePermissionAssigned == false) continue; - - var permission = Services.UserService.GetPermissions(u, _node.Path); - - if (permission.AssignedPermissions.Contains(a.Letter.ToString(), StringComparer.Ordinal)) - { - chk.Checked = true; - } - - var cell = new HtmlTableCell(); - cell.Style.Add("text-align", "center"); - cell.Controls.Add(chk); - - _permissions[a.Alias].Cells.Add(cell); - } - } - } - - //add all collected rows - foreach (var perm in _permissions.Values) - { - ht.Rows.Add(perm); - } - - PlaceHolder1.Controls.Add(ht); - } - - - protected void Button1_Click(object sender, EventArgs e) - { - //get non disabled, non admin users and project to a dictionary, - // the string (value) portion will store the array of chars = their permissions - long totalUsers; - var usersPermissions = Services.UserService.GetAll(0, int.MaxValue, out totalUsers) - .Where(user => user.IsApproved && user.Id > 0) - .ToDictionary(user => user, user => ""); - - //iterate over each row which equals: - // * a certain permission and the user's who will be allowed/denied that permission - foreach (var row in _permissions) - { - //iterate each cell that is not the first cell (which is the permission header cell) - for (var i = 1; i < row.Value.Cells.Count; i++) - { - var currCell = row.Value.Cells[i]; - //there's only one control per cell = the check box - var chk = (CheckBox)currCell.Controls[0]; - //if it's checked then append the permissions - if (chk.Checked) - { - //now we will parse the checkbox ID which is the userId_permissionAlias - var split = chk.ID.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); - //get the reference to the user - var user = usersPermissions.Keys.Single(x => x.Id == int.Parse(split[0])); - //get the char permission - var permAlias = split[1]; - //now append that char permission to the user - usersPermissions[user] += permAlias; - } - } - } - - // Loop through the users and update their permissions - foreach (var user in usersPermissions) - { - //default to "-" for whatever reason (was here before so we'll leave it) - var cruds = "-"; - if (user.Value.IsNullOrWhiteSpace() == false) - { - cruds = user.Value; - } - //BusinessLogic.Permission.UpdateCruds(user.Key, _node, cruds); - Current.Services.UserService.ReplaceUserPermissions( - user.Key.Id, - cruds.ToCharArray(), - _node.Id); - } - - // Update feedback message - //FeedBackMessage.Text = "
" + Services.TextService.Localize("rights") + " " + Services.TextService.Localize("ok") + "
"; - feedback1.type = Umbraco.Web._Legacy.Controls.Feedback.feedbacktype.success; - feedback1.Text = Services.TextService.Localize("rights") + " " + Services.TextService.Localize("ok"); - PlaceHolder1.Visible = false; - panel_buttons.Visible = false; - - - } - - /// - /// pane_form control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_form; - - /// - /// feedback1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback feedback1; - - /// - /// PlaceHolder1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder PlaceHolder1; - - /// - /// panel_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl panel_buttons; - - /// - /// Button1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button Button1; - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs index eecd44bc81..5499a6b406 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs @@ -1,6 +1,7 @@ using Umbraco.Core.Services; using System; using System.Collections; +using System.Globalization; using System.Linq; using System.Web.UI.WebControls; using Umbraco.Core; @@ -52,7 +53,7 @@ namespace umbraco.dialogs { CheckBox c = new CheckBox(); - c.ID = a.Letter.ToString(); + c.ID = a.Letter.ToString(CultureInfo.InvariantCulture); var notifications = Services.NotificationService.GetUserNotifications(Security.CurrentUser, node.Path); if (notifications.Any(x => x.Action == a.Letter.ToString())) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs index 39f65bd468..b24f91d259 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Mail; using System.Web; using System.Linq; @@ -74,7 +75,7 @@ namespace umbraco.presentation.dialogs // Translators long totalUsers; foreach (var u in Services.UserService.GetAll(0, int.MaxValue, out totalUsers)) - if (u.UserType.Alias.ToLower() == "translator" || UserHasTranslatePermission(u, _currentPage)) + if (u.GetGroups().Select(x => x.ToLower()).Contains("translators") || UserHasTranslatePermission(u, _currentPage)) translator.Items.Add(new ListItem(u.Name, u.Id.ToString())); if (translator.Items.Count == 0) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs deleted file mode 100644 index f48d32cdee..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web._Legacy.Controls; -using Umbraco.Web.Composing; -using Umbraco.Web.UI.Pages; - -namespace umbraco.cms.presentation.settings -{ - /// - /// Summary description for editTemplate. - /// - public partial class editTemplate : UmbracoEnsuredPage - { - private Umbraco.Core.Models.ITemplate _template; - public MenuButton SaveButton; - - public editTemplate() - { - CurrentApp = Constants.Applications.Settings.ToString(); - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - ScriptManager.GetCurrent(Page).Services.Add( - new ServiceReference(IOHelper.ResolveUrl(SystemDirectories.WebServices + "/codeEditorSave.asmx"))); - ScriptManager.GetCurrent(Page).Services.Add( - new ServiceReference(IOHelper.ResolveUrl(SystemDirectories.WebServices + "/legacyAjaxCalls.asmx"))); - } - - protected string TemplateTreeSyncPath { get; private set; } - - protected void Page_Load(object sender, EventArgs e) - { - MasterTemplate.Attributes.Add("onchange", "changeMasterPageFile()"); - TemplateTreeSyncPath = "-1,init," + _template.Path.Replace("-1,", ""); - - if (!IsPostBack) - { - MasterTemplate.Items.Add(new ListItem(Services.TextService.Localize("none"), "0")); - foreach (var t in Current.Services.FileService.GetTemplates()) - { - if (t.Id != _template.Id) - { - var text = t.Name; - if (text.StartsWith("#")) - { - var lang = Current.Services.LocalizationService.GetLanguageByIsoCode(System.Threading.Thread.CurrentThread.CurrentCulture.Name); - if (lang != null && Current.Services.LocalizationService.DictionaryItemExists(text.Substring(1))) - { - var di = Current.Services.LocalizationService.GetDictionaryItemByKey(text.Substring(1)); - text = di.GetTranslatedValue(lang.Id); - } - else - { - text = "[" + text + "]"; - } - } - var li = new ListItem(text, t.Id.ToString()); - li.Attributes.Add("id", t.Alias.Replace(" ", "")); - MasterTemplate.Items.Add(li); - } - } - - NameTxt.Text = _template.Name; - AliasTxt.Text = _template.Alias; - editorSource.Text = _template.Content; - - var master = Current.Services.FileService.GetTemplate(_template.MasterTemplateAlias); - if (master != null) - MasterTemplate.SelectedValue = master.Id.ToString(); - - ClientTools - .SetActiveTreeType(Constants.Trees.Templates) - .SyncTree(TemplateTreeSyncPath, false); - - LoadScriptingTemplates(); - LoadMacros(); - } - } - - protected override void OnInit(EventArgs e) - { - _template = Current.Services.FileService.GetTemplate(int.Parse(Request.QueryString["templateID"])); - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - InitializeComponent(); - base.OnInit(e); - Panel1.hasMenu = true; - - var editor = Panel1.NewTabPage(Services.TextService.Localize("template")); - editor.Controls.Add(Pane8); - - var props = Panel1.NewTabPage(Services.TextService.Localize("properties")); - props.Controls.Add(Pane7); - - SaveButton = Panel1.Menu.NewButton(); - SaveButton.Text = Services.TextService.Localize("save"); - SaveButton.ButtonType = MenuButtonType.Primary; - SaveButton.ID = "save"; - SaveButton.CssClass = "client-side"; - - Panel1.Text = Services.TextService.Localize("edittemplate"); - pp_name.Text = Services.TextService.Localize("name"); - pp_alias.Text = Services.TextService.Localize("alias"); - pp_masterTemplate.Text = Services.TextService.Localize("mastertemplate"); - - - // Editing buttons - MenuIconI umbField = editorSource.Menu.NewIcon(); - umbField.ImageURL = SystemDirectories.Umbraco + "/images/editor/insField.gif"; - umbField.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + - editorSource.ClientID + "&tagName=UMBRACOGETDATA", Services.TextService.Localize("template/insertPageField"), 640, 550); - umbField.AltText = Services.TextService.Localize("template/insertPageField"); - - - // TODO: Update icon - MenuIconI umbDictionary = editorSource.Menu.NewIcon(); - umbDictionary.ImageURL = GlobalSettings.Path + "/images/editor/dictionaryItem.gif"; - umbDictionary.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + - editorSource.ClientID + "&tagName=UMBRACOGETDICTIONARY", Services.TextService.Localize("template/insertDictionaryItem"), - 640, 550); - umbDictionary.AltText = "Insert umbraco dictionary item"; - - editorSource.Menu.NewElement("div", "splitButtonMacroPlaceHolder", "sbPlaceHolder", 40); - - if (UmbracoConfig.For.UmbracoSettings().Templates.UseAspNetMasterPages) - { - MenuIconI umbContainer = editorSource.Menu.NewIcon(); - umbContainer.ImageURL = SystemDirectories.Umbraco + "/images/editor/masterpagePlaceHolder.gif"; - umbContainer.AltText = Services.TextService.Localize("template/insertContentAreaPlaceHolder"); - umbContainer.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + - "/dialogs/insertMasterpagePlaceholder.aspx?&id=" + _template.Id, - Services.TextService.Localize("template/insertContentAreaPlaceHolder"), 470, 320); - - MenuIconI umbContent = editorSource.Menu.NewIcon(); - umbContent.ImageURL = SystemDirectories.Umbraco + "/images/editor/masterpageContent.gif"; - umbContent.AltText = Services.TextService.Localize("template/insertContentArea"); - umbContent.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/insertMasterpageContent.aspx?id=" + - _template.Id, Services.TextService.Localize("template/insertContentArea"), 470, 300); - } - - - //Spit button - editorSource.Menu.InsertSplitter(); - editorSource.Menu.NewElement("div", "splitButtonPlaceHolder", "sbPlaceHolder", 40); - - // Help - editorSource.Menu.InsertSplitter(); - - MenuIconI helpIcon = editorSource.Menu.NewIcon(); - helpIcon.OnClickCommand = - ClientTools.Scripts.OpenModalWindow( - IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/settings/modals/showumbracotags.aspx?alias=" + - _template.Alias, Services.TextService.Localize("template/quickGuide"), 600, 580); - helpIcon.ImageURL = SystemDirectories.Umbraco + "/images/editor/help.png"; - helpIcon.AltText = Services.TextService.Localize("template/quickGuide"); - } - - - private void LoadScriptingTemplates() - { - string path = SystemDirectories.Umbraco + "/scripting/templates/cshtml/"; - string abPath = IOHelper.MapPath(path); - - var files = new List>(); - - if (Directory.Exists(abPath)) - { - string extension = ".cshtml"; - - foreach (FileInfo fi in new DirectoryInfo(abPath).GetFiles("*" + extension)) - { - string filename = Path.GetFileName(fi.FullName); - - files.Add(new KeyValuePair( - filename, - filename.Replace(extension, "").SplitPascalCasing().ToFirstUpperInvariant() - )); - } - } - - rpt_codeTemplates.DataSource = files; - rpt_codeTemplates.DataBind(); - } - - private void LoadMacros() - { - var macroRenderings = - Services.MacroService.GetAll() - .Select(x => new TempMacroClass() - { - id = x.Id, - macroAlias = x.Alias, - macroName = x.Name - }); - - rpt_macros.DataSource = macroRenderings; - rpt_macros.DataBind(); - } - - private class TempMacroClass - { - public int id { get; set; } - public string macroAlias { get; set; } - public string macroName { get; set; } - } - - public string DoesMacroHaveSettings(string macroId) - { - int val; - using (var scope = Current.ScopeProvider.CreateScope()) - { - val = scope.Database.ExecuteScalar("select 1 from cmsMacroProperty where macro = @macroId", new { macroId }); - scope.Complete(); - } - - return val == 1 ? "1" : "0"; - } - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - } - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude; - - /// - /// Panel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.TabView Panel1; - - /// - /// Pane7 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane Pane7; - protected global::Umbraco.Web._Legacy.Controls.Pane Pane8; - - /// - /// pp_name control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_name; - - /// - /// NameTxt control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox NameTxt; - - /// - /// pp_alias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_alias; - - /// - /// AliasTxt control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox AliasTxt; - - /// - /// pp_masterTemplate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_masterTemplate; - - /// - /// MasterTemplate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.DropDownList MasterTemplate; - - /// - /// pp_source control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_source; - - /// - /// editorSource control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.CodeArea editorSource; - - /// - /// rpt_codeTemplates control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater rpt_codeTemplates; - - /// - /// rpt_macros control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater rpt_macros; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx deleted file mode 100644 index 628e1a645a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx +++ /dev/null @@ -1,59 +0,0 @@ -<%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="true" - CodeBehind="editScript.aspx.cs" Inherits="umbraco.cms.presentation.settings.scripts.editScript" - ValidateRequest="False" %> - -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs deleted file mode 100644 index 2d616cbe9e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Web.UI; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using umbraco.cms.presentation.Trees; -using Umbraco.Web._Legacy.Controls; -using Umbraco.Web; -using Umbraco.Web.Composing; - -namespace umbraco.cms.presentation.settings.scripts -{ - public partial class editScript : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - public editScript() - { - CurrentApp = Constants.Applications.Settings.ToString(); - - } - protected System.Web.UI.HtmlControls.HtmlForm Form1; - protected Umbraco.Web._Legacy.Controls.TabView Panel1; - protected System.Web.UI.WebControls.TextBox NameTxt; - protected Umbraco.Web._Legacy.Controls.Pane Pane7; - protected Umbraco.Web._Legacy.Controls.Pane Pane8; - - protected System.Web.UI.WebControls.Literal lttPath; - protected System.Web.UI.WebControls.Literal editorJs; - protected Umbraco.Web._Legacy.Controls.CodeArea editorSource; - protected Umbraco.Web._Legacy.Controls.PropertyPanel pp_name; - protected Umbraco.Web._Legacy.Controls.PropertyPanel pp_path; - - protected MenuButton SaveButton; - - private string filename; - protected string ScriptTreeSyncPath { get; private set; } - - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - // get the script, ensure it exists (not null) and validate (because - // the file service ensures that it loads scripts from the proper location - // but does not seem to validate extensions?) - in case of an error, - // throw - that's what we did anyways. - - // also scrapping the code that added .cshtml and .vbhtml extensions, and - // ~/Views directory - we're not using editScript.aspx for views anymore. - - var svce = Current.Services.FileService; - var script = svce.GetScriptByName(filename); - if (script == null) // not found - throw new FileNotFoundException("Could not find file '" + filename + "'."); - - lttPath.Text = "" + script.VirtualPath + ""; - editorSource.Text = script.Content; - ScriptTreeSyncPath = BaseTree.GetTreePathFromFilePath(filename); - - // name derives from filename, clean for xss - NameTxt.Text = filename.CleanForXss('\\', '/'); - - Panel1.Text = Services.TextService.Localize("editscript"); - pp_name.Text = Services.TextService.Localize("name"); - pp_path.Text = Services.TextService.Localize("path"); - - if (IsPostBack == false) - { - ClientTools - .SetActiveTreeType(Constants.Trees.Scripts) - .SyncTree(ScriptTreeSyncPath, false); - } - } - - override protected void OnInit(EventArgs e) - { - base.OnInit(e); - - filename = Request.QueryString["file"].Replace('\\', '/').TrimStart('/'); - - //need to change the editor type if it is XML - if (filename.EndsWith("xml")) - editorSource.CodeBase = Umbraco.Web._Legacy.Controls.CodeArea.EditorType.XML; - else if (filename.EndsWith("master")) - editorSource.CodeBase = Umbraco.Web._Legacy.Controls.CodeArea.EditorType.HTML; - - - var editor = Panel1.NewTabPage(Services.TextService.Localize("settings/script")); - editor.Controls.Add(Pane7); - - var props = Panel1.NewTabPage(Services.TextService.Localize("properties")); - props.Controls.Add(Pane8); - - - SaveButton = Panel1.Menu.NewButton(); - SaveButton.Text = Services.TextService.Localize("save"); - SaveButton.ButtonType = MenuButtonType.Primary; - SaveButton.ID = "save"; - SaveButton.CssClass = "client-side"; - - if (editorSource.CodeBase == Umbraco.Web._Legacy.Controls.CodeArea.EditorType.HTML) - { - // Editing buttons - Panel1.Menu.InsertSplitter(); - Umbraco.Web._Legacy.Controls.MenuIconI umbField = Panel1.Menu.NewIcon(); - umbField.ImageURL = SystemDirectories.Umbraco + "/images/editor/insField.gif"; - umbField.OnClickCommand = Umbraco.Web.UI.Pages.ClientTools.Scripts.OpenModalWindow(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + editorSource.ClientID + "&tagName=UMBRACOGETDATA", Services.TextService.Localize("template/insertPageField"), 640, 550); - umbField.AltText = Services.TextService.Localize("template/insertPageField"); - - // TODO: Update icon - Umbraco.Web._Legacy.Controls.MenuIconI umbDictionary = Panel1.Menu.NewIcon(); - umbDictionary.ImageURL = GlobalSettings.Path + "/images/editor/dictionaryItem.gif"; - umbDictionary.OnClickCommand = Umbraco.Web.UI.Pages.ClientTools.Scripts.OpenModalWindow(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + editorSource.ClientID + "&tagName=UMBRACOGETDICTIONARY", Services.TextService.Localize("template/insertDictionaryItem"), 640, 550); - umbDictionary.AltText = "Insert umbraco dictionary item"; - - // Help - Panel1.Menu.InsertSplitter(); - - Umbraco.Web._Legacy.Controls.MenuIconI helpIcon = Panel1.Menu.NewIcon(); - helpIcon.OnClickCommand = Umbraco.Web.UI.Pages.ClientTools.Scripts.OpenModalWindow(Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/settings/modals/showumbracotags.aspx?alias=", Services.TextService.Localize("template/quickGuide"), 600, 580); - helpIcon.ImageURL = SystemDirectories.Umbraco + "/images/editor/help.png"; - helpIcon.AltText = Services.TextService.Localize("template/quickGuide"); - - } - - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/codeEditorSave.asmx")); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Item.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Item.cs index 21c681b7bd..b015fdd02a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Item.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Item.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.ComponentModel; +using System.Globalization; using System.Web; using System.Linq; using System.Web.UI; @@ -314,7 +315,7 @@ namespace umbraco.presentation.templateControls if (u == null) return false; var permission = Current.Services.UserService.GetPermissions(u, PageElements["path"].ToString()); - return permission.AssignedPermissions.Contains(ActionUpdate.Instance.Letter.ToString(), StringComparer.Ordinal); + return permission.AssignedPermissions.Contains(ActionUpdate.Instance.Letter.ToString(CultureInfo.InvariantCulture), StringComparer.Ordinal); } #endregion diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs deleted file mode 100644 index 747accaef7..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ /dev/null @@ -1,490 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Configuration.Provider; -using System.Globalization; -using System.Linq; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.HtmlControls; -using System.Web.UI.WebControls; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Web; -using Umbraco.Web.Security; -using umbraco.controls; -using Umbraco.Core.IO; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.UI; -using Umbraco.Web.UI.Pages; -using Umbraco.Core.Security; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Controls; - -namespace umbraco.cms.presentation.user -{ - /// - /// Summary description for EditUser. - /// - public partial class EditUser : UmbracoEnsuredPage - { - public EditUser() - { - CurrentApp = Constants.Applications.Users.ToString(); - } - protected HtmlTable macroProperties; - protected TextBox uname = new TextBox() { ID = "uname" }; - protected RequiredFieldValidator unameValidator = new RequiredFieldValidator(); - protected TextBox lname = new TextBox() { ID = "lname" }; - protected RequiredFieldValidator lnameValidator = new RequiredFieldValidator(); - protected CustomValidator lnameCustomValidator = new CustomValidator(); - protected PlaceHolder passw = new PlaceHolder(); - protected CheckBoxList lapps = new CheckBoxList(); - protected TextBox email = new TextBox() { ID = "email" }; - protected RequiredFieldValidator emailValidator = new RequiredFieldValidator(); - protected CustomValidator emailCustomValidator = new CustomValidator(); - protected DropDownList userType = new DropDownList(); - protected DropDownList userLanguage = new DropDownList(); - protected CheckBox NoConsole = new CheckBox(); - protected CheckBox Disabled = new CheckBox(); - - protected ContentPicker mediaPicker = new ContentPicker(); - protected ContentPicker contentPicker = new ContentPicker(); - - protected TextBox cName = new TextBox(); - protected CheckBox cFulltree = new CheckBox(); - protected DropDownList cDocumentType = new DropDownList(); - protected DropDownList cDescription = new DropDownList(); - protected DropDownList cCategories = new DropDownList(); - protected DropDownList cExcerpt = new DropDownList(); - protected ContentPicker cMediaPicker = new ContentPicker(); - protected ContentPicker cContentPicker = new ContentPicker(); - protected CustomValidator sectionValidator = new CustomValidator(); - - protected Pane pp = new Pane(); - - private IUser u; - - private MembershipHelper _membershipHelper; - - private MembershipProvider BackOfficeProvider - { - get { return global::Umbraco.Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); } - } - - protected void Page_Load(object sender, EventArgs e) - { - _membershipHelper = new MembershipHelper(UmbracoContext.Current); - int UID = int.Parse(Request.QueryString["id"]); - u = Services.UserService.GetUserById(UID); - - //the true admin can only edit the true admin - if (u.Id == 0 && Security.CurrentUser.Id != 0) - { - throw new Exception("Only the root user can edit the 'root' user (id:0)"); - } - - //only another admin can edit another admin (who is not the true admin) - if (u.IsAdmin() && Security.CurrentUser.IsAdmin() == false) - { - throw new Exception("Admin users can only be edited by admins"); - } - - // Populate usertype list - foreach (var ut in Services.UserService.GetAllUserTypes()) - { - if (Security.CurrentUser.IsAdmin() || ut.Alias != "admin") - { - ListItem li = new ListItem(Services.TextService.Localize("user", ut.Name.ToLower()), ut.Id.ToString()); - if (ut.Id == u.UserType.Id) - li.Selected = true; - - userType.Items.Add(li); - } - } - - var userCulture = UserExtensions.GetUserCulture(u.Language, Services.TextService); - - // Populate ui language lsit - foreach (var lang in Services.TextService.GetSupportedCultures()) - { - var regionCode = Services.TextService.ConvertToRegionCodeFromSupportedCulture(lang); - - var li = new ListItem(lang.DisplayName, regionCode); - - if (Equals(lang, userCulture)) - li.Selected = true; - - userLanguage.Items.Add(li); - } - - // Console access and disabling - NoConsole.Checked = u.IsLockedOut; - Disabled.Checked = u.IsApproved == false; - - PlaceHolder medias = new PlaceHolder(); - mediaPicker.AppAlias = Constants.Applications.Media; - mediaPicker.TreeAlias = "media"; - - if (u.StartMediaId > 0) - mediaPicker.Value = u.StartMediaId.ToString(); - else - mediaPicker.Value = "-1"; - - medias.Controls.Add(mediaPicker); - - PlaceHolder content = new PlaceHolder(); - contentPicker.AppAlias = Constants.Applications.Content; - contentPicker.TreeAlias = "content"; - - if (u.StartContentId > 0) - contentPicker.Value = u.StartContentId.ToString(CultureInfo.InvariantCulture); - else - contentPicker.Value = "-1"; - - content.Controls.Add(contentPicker); - - // Add password changer - var passwordChanger = (passwordChanger)LoadControl(SystemDirectories.Umbraco + "/controls/passwordChanger.ascx"); - passwordChanger.MembershipProviderName = UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider; - - //Add a custom validation message for the password changer - var passwordValidation = new CustomValidator - { - ID = "PasswordChangerValidator" - }; - var validatorContainer = new HtmlGenericControl("div") - { - Visible = false, - EnableViewState = false - }; - validatorContainer.Attributes["class"] = "alert alert-error"; - validatorContainer.Style.Add(HtmlTextWriterStyle.MarginTop, "10px"); - validatorContainer.Style.Add(HtmlTextWriterStyle.Width, "300px"); - var validatorContainer2 = new HtmlGenericControl("p"); - validatorContainer.Controls.Add(validatorContainer2); - validatorContainer2.Controls.Add(passwordValidation); - passw.Controls.Add(passwordChanger); - passw.Controls.Add(validatorContainer); - - var validationSummary = new ValidationSummary - { - ID = "validationSummary", - DisplayMode = ValidationSummaryDisplayMode.BulletList, - CssClass = "error" - }; - - pp.addProperty(validationSummary); - - pp.addProperty(Services.TextService.Localize("user/username"), uname, unameValidator); - pp.addProperty(Services.TextService.Localize("user/loginname"), lname, lnameValidator, lnameCustomValidator); - pp.addProperty(Services.TextService.Localize("user/password"), passw); - - pp.addProperty(Services.TextService.Localize("general/email"), email, emailValidator, emailCustomValidator); - pp.addProperty(Services.TextService.Localize("user/usertype"), userType); - pp.addProperty(Services.TextService.Localize("user/language"), userLanguage); - - //Media / content root nodes - Pane ppNodes = new Pane(); - ppNodes.addProperty(Services.TextService.Localize("user/startnode"), content); - ppNodes.addProperty(Services.TextService.Localize("user/mediastartnode"), medias); - - //Generel umrbaco access - Pane ppAccess = new Pane(); - ppAccess.addProperty(Services.TextService.Localize("user/noConsole"), NoConsole); - ppAccess.addProperty(Services.TextService.Localize("user/disabled"), Disabled); - - //access to which modules... - Pane ppModules = new Pane(); - ppModules.addProperty(Services.TextService.Localize("user/modules"), lapps); - ppModules.addProperty(" ", sectionValidator); - - TabPage userInfo = UserTabs.NewTabPage(u.Name); - - userInfo.Controls.Add(pp); - - userInfo.Controls.Add(ppAccess); - userInfo.Controls.Add(ppNodes); - - userInfo.Controls.Add(ppModules); - - userInfo.HasMenu = true; - - var save = userInfo.Menu.NewButton(); - save.Click += SaveUser_Click; - save.ID = "save"; - save.ToolTip = Services.TextService.Localize("save"); - save.Text = Services.TextService.Localize("save"); - save.ButtonType = MenuButtonType.Primary; - - sectionValidator.ServerValidate += SectionValidator_OnServerValidate; - sectionValidator.ControlToValidate = lapps.ID; - sectionValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorMandatoryWithoutTab", new[] { Services.TextService.Localize("user/modules") }); - sectionValidator.CssClass = "error"; - sectionValidator.Style.Add("color", "red"); - - unameValidator.ControlToValidate = uname.ID; - unameValidator.Display = ValidatorDisplay.Dynamic; - unameValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); - unameValidator.CssClass = "error"; - unameValidator.Style.Add("color", "red"); - unameValidator.Style.Add("margin-left", "5px"); - unameValidator.Style.Add("line-height", "28px"); - - lnameValidator.ControlToValidate = lname.ID; - lnameValidator.Display = ValidatorDisplay.Dynamic; - lnameValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); - lnameValidator.CssClass = "error"; - lnameValidator.Style.Add("color", "red"); - lnameValidator.Style.Add("margin-left", "5px"); - lnameValidator.Style.Add("line-height", "28px"); - - lnameCustomValidator.ServerValidate += LnameCustomValidator_OnServerValidate; - lnameCustomValidator.Display = ValidatorDisplay.Dynamic; - lnameCustomValidator.ControlToValidate = lname.ID; - var localizedLname = Services.TextService.Localize("user/loginname"); - lnameCustomValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorExistsWithoutTab", localizedLname); - lnameCustomValidator.CssClass = "error"; - lnameCustomValidator.Style.Add("color", "red"); - lnameCustomValidator.Style.Add("margin-left", "5px"); - lnameCustomValidator.Style.Add("line-height", "28px"); - - emailValidator.ControlToValidate = email.ID; - emailValidator.Display = ValidatorDisplay.Dynamic; - emailValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); - emailValidator.CssClass = "error"; - emailValidator.Style.Add("color", "red"); - emailValidator.Style.Add("margin-left", "5px"); - emailValidator.Style.Add("line-height", "28px"); - - emailCustomValidator.ServerValidate += EmailCustomValidator_OnServerValidate; - emailCustomValidator.Display = ValidatorDisplay.Dynamic; - emailCustomValidator.ControlToValidate = email.ID; - var localizedEmail = Services.TextService.Localize("general/email"); - emailCustomValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorRegExpWithoutTab", localizedEmail); - emailCustomValidator.CssClass = "error"; - emailCustomValidator.Style.Add("color", "red"); - emailCustomValidator.Style.Add("margin-left", "5px"); - emailCustomValidator.Style.Add("line-height", "28px"); - - SetupForm(); - - ClientTools - .SetActiveTreeType(Constants.Trees.Users) - .SyncTree(UID.ToString(), false); - } - - private void LnameCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) - { - var usersWithLoginName = Services.UserService.GetByUsername(lname.Text); - args.IsValid = usersWithLoginName == null || usersWithLoginName.Id == u.Id; - } - - private void EmailCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) - { - args.IsValid = MembershipProviderBase.IsEmailValid(email.Text.Trim()); - } - - private void SectionValidator_OnServerValidate(object source, ServerValidateEventArgs args) - { - args.IsValid = false || lapps.SelectedIndex >= 0; - } - - /// - /// Setups the form. - /// - private void SetupForm() - { - - if (!IsPostBack) - { - MembershipUser user = BackOfficeProvider.GetUser(u.Username, false); - uname.Text = u.Name; - lname.Text = (user == null) ? u.Username : user.UserName; - email.Text = (user == null) ? u.Email : user.Email; - - contentPicker.Value = u.StartContentId.ToString(CultureInfo.InvariantCulture); - mediaPicker.Value = u.StartMediaId.ToString(CultureInfo.InvariantCulture); - - // get the current users applications - string currentUserApps = ";"; - foreach (var a in Security.CurrentUser.AllowedSections) - currentUserApps += a + ";"; - - var uapps = u.AllowedSections.ToArray(); - foreach (var app in Services.SectionService.GetSections()) - { - if (Security.CurrentUser.IsAdmin() || currentUserApps.Contains(";" + app.Alias + ";")) - { - var li = new ListItem(Services.TextService.Localize("sections", app.Alias), app.Alias); - if (IsPostBack == false) - { - foreach (var tmp in uapps) - { - if (app.Alias == tmp) li.Selected = true; - } - } - lapps.Items.Add(li); - } - } - } - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - //lapps.SelectionMode = ListSelectionMode.Multiple; - lapps.RepeatLayout = RepeatLayout.Flow; - lapps.RepeatDirection = RepeatDirection.Vertical; - } - - /// - /// This handles changing the password - /// - /// - /// - /// - private void ChangePassword(passwordChanger passwordChangerControl, MembershipUser membershipUser, CustomValidator passwordChangerValidator) - { - if (passwordChangerControl.IsChangingPassword) - { - //SD: not sure why this check is here but must have been for some reason at some point? - if (string.IsNullOrEmpty(passwordChangerControl.ChangingPasswordModel.NewPassword) == false) - { - // make sure password is not empty - if (string.IsNullOrEmpty(u.RawPasswordValue)) u.RawPasswordValue = "default"; - } - - var changePasswordModel = passwordChangerControl.ChangingPasswordModel; - - //now do the actual change - var changePassResult = _membershipHelper.ChangePassword( - membershipUser.UserName, changePasswordModel, BackOfficeProvider); - - if (changePassResult.Success) - { - //if it is successful, we need to show the generated password if there was one, so set - //that back on the control - passwordChangerControl.ChangingPasswordModel.GeneratedPassword = changePassResult.Result.ResetPassword; - } - else - { - passwordChangerValidator.IsValid = false; - passwordChangerValidator.ErrorMessage = changePassResult.Result.ChangeError.ErrorMessage; - passw.Controls[1].Visible = true; - } - - } - } - - /// - /// Handles the Click event of the saveUser control. - /// - /// The source of the event. - /// The instance containing the event data. - private void SaveUser_Click(object sender, EventArgs e) - { - if (base.IsValid) - { - try - { - var membershipUser = BackOfficeProvider.GetUser(u.Username, false); - if (membershipUser == null) - { - throw new ProviderException("Could not find user in the membership provider with login name " + u.Username); - } - - var passwordChangerControl = (passwordChanger)passw.Controls[0]; - var passwordChangerValidator = (CustomValidator)passw.Controls[1].Controls[0].Controls[0]; - - //perform the changing password logic - ChangePassword(passwordChangerControl, membershipUser, passwordChangerValidator); - - //update the membership provider - UpdateMembershipProvider(membershipUser); - - //update the Umbraco user properties - even though we are updating some of these properties in the membership provider that is - // ok since the membership provider might be storing these details someplace totally different! But we want to keep our UI in sync. - u.Name = uname.Text.Trim(); - u.Language = userLanguage.SelectedValue; - u.UserType = Services.UserService.GetUserTypeById(int.Parse(userType.SelectedValue)); - u.Email = email.Text.Trim(); - u.Username = lname.Text; - u.IsApproved = Disabled.Checked == false; - u.IsLockedOut = NoConsole.Checked; - - int startNode; - if (int.TryParse(contentPicker.Value, out startNode) == false) - { - //set to default if nothing is choosen - if (u.StartContentId > 0) - startNode = u.StartContentId; - else - startNode = -1; - } - u.StartContentId = startNode; - - - int mstartNode; - if (int.TryParse(mediaPicker.Value, out mstartNode) == false) - { - //set to default if nothing is choosen - if (u.StartMediaId > 0) - mstartNode = u.StartMediaId; - else - mstartNode = -1; - } - u.StartMediaId = mstartNode; - - u.ClearAllowedSections(); - foreach (ListItem li in lapps.Items) - { - if (li.Selected) - u.AddAllowedSection(li.Value); - } - - Services.UserService.Save(u); - - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Save, Services.TextService.Localize("speechBubbles/editUserSaved"), ""); - } - catch (Exception ex) - { - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Error, Services.TextService.Localize("speechBubbles/editUserError"), ""); - Current.Logger.Error("Exception", ex); - } - } - else - { - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Error, - Services.TextService.Localize("speechBubbles/validationFailedHeader"), - Services.TextService.Localize("speechBubbles/validationFailedMessage")); - } - } - - private void UpdateMembershipProvider(MembershipUser membershipUser) - { - //SD: This check must be here for some reason but apparently we don't want to try to - // update when the AD provider is active. - if ((BackOfficeProvider is ActiveDirectoryMembershipProvider) == false) - { - var membershipHelper = new MembershipHelper(new HttpContextWrapper(Context)); - //set the writable properties that we are editing - membershipHelper.UpdateMember(membershipUser, BackOfficeProvider, - email.Text.Trim(), - Disabled.Checked == false); - } - } - - /// - /// UserTabs control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected TabView UserTabs; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx deleted file mode 100644 index d8671f0596..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx +++ /dev/null @@ -1,27 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUserType.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" - Inherits="umbraco.cms.presentation.user.EditUserType" %> - -<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs deleted file mode 100644 index c32fcaf76e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Web.UI.WebControls; -using System.Collections.Generic; -using System.Linq; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.UI; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; -using Action = Umbraco.Web._Legacy.Actions.Action; - -namespace umbraco.cms.presentation.user -{ - public partial class EditUserType : UmbracoEnsuredPage - { - public EditUserType() - { - CurrentApp = Constants.Applications.Users; - } - protected void Page_Load(object sender, EventArgs e) - { - pnlUmbraco.Text = Services.TextService.Localize("usertype"); - - var save = pnlUmbraco.Menu.NewButton(); - save.Click += save_Click; - save.ID = "save"; - save.ToolTip = Services.TextService.Localize("save"); - save.Text = Services.TextService.Localize("save"); - - pp_alias.Text = Services.TextService.Localize("usertype") + " " + Services.TextService.Localize("alias"); - pp_name.Text = Services.TextService.Localize("usertype") + " " + Services.TextService.Localize("name"); - - pp_rights.Text = Services.TextService.Localize("default") + " " + Services.TextService.Localize("rights"); - - //ensure we have a query string - if (string.IsNullOrEmpty(Request.QueryString["id"])) - return; - //ensuer it is an integer - if (!int.TryParse(Request.QueryString["id"], out m_userTypeID)) - return; - - if (!IsPostBack) - { - BindActions(); - - ClientTools - .SetActiveTreeType(Constants.Trees.UserTypes) - .SyncTree(m_userTypeID.ToString(), false); - } - } - - void save_Click(object sender, EventArgs e) - { - var userType = CurrentUserType; - userType.Name = txtUserTypeName.Text; - string actions = ""; - - foreach (ListItem li in cbl_rights.Items) { - if (li.Selected) - actions += li.Value; - } - - userType.Permissions = actions.ToCharArray().Select(x => x.ToString()); - Services.UserService.SaveUserType(userType); - - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Save, Services.TextService.Localize("speechBubbles/editUserTypeSaved"), ""); - } - - protected List CurrentUserTypeActions - { - get - { - if (m_userTypeActions == null) - m_userTypeActions = Action.FromString(string.Join("", CurrentUserType.Permissions)); - return m_userTypeActions; - } - } - - protected IUserType CurrentUserType - { - get - { - if (m_userType == null) - m_userType = Services.UserService.GetUserTypeById(m_userTypeID); - return m_userType; - } - } - private IUserType m_userType; - private List m_userTypeActions; - private int m_userTypeID; - - private void BindActions() - { - lblUserTypeAlias.Text = CurrentUserType.Alias; - txtUserTypeName.Text = CurrentUserType.Name; - hidUserTypeID.Value = CurrentUserType.Id.ToString(); - - foreach (IAction ai in Action.GetPermissionAssignable()) { - - ListItem li = new ListItem(Services.TextService.Localize(ai.Alias), ai.Letter.ToString()); - - if(CurrentUserTypeActions.Contains(ai)) - li.Selected = true; - - cbl_rights.Items.Add(li); - } - } - - - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs deleted file mode 100644 index fc88ec213c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUserType.aspx.designer.cs +++ /dev/null @@ -1,105 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.user { - - - public partial class EditUserType { - - /// - /// pnlUmbraco control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.UmbracoPanel pnlUmbraco; - - /// - /// pnl1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pnl1; - - /// - /// hidUserTypeID control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.HiddenField hidUserTypeID; - - /// - /// pp_name control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_name; - - /// - /// txtUserTypeName control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox txtUserTypeName; - - /// - /// pp_alias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_alias; - - /// - /// lblUserTypeAlias control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Label lblUserTypeAlias; - - /// - /// pnl2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pnl2; - - /// - /// pp_rights control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_rights; - - /// - /// cbl_rights control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CheckBoxList cbl_rights; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx deleted file mode 100644 index 694b3e9ee4..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx +++ /dev/null @@ -1,35 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="NodePermissions.ascx.cs" Inherits="umbraco.cms.presentation.user.NodePermissions" %> -

- <%=Services.TextService.Localize("user/permissionSelectedPages")%> -
- -

- - -

- - - - -

-
- - - - -
    - - -
  • - /> - -
  • -
    - -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs deleted file mode 100644 index e194933d8e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; - -using Umbraco.Core.Models.Membership; - -using Umbraco.Web.UI.Controls; -using Umbraco.Web._Legacy.Actions; -using Action = Umbraco.Web._Legacy.Actions.Action; - -namespace umbraco.cms.presentation.user -{ - - /// - /// An object to display the current permissions for a user and a node. - /// - public partial class NodePermissions : UmbracoUserControl - { - - protected override void OnInit(EventArgs e) { - base.OnInit(e); - } - - protected void Page_Load(object sender, EventArgs e) - { - DataBind(); - } - - private IUser m_umbracoUser; - private int[] m_nodeID = {-1}; - private UserPermissions m_userPermissions; - private string m_clientItemChecked = "void(0);"; - - public int UserID - { - get { return m_umbracoUser.Id; } - set - { - m_umbracoUser = Services.UserService.GetUserById(value); - m_userPermissions = new UserPermissions(m_umbracoUser); - } - } - - private bool m_viewOnly = false; - public bool ViewOnly { - get { return m_viewOnly; } - set { m_viewOnly = value; } - } - - public int[] NodeID - { - get { return m_nodeID; } - set { m_nodeID = value; } - } - - /// - /// The JavaScript method to call when a node is checked. The method will receive a comma separated list of node IDs that are checked. - /// - public string OnClientItemChecked - { - get { return m_clientItemChecked; } - set { m_clientItemChecked = value; } - } - - public override void DataBind() - { - base.DataBind(); - - - if (m_umbracoUser == null) - throw new ArgumentNullException("No User specified"); - - //get the logged in user's permissions - UserPermissions currUserPermissions = new UserPermissions(Security.CurrentUser); - - //lookup permissions for last node selected - int selectedNodeId = m_nodeID[m_nodeID.Length - 1]; - - List lstCurrUserActions = currUserPermissions.GetExistingNodePermission(selectedNodeId); - List lstLookupUserActions = m_userPermissions.GetExistingNodePermission(selectedNodeId); - - List lstAllActions = Action.GetPermissionAssignable(); - - //no node is selected, disable the check boxes - if (m_nodeID[0] == -1) - { - ShowMessage("No node selected"); - return; - } - - //ensure the current user has access to assign permissions. - //if their actions list is null then it means that the node is not published. - if (lstCurrUserActions == null || lstCurrUserActions.Contains(ActionRights.Instance)) - BindExistingPermissions(lstAllActions, lstLookupUserActions); - else - ShowMessage("You do not have access to assign permissions to this node"); - - string names = ""; - foreach (int id in m_nodeID) { - if (id > 0) - { - var c = Services.ContentService.GetById(id); - names += c.Name + ", "; - } - } - - lt_names.Text = names.Trim().Trim(','); - } - - private void ShowMessage(string msg) - { - lblMessage.Visible = true; - lblMessage.Text = msg; - - } - - private void BindExistingPermissions(List allActions, List userActions) - { - - List assignedPermissions = new List(); - foreach (var a in allActions) - { - AssignedPermission p = new AssignedPermission(); - p.Permission = a; - p.HasPermission = (userActions != null ? userActions.Contains(a) : false); - assignedPermissions.Add(p); - } - - rptPermissionsList.DataSource = assignedPermissions; - rptPermissionsList.DataBind(); - - } - - protected struct AssignedPermission - { - public IAction Permission; - public bool HasPermission; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.designer.cs deleted file mode 100644 index a1e109b458..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/NodePermissions.ascx.designer.cs +++ /dev/null @@ -1,51 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.user { - - - public partial class NodePermissions { - - /// - /// lt_names control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal lt_names; - - /// - /// pnlReplaceChildren control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel pnlReplaceChildren; - - /// - /// lblMessage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Label lblMessage; - - /// - /// rptPermissionsList control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater rptPermissionsList; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx deleted file mode 100644 index 75b5d6e66a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx +++ /dev/null @@ -1,47 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="PermissionEditor.aspx.cs" Inherits="umbraco.cms.presentation.user.PermissionEditor" %> -<%@ Import Namespace="Umbraco.Web" %> -<%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register Src="NodePermissions.ascx" TagName="NodePermissions" TagPrefix="user" %> -<%@ Register TagPrefix="ui" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> - - - - - - - - - - - -
- -
- -
- -
- - - -
-
- - -
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs deleted file mode 100644 index 330c53f8db..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Web.UI; -using Umbraco.Core; -using umbraco.cms.presentation.Trees; -using Umbraco.Core.IO; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.UI.Pages; - -namespace umbraco.cms.presentation.user -{ - - public partial class PermissionEditor : UmbracoEnsuredPage - { - public PermissionEditor() - { - CurrentApp = Constants.Applications.Users; - - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - if (!IsPostBack) - { - JTree.App = Constants.Applications.Content; - JTree.ShowContextMenu = false; - JTree.IsDialog = true; - } - } - - protected void Page_Load(object sender, EventArgs e) - { - if (string.IsNullOrEmpty(Request.QueryString["id"])) - return; - - CheckUser(Request.QueryString["id"]); - - var save = pnlUmbraco.Menu.NewButton(); - save.ID = "btnSave"; - save.ButtonType = Umbraco.Web._Legacy.Controls.MenuButtonType.Primary; - save.OnClientClick = "SavePermissions(); return false;"; - save.Text = Services.TextService.Localize("save"); - save.ToolTip = Services.TextService.Localize("save"); - - - nodePermissions.UserID = Convert.ToInt32(Request.QueryString["id"]); - pnlUmbraco.Text = Services.TextService.Localize("user/userPermissions"); - pnl1.Text = Services.TextService.Localize("user/permissionSelectPages"); - - if (!IsPostBack) - { - ClientTools cTools = new ClientTools(this); - cTools.SetActiveTreeType(Constants.Trees.UserPermissions) - .SyncTree(Request.QueryString["id"], false); - } - } - - /// - /// Since Umbraco stores users in cache, we'll use this method to retrieve our user object by the selected id - /// - public IUser UmbracoUser - { - get - { - return Services.UserService.GetUserById(Convert.ToInt32(Request.QueryString["id"])); - } - } - - /// - /// Makes sure the user exists with the id specified - /// - /// - private void CheckUser(string strID) - { - int id; - bool parsed = false; - IUser oUser = null; - if (parsed = int.TryParse(strID, out id)) - oUser = Services.UserService.GetUserById(id); - - if (oUser == null || oUser.UserType == null || parsed == false) - throw new Exception("No user found with id: " + strID); - } - - - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference(SystemDirectories.Umbraco + "/users/PermissionsHandler.asmx")); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.designer.cs deleted file mode 100644 index 1397e88629..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx.designer.cs +++ /dev/null @@ -1,78 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.user { - - - public partial class PermissionEditor { - - /// - /// CssInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude2; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// pnlUmbraco control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.UmbracoPanel pnlUmbraco; - - /// - /// pnl1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pnl1; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - - /// - /// nodePermissions control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.cms.presentation.user.NodePermissions nodePermissions; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx deleted file mode 100644 index 274efb0789..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="PermissionsHandler.asmx.cs" Class="umbraco.cms.presentation.user.PermissionsHandler" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs deleted file mode 100644 index 70ef741aab..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionsHandler.asmx.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Web; -using System.Web.Services; -using System.ComponentModel; -using System.Web.Script.Services; -using System.Web.UI; -using System.IO; -using System.Collections.Generic; -using Umbraco.Core.IO; -using Umbraco.Web.WebServices; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.user -{ - /// - /// Summary description for PermissionsHandler - /// - [WebService(Namespace = "http://tempuri.org/")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [ToolboxItem(false)] - [ScriptService] - public class PermissionsHandler : UmbracoAuthorizedWebService - { - - /// - /// Loads the NodePermissions UserControl with the appropriate properties, renders the contents and returns the output html. - /// - /// - /// - /// - [WebMethod] - public string GetNodePermissions(int userID, string nodes) - { - AuthorizeRequest(true); - - Page page = new Page(); - - string path = SystemDirectories.Umbraco + "/users/NodePermissions.ascx"; - NodePermissions nodePermissions = page.LoadControl(path) as NodePermissions; - - nodePermissions.UserID = userID; - nodePermissions.NodeID = toIntArray(nodes); - nodePermissions.ID = "nodePermissions"; - - page.Controls.Add(nodePermissions); - StringWriter sw = new StringWriter(); - HttpContext.Current.Server.Execute(page, sw, false); - return sw.ToString(); - } - - - - - [WebMethod] - public string SaveNodePermissions(int userID, string nodes, string permissions, bool replaceChild) - { - AuthorizeRequest(true); - - UserPermissions uPermissions = new UserPermissions(Services.UserService.GetUserById(userID)); - List actions = Action.FromString(permissions); - uPermissions.SaveNewPermissions(toIntArray(nodes), actions, replaceChild); - - return GetNodePermissions(userID, nodes); - } - - - private int[] toIntArray(string nodeIds) { - - string[] s_nodes = nodeIds.Split(','); - int[] i_nodes = new int[s_nodes.Length]; - - for (int i = 0; i < s_nodes.Length; i++) { - i_nodes[i] = int.Parse(s_nodes[i]); - } - - return i_nodes; - - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs deleted file mode 100644 index abd0343773..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.user -{ - /// - /// Provides umbraco user permission functionality on various nodes. Only nodes that are published are queried via the cache. - /// - public class UserPermissions - { - readonly IUser _user; - - public UserPermissions(IUser user) - { - _user = user; - } - - /// - /// saves new permissions with the parameters supplied - /// - /// - /// - /// - public void SaveNewPermissions(int[] nodeIDs, List actions, bool replaceChildren) - { - //ensure permissions that are permission assignable - var permissions = actions.FindAll(a => (a.CanBePermissionAssigned)); - - //ensure that only the nodes that the user has permissions to update are updated - var lstNoPermissions = new List(); - foreach (var nodeId in nodeIDs) - { - var nodeActions = Current.Services.UserService.GetPermissions(UmbracoContext.Current.Security.CurrentUser, GetNodePath(nodeId)); - - var lstActions = Action.FromEntityPermission(nodeActions); - if (lstActions == null || !lstActions.Contains(ActionRights.Instance)) - lstNoPermissions.Add(nodeId); - } - //remove the nodes that the user doesn't have permission to update - var lstNodeIDs = new List(); - lstNodeIDs.AddRange(nodeIDs); - foreach (var noPermission in lstNoPermissions) - lstNodeIDs.Remove(noPermission); - nodeIDs = lstNodeIDs.ToArray(); - - //get the complete list of node ids that this change will affect - var allNodes = new List(); - if (replaceChildren) - { - foreach (var nodeId in nodeIDs) - { - allNodes.Add(nodeId); - allNodes.AddRange(FindChildNodes(nodeId)); - } - } - else - { - allNodes.AddRange(nodeIDs); - } - - //if permissions are to be assigned, then assign them - if (permissions.Count > 0) - { - Current.Services.UserService.ReplaceUserPermissions( - _user.Id, permissions.Select(x => x.Letter), allNodes.ToArray()); - } - else - { - //If there are NO permissions for this node, we need to assign the ActionNull permission otherwise - //the node will inherit from it's parent. - Current.Services.UserService.ReplaceUserPermissions( - _user.Id, new[] { ActionNull.Instance.Letter }, allNodes.ToArray()); - } - - } - - /// - /// Returns the current user permissions for the node specified - /// - /// - /// - public List GetExistingNodePermission(int nodeId) - { - var path = GetNodePath(nodeId); - if (path != "") - { - //get the user and their permissions - - var permissions = Current.Services.UserService.GetPermissions(_user, path); - return Action.FromEntityPermission(permissions); - } - return null; - } - - /// - /// gets path attribute for node id passed - /// - /// - /// - private static string GetNodePath(int iNodeId) - { - var e = Current.Services.EntityService.Get(iNodeId, UmbracoObjectTypes.Document); - return e == null ? string.Empty : e.Path; - } - - /// - /// Finds all child node IDs - /// - /// - /// - private static IEnumerable FindChildNodes(int nodeId) - { - var docs = Current.Services.EntityService.GetChildren(nodeId, UmbracoObjectTypes.Document); - var nodeIds = new List(); - foreach (var doc in docs) - { - nodeIds.Add(doc.Id); - nodeIds.AddRange(FindChildNodes(doc.Id)); - } - return nodeIds; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs deleted file mode 100644 index d9a1286c14..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserTypeTasks.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Web.UI; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; - -namespace umbraco.cms.presentation.user -{ - public class UserTypeTasks : LegacyDialogTask - { - public override bool PerformSave() - { - try - { - var u = new UserType(Alias, Alias); - Current.Services.UserService.SaveUserType(u); - _returnUrl = string.Format("users/EditUserType.aspx?id={0}", u.Id); - return true; - } - catch - { - return false; - } - } - - public override bool PerformDelete() - { - var userType = Current.Services.UserService.GetUserTypeById(ParentID); - if (userType == null) - return false; - Current.Services.UserService.DeleteUserType(userType); - return true; - } - - private string _returnUrl = ""; - public override string ReturnUrl - { - get { return _returnUrl; } - } - - public override string AssignedApp - { - get { return Constants.Applications.Users.ToString(); } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx deleted file mode 100644 index 2ca307ed10..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs deleted file mode 100644 index 8b519f0a2f..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Globalization; -using System.Web.Services; -using System.Xml; -using Umbraco.Core; -using Umbraco.Web.WebServices; -using Umbraco.Core.Xml; - -namespace umbraco.webservices -{ - /// - /// Summary description for Developer. - /// - [WebService(Namespace="http://umbraco.org/webservices/")] - public class Developer : UmbracoAuthorizedWebService - { - - [WebMethod] - public XmlNode GetMacros(string Login, string Password) - { - if (ValidateCredentials(Login, Password) - && UserHasAppAccess(Constants.Applications.Developer.ToString(), Login)) - { - var xmlDoc = new XmlDocument(); - var macros = xmlDoc.CreateElement("macros"); - foreach (var m in Services.MacroService.GetAll()) - { - var mXml = xmlDoc.CreateElement("macro"); - mXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "id", m.Id.ToString(CultureInfo.InvariantCulture))); - mXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "alias", m.Alias)); - mXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "name", m.Name)); - macros.AppendChild(mXml); - } - return macros; - } - return null; - } - - [WebMethod] - public XmlNode GetMacro(int Id, string Login, string Password) - { - if (ValidateCredentials(Login, Password) - && UserHasAppAccess(Constants.Applications.Developer.ToString(), Login)) - { - var xmlDoc = new XmlDocument(); - var macro = xmlDoc.CreateElement("macro"); - var m = Services.MacroService.GetById(Id); //new cms.businesslogic.macro.Macro(Id); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "id", m.Id.ToString(CultureInfo.InvariantCulture))); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "refreshRate", m.CacheDuration.ToString(CultureInfo.InvariantCulture))); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "useInEditor", m.UseInEditor.ToString())); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "alias", m.Alias)); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "name", m.Name)); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "type", m.ControlType)); - macro.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "xslt", m.XsltPath)); - var properties = xmlDoc.CreateElement("properties"); - foreach (var mp in m.Properties) - { - var pXml = xmlDoc.CreateElement("property"); - pXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "alias", mp.Alias)); - pXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "name", mp.Name)); - pXml.Attributes.Append(XmlHelper.AddAttribute(xmlDoc, "public", true.ToString())); - properties.AppendChild(pXml); - } - macro.AppendChild(properties); - return macro; - } - return null; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx deleted file mode 100644 index 318840e4f7..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx.cs deleted file mode 100644 index bb696f60a1..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/templates.asmx.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Web.Services; -using System.Web.Script.Services; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Web.WebServices; - -namespace umbraco.webservices -{ - /// - /// Summary description for templates. - /// - [WebService(Namespace="http://umbraco.org/webservices/")] - [ScriptService] - public class templates : UmbracoAuthorizedWebService - { - - [WebMethod] - [ScriptMethod] - public string GetCodeSnippet(object templateId) - { - //NOTE: The legacy code threw an exception so will continue to do that. - AuthorizeRequest(Constants.Applications.Settings.ToString(), true); - - var snippetPath = SystemDirectories.Umbraco + "/scripting/templates/cshtml/"; - var filePath = IOHelper.MapPath(snippetPath + templateId); - - //Directory check.. only allow files in script dir and below to be edited - if (filePath.StartsWith(IOHelper.MapPath(snippetPath))) - { - var templateFile = - System.IO.File.OpenText(filePath); - var content = templateFile.ReadToEnd(); - templateFile.Close(); - return content; - } - else - { - throw new ArgumentException("Couldn't open snippet - Illegal path"); - - } - } - - } -}