From 898d25f3cb6c4d036f685e9d9afbfca62ebda47e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 31 Jul 2013 18:31:39 +0200 Subject: [PATCH 01/10] Include lang files in project so they'll actually end up in the final build --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 709c257b86..4fe113f747 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -916,6 +916,7 @@ + @@ -936,6 +937,7 @@ + @@ -958,6 +960,7 @@ + @@ -977,6 +980,7 @@ + @@ -1029,6 +1033,7 @@ + @@ -1058,6 +1063,7 @@ + @@ -1091,6 +1097,7 @@ + @@ -1140,6 +1147,7 @@ + @@ -1170,6 +1178,7 @@ + @@ -1194,6 +1203,7 @@ + @@ -1223,6 +1233,7 @@ + @@ -1246,6 +1257,7 @@ + @@ -1260,8 +1272,12 @@ + + + + @@ -1270,6 +1286,8 @@ + + @@ -1278,8 +1296,12 @@ + + + + @@ -1287,8 +1309,10 @@ + + @@ -1297,6 +1321,7 @@ + @@ -1312,6 +1337,8 @@ + + @@ -1353,6 +1380,7 @@ + From bdb6c5768aee71b6f3560c38b97db8f8ded38b05 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 1 Aug 2013 13:40:23 +0200 Subject: [PATCH 02/10] U4-2553 Double tabs when installing package that makes use of Dictionary Items to translate document types Added GetRawCaptionById method for the packager so the generated XML is correct --- src/umbraco.cms/businesslogic/ContentType.cs | 129 +++++++++++-------- 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index ebacf89fa1..71d676f54d 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -70,7 +70,7 @@ namespace umbraco.cms.businesslogic _alias = alias; _iconurl = icon; _thumbnail = thumbnail; - + if (masterContentType.HasValue) MasterContentType = masterContentType.Value; @@ -92,7 +92,7 @@ namespace umbraco.cms.businesslogic allowAtRoot, isContainer, Alias,icon,thumbnail,description FROM umbracoNode INNER JOIN cmsContentType ON umbracoNode.id = cmsContentType.nodeId WHERE nodeObjectType = @nodeObjectType"; - + #endregion #region Static Methods @@ -113,12 +113,12 @@ namespace umbraco.cms.businesslogic /// internal static IDictionary GetAliasesAndNames(string contentTypeAlias) { - return AliasToNames.GetOrAdd(contentTypeAlias, s => - { - var ct = GetByAlias(contentTypeAlias); - var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); - return userFields; - }); + return AliasToNames.GetOrAdd(contentTypeAlias, s => + { + var ct = GetByAlias(contentTypeAlias); + var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); + return userFields; + }); } /// @@ -150,21 +150,21 @@ namespace umbraco.cms.businesslogic //propertyTypeAlias needs to be invariant, so we will store uppercase var key = new System.Tuple(contentTypeAlias, propertyTypeAlias.ToUpper()); - + return PropertyTypeCache.GetOrAdd( key, tuple => - { - // With 4.10 we can't do this via direct SQL as we have content type mixins - var controlId = Guid.Empty; - var ct = GetByAlias(contentTypeAlias); + { + // With 4.10 we can't do this via direct SQL as we have content type mixins + var controlId = Guid.Empty; + var ct = GetByAlias(contentTypeAlias); var pt = ct.getPropertyType(propertyTypeAlias); if (pt != null) { controlId = pt.DataTypeDefinition.DataType.Id; } - return controlId; - }); + return controlId; + }); } /// @@ -244,7 +244,7 @@ namespace umbraco.cms.businesslogic private bool _isContainerContentType; private List _allowedChildContentTypeIDs; private List _virtualTabs; - + protected internal IContentTypeComposition ContentTypeItem; #endregion @@ -264,7 +264,7 @@ namespace umbraco.cms.businesslogic foreach (var i in GetContentIdsForContentType()) { RebuildXmlStructureForContentItem(i); - } + } } /// @@ -292,7 +292,7 @@ namespace umbraco.cms.businesslogic dr.Close(); } return ids; - } + } /// /// Rebuilds the xml structure for the content item by id @@ -325,7 +325,7 @@ namespace umbraco.cms.businesslogic INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId WHERE cmsContentType.nodeId = @nodeId)", SqlHelper.CreateParameter("@nodeId", this.Id)); - } + } #endregion @@ -443,7 +443,7 @@ namespace umbraco.cms.businesslogic } } } - + /// /// Gets or sets the description. /// @@ -580,41 +580,41 @@ namespace umbraco.cms.businesslogic cacheKey, TimeSpan.FromMinutes(15), () => + { + //MCH NOTE: For the timing being I have changed this to a dictionary to ensure that property types + //aren't added multiple times through the MasterContentType structure, because each level loads + //its own + inherited property types, which is wrong. Once we are able to fully switch to the new api + //this should no longer be a problem as the composition always contains a correct list of property types. + var result = new Dictionary(); + using (IRecordsReader dr = + SqlHelper.ExecuteReader( + "select id from cmsPropertyType where contentTypeId = @ctId order by sortOrder", + SqlHelper.CreateParameter("@ctId", Id))) { - //MCH NOTE: For the timing being I have changed this to a dictionary to ensure that property types - //aren't added multiple times through the MasterContentType structure, because each level loads - //its own + inherited property types, which is wrong. Once we are able to fully switch to the new api - //this should no longer be a problem as the composition always contains a correct list of property types. - var result = new Dictionary(); - using (IRecordsReader dr = - SqlHelper.ExecuteReader( - "select id from cmsPropertyType where contentTypeId = @ctId order by sortOrder", - SqlHelper.CreateParameter("@ctId", Id))) + while (dr.Read()) { - while (dr.Read()) + int id = dr.GetInt("id"); + PropertyType pt = PropertyType.GetPropertyType(id); + if (pt != null) + result.Add(pt.Id, pt); + } + } + + // Get Property Types from the master content type + if (MasterContentTypes.Count > 0) + { + foreach (var mct in MasterContentTypes) + { + var pts = GetContentType(mct).PropertyTypes; + foreach (var pt in pts) { - int id = dr.GetInt("id"); - PropertyType pt = PropertyType.GetPropertyType(id); - if (pt != null) + if (result.ContainsKey(pt.Id) == false) result.Add(pt.Id, pt); } } - - // Get Property Types from the master content type - if (MasterContentTypes.Count > 0) - { - foreach (var mct in MasterContentTypes) - { - var pts = GetContentType(mct).PropertyTypes; - foreach (var pt in pts) - { - if (result.ContainsKey(pt.Id) == false) - result.Add(pt.Id, pt); - } - } - } - return result.Select(x => x.Value).ToList(); - }); + } + return result.Select(x => x.Value).ToList(); + }); } } @@ -850,7 +850,7 @@ namespace umbraco.cms.businesslogic foreach (var i in value) { int id = i; - list.Add(new ContentTypeSort{Id = new Lazy(() => id), SortOrder = sort}); + list.Add(new ContentTypeSort { Id = new Lazy(() => id), SortOrder = sort }); sort++; } @@ -963,7 +963,7 @@ namespace umbraco.cms.businesslogic { ContentTypeItem.RemovePropertyType(pt.Alias); } - + // Remove from cache FlushFromCache(Id); } @@ -1163,7 +1163,7 @@ namespace umbraco.cms.businesslogic /// /// The id. public static void FlushFromCache(int id) - { + { //Ensure that MediaTypes are reloaded from db by clearing cache InMemoryCacheProvider.Current.Clear(); @@ -1428,12 +1428,12 @@ namespace umbraco.cms.businesslogic public List GetAllPropertyTypes() { var db = ApplicationContext.Current.DatabaseContext.Database; - var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new {Id = _id}); + var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = _id }); var tmp = propertyTypeDtos .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) .ToList(); - var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new {Id = _id}); + var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new { Id = _id }); foreach (var propertyTypeGroupDto in propertyTypeGroupDtos) { var inheritedPropertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = propertyTypeGroupDto.Id }); @@ -1504,6 +1504,24 @@ namespace umbraco.cms.businesslogic } } + /// + /// Gets the tab caption by id. + /// + /// The id. + /// + internal static string GetRawCaptionById(int id) + { + try + { + var tempCaption = SqlHelper.ExecuteScalar("Select text from cmsPropertyTypeGroup where id = " + id); + return tempCaption; + } + catch + { + return null; + } + } + private readonly int _id; private int? _sortOrder; @@ -1629,7 +1647,7 @@ namespace umbraco.cms.businesslogic { if (!_caption.StartsWith("#")) return _caption; - + var lang = Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name); if (lang != null) { @@ -1645,6 +1663,5 @@ namespace umbraco.cms.businesslogic } } #endregion - } } From 3435a75e9e0aa54d29d52a158311b78f739aff7e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 1 Aug 2013 14:39:22 +0200 Subject: [PATCH 03/10] U4-2553 Double tabs when installing package that makes use of Dictionary Items to translate document types Forgto to commit a file :) --- src/umbraco.cms/businesslogic/web/DocumentType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index 1a9e203ada..31d5d89e14 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -527,7 +527,7 @@ namespace umbraco.cms.businesslogic.web //Datatype definition guid was added in v4 to enable datatype imports ptx.AppendChild(XmlHelper.AddTextNode(xd, "Definition", pt.DataTypeDefinition.UniqueId.ToString())); - ptx.AppendChild(XmlHelper.AddTextNode(xd, "Tab", Tab.GetCaptionById(pt.TabId))); + ptx.AppendChild(XmlHelper.AddTextNode(xd, "Tab", Tab.GetRawCaptionById(pt.TabId))); ptx.AppendChild(XmlHelper.AddTextNode(xd, "Mandatory", pt.Mandatory.ToString())); ptx.AppendChild(XmlHelper.AddTextNode(xd, "Validation", pt.ValidationRegExp)); ptx.AppendChild(XmlHelper.AddCDataNode(xd, "Description", pt.GetRawDescription())); From c4b44ea0e391a53381d1e47e67625245e316c2f7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2013 15:16:04 +1000 Subject: [PATCH 04/10] Fixes: U4-2577 Can't save umbraco user - without re-filling in the password Fixes: U4-541 Wrong dictionary key when using in backend template names This changes the way that the value that is stored in the auth cookie. Previously we just stored a GUID which was the user's contextid stored in the db, now we store encrypted values of a few necessary user objects. In 6.2 we'll actually set a real .Net user object on the HttpContext. For now, the http module will simply just ensure that the culture is set correctly for the currently logged in user. --- .../Configuration/UmbracoSettings.cs | 27 +++ .../Security/AuthenticationExtensions.cs | 199 ++++++++++++++++++ .../Security/UmbracoBackOfficeIdentity.cs | 110 ++++++++++ src/Umbraco.Core/Security/UserData.cs | 51 +++++ src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Core/UriExtensions.cs | 4 + .../config/metablogConfig.config | 4 +- .../umbraco/controls/passwordChanger.ascx | 44 ++-- src/Umbraco.Web/Security/WebSecurity.cs | 73 +++---- .../UI/Pages/UmbracoEnsuredPage.cs | 8 - src/Umbraco.Web/UmbracoModule.cs | 60 +++++- .../umbraco/users/EditUser.aspx.cs | 28 +-- .../BasePages/BasePage.cs | 73 ++++--- .../BasePages/UmbracoEnsuredPage.cs | 3 - 14 files changed, 564 insertions(+), 123 deletions(-) create mode 100644 src/Umbraco.Core/Security/AuthenticationExtensions.cs create mode 100644 src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs create mode 100644 src/Umbraco.Core/Security/UserData.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 4ed5027539..9292fd21e9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Web; using System.Web.Caching; +using System.Web.Security; using System.Xml; using System.Configuration; @@ -299,6 +300,32 @@ namespace Umbraco.Core.Configuration } } + internal static string AuthCookieName + { + get + { + var value = GetKey("/settings/security/authCookieName"); + if (string.IsNullOrEmpty(value) == false) + { + return value; + } + return "UMB_UCONTEXT"; + } + } + + internal static string AuthCookieDomain + { + get + { + var value = GetKey("/settings/security/authCookieDomain"); + if (string.IsNullOrEmpty(value) == false) + { + return value; + } + return FormsAuthentication.CookieDomain; + } + } + /// /// Enables the experimental canvas (live) editing on the frontend of the website /// diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs new file mode 100644 index 0000000000..87d09aecf5 --- /dev/null +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -0,0 +1,199 @@ +using System; +using System.Web; +using System.Web.Security; +using Newtonsoft.Json; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Security +{ + internal static class FormsAuthenticationTicketExtensions + { + public static UmbracoBackOfficeIdentity CreateUmbracoIdentity(this FormsAuthenticationTicket ticket) + { + try + { + //create the Umbraco user identity + return new UmbracoBackOfficeIdentity(ticket); + } + catch (Exception ex) + { + //This might occur if we cannot decrypt the value in which case we'll just ignore it, it will + // be handled by the base pages + LogHelper.Error(typeof(FormsAuthenticationTicketExtensions), "An error occurred decrypting the user's ticket", ex); + return null; + } + } + } + + /// + /// Extensions to create and renew and remove authentication tickets for the Umbraco back office + /// + internal static class AuthenticationExtensions + { + //public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http) + //{ + // return http.User.Identity as UmbracoBackOfficeIdentity; + //} + + //internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http) + //{ + // return new HttpContextWrapper(http).GetCurrentIdentity(); + //} + + /// + /// This clears the authentication cookie + /// + public static void UmbracoLogout(this HttpContextBase http) + { + Logout(http, UmbracoSettings.AuthCookieName); + } + + internal static void UmbracoLogout(this HttpContext http) + { + new HttpContextWrapper(http).UmbracoLogout(); + } + + /// + /// Creates the umbraco authentication ticket + /// + /// + /// + public static void CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata) + { + CreateAuthTicket( + http, + userdata, + //This is one full day... this is how Umbraco has always created this cookie, it is setup to always + //expire one day from issue and it never gets updated. + 1440, + "/", + UmbracoSettings.AuthCookieName, + UmbracoSettings.AuthCookieDomain); + } + + internal static void CreateUmbracoAuthTicket(this HttpContext http, UserData userdata) + { + new HttpContextWrapper(http).CreateUmbracoAuthTicket(userdata); + } + + /// + /// Gets the umbraco auth ticket + /// + /// + /// + public static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http) + { + return GetAuthTicket(http, UmbracoSettings.AuthCookieName); + } + + internal static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContext http) + { + return new HttpContextWrapper(http).GetUmbracoAuthTicket(); + } + + /// + /// This clears the authentication cookie + /// + /// + /// + private static void Logout(this HttpContextBase http, string cookieName) + { + //remove from the request + http.Request.Cookies.Remove(cookieName); + + //expire from the response + var formsCookie = http.Response.Cookies[cookieName]; + if (formsCookie != null) + { + //this will expire immediately and be removed from the browser + formsCookie.Expires = DateTime.Now.AddYears(-1); + } + else + { + //ensure there's def an expired cookie + http.Response.Cookies.Add(new HttpCookie(cookieName) { Expires = DateTime.Now.AddYears(-1) }); + } + } + + /// + /// In v6 this is a custom cookie, in v7 this is a real formsauth cookie. + /// + /// + /// + /// + private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) + { + var formsCookie = http.Request.Cookies[cookieName]; + if (formsCookie == null) + { + return null; + } + + try + { + //get the cookie value + var cookieVal = formsCookie.Value.DecryptWithMachineKey(); + + //here we need to see if the cookie val can be serialized into UserData, if not it means it's probably an old cookie + var deserialized = JsonConvert.DeserializeObject(cookieVal); + + //in v6, we're not using real FormsAuth but our own custom cookie and then we just return a custom FormsAuth ticket + // for this request. + return new FormsAuthenticationTicket( + 4, + deserialized.RealName, + DateTime.Now, + DateTime.Now.AddMinutes(GlobalSettings.TimeOutInMinutes), + false, + cookieVal, + "/"); + + } + catch (Exception) + { + //occurs when decryption fails + http.Logout(cookieName); + return null; + } + } + + /// + /// Creates a custom umbraco auth cookie with the data specified + /// + /// The HTTP. + /// The user data. + /// The minutes persisted. + /// The cookie path. + /// Name of the cookie. + /// The cookie domain. + private static void CreateAuthTicket(this HttpContextBase http, + UserData userData, + int minutesPersisted, + string cookiePath, + string cookieName, + string cookieDomain) + { + var cookie = new HttpCookie(cookieName); + + if (GlobalSettings.UseSSL) + cookie.Secure = true; + + //ensure http only, this should only be able to be accessed via the server + cookie.HttpOnly = true; + cookie.Path = cookiePath; + cookie.Domain = cookieDomain; + cookie.Expires = DateTime.Now.AddMinutes(minutesPersisted); + + //serialize the user data + var json = JsonConvert.SerializeObject(userData); + //encrypt it + var encTicket = json.EncryptWithMachineKey(); + + //set the cookie value + cookie.Value = encTicket; + + http.Response.Cookies.Set(cookie); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs new file mode 100644 index 0000000000..bd48b12116 --- /dev/null +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -0,0 +1,110 @@ +using System; +using System.Web; +using System.Web.Security; +using Newtonsoft.Json; + +namespace Umbraco.Core.Security +{ + /// + /// A custom user identity for the Umbraco backoffice + /// + /// + /// All values are lazy loaded for performance reasons as the constructor is called for every single request + /// + internal class UmbracoBackOfficeIdentity : FormsIdentity + { + public UmbracoBackOfficeIdentity(FormsAuthenticationTicket ticket) + : base(ticket) + { + UserData = ticket.UserData; + EnsureDeserialized(); + } + + protected readonly string UserData; + internal UserData DeserializedData; + + public string UserContextId + { + get { return DeserializedData.UserContextId; } + } + + public int StartContentNode + { + get { return DeserializedData.StartContentNode; } + } + + public int StartMediaNode + { + get { return DeserializedData.StartMediaNode; } + } + + public string[] AllowedApplications + { + get { return DeserializedData.AllowedApplications; } + } + + public object Id + { + get { return DeserializedData.Id; } + } + + public string RealName + { + get { return DeserializedData.RealName; } + } + + public string Culture + { + get { return DeserializedData.Culture; } + } + + //public int SessionTimeout + //{ + // get + // { + // EnsureDeserialized(); + // return DeserializedData.SessionTimeout; + // } + //} + + public string[] Roles + { + get { return DeserializedData.Roles; } + } + + /// + /// This will ensure we only deserialize once + /// + /// + /// For performance reasons, we'll also check if there's an http context available, + /// if so, we'll chuck our instance in there so that we only deserialize once per request. + /// + protected void EnsureDeserialized() + { + if (DeserializedData != null) + return; + + if (HttpContext.Current != null) + { + //check if we've already done this in this request + var data = HttpContext.Current.Items[typeof(UmbracoBackOfficeIdentity)] as UserData; + if (data != null) + { + DeserializedData = data; + return; + } + } + + if (string.IsNullOrEmpty(UserData)) + { + throw new NullReferenceException("The " + typeof(UserData) + " found in the ticket cannot be empty"); + } + DeserializedData = JsonConvert.DeserializeObject(UserData); + + if (HttpContext.Current != null) + { + HttpContext.Current.Items[typeof (UmbracoBackOfficeIdentity)] = DeserializedData; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/UserData.cs b/src/Umbraco.Core/Security/UserData.cs new file mode 100644 index 0000000000..feee9c9dfa --- /dev/null +++ b/src/Umbraco.Core/Security/UserData.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Security +{ + /// + /// Data structure used to store information in the authentication cookie + /// + [DataContract(Name = "userData", Namespace = "")] + internal class UserData + { + public UserData() + { + AllowedApplications = new string[] {}; + Roles = new string[] {}; + } + + ///// + ///// When their session is going to expire (in ticks) + ///// + //[DataMember(Name = "timeout")] + //public long Timeout { get; set; } + + [DataMember(Name = "userContextId")] + public string UserContextId { get; set; } + + [DataMember(Name = "id")] + public object Id { get; set; } + + [DataMember(Name = "roles")] + public string[] Roles { get; set; } + + [DataMember(Name = "username")] + public string Username { get; set; } + + [DataMember(Name = "name")] + public string RealName { get; set; } + + [DataMember(Name = "startContent")] + public int StartContentNode { get; set; } + + [DataMember(Name = "startMedia")] + public int StartMediaNode { get; set; } + + [DataMember(Name = "allowedApps")] + public string[] AllowedApplications { get; set; } + + [DataMember(Name = "culture")] + public string Culture { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b1b83ace68..f4011e90a8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -666,6 +666,9 @@ + + + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index e5de22456b..6ecb8d67e8 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core /// public static class UriExtensions { + /// /// Checks if the current uri is a back office request /// @@ -19,11 +20,14 @@ namespace Umbraco.Core /// internal static bool IsBackOfficeRequest(this Uri url) { + var authority = url.GetLeftPart(UriPartial.Authority); var afterAuthority = url.GetLeftPart(UriPartial.Query) .TrimStart(authority) .TrimStart("/"); + + //check if this is in the umbraco back office return afterAuthority.InvariantStartsWith(GlobalSettings.Path.TrimStart("/")); } diff --git a/src/Umbraco.Web.UI/config/metablogConfig.config b/src/Umbraco.Web.UI/config/metablogConfig.config index 83cc2c1aa1..5621dbee75 100644 --- a/src/Umbraco.Web.UI/config/metablogConfig.config +++ b/src/Umbraco.Web.UI/config/metablogConfig.config @@ -5,7 +5,7 @@ 0 1080 False - Home + Base @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx b/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx index fccd948dec..b9da0cbac7 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx @@ -1,16 +1,34 @@ <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="passwordChanger.ascx.cs" Inherits="umbraco.controls.passwordChanger" %> -Change password
+ - \ No newline at end of file +Change password
+ + diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 08f5b0b67a..f45ff9c30e 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -5,12 +5,15 @@ using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Security; +using umbraco; using umbraco.BusinessLogic; using umbraco.DataLayer; using umbraco.businesslogic.Exceptions; using umbraco.cms.businesslogic.member; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; +using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Web.Security { @@ -369,53 +372,51 @@ namespace Umbraco.Web.Security ///
/// The umbraco user context ID. public string UmbracoUserContextId - { + { get { - if (StateHelper.Cookies.HasCookies && StateHelper.Cookies.UserContext.HasValue) + var authTicket = HttpContext.Current.GetUmbracoAuthTicket(); + if (authTicket == null) { - try - { - var encTicket = StateHelper.Cookies.UserContext.GetValue(); - if (string.IsNullOrEmpty(encTicket) == false) - { - return encTicket.DecryptWithMachineKey(); - } - } - catch (Exception ex) - { - if (ex is ArgumentException || ex is FormatException || ex is HttpException) - { - StateHelper.Cookies.UserContext.Clear(); - } - else - { - throw; - } - } + return ""; } - return ""; + var identity = authTicket.CreateUmbracoIdentity(); + if (identity == null) + { + HttpContext.Current.UmbracoLogout(); + return ""; + } + return identity.UserContextId; } set { - // zb-00004 #29956 : refactor cookies names & handling - if (StateHelper.Cookies.HasCookies) + if (value.IsNullOrWhiteSpace()) { - // Clearing all old cookies before setting a new one. - if (StateHelper.Cookies.UserContext.HasValue) - StateHelper.Cookies.ClearAll(); - - if (string.IsNullOrEmpty(value) == false) + HttpContext.Current.UmbracoLogout(); + } + else + { + var uid = GetUserId(value); + if (uid == -1) { - // Encrypt the value - var encTicket = value.EncryptWithMachineKey(); - - // Create new cookie. - StateHelper.Cookies.UserContext.SetValue(encTicket, 1); + HttpContext.Current.UmbracoLogout(); } else { - StateHelper.Cookies.UserContext.Clear(); + var user = User.GetUser(uid); + HttpContext.Current.CreateUmbracoAuthTicket( + new UserData + { + Id = uid, + AllowedApplications = user.Applications.Select(x => x.alias).ToArray(), + Culture = ui.Culture(user), + RealName = user.Name, + Roles = new string[] { user.UserType.Alias }, + StartContentNode = user.StartNodeId, + StartMediaNode = user.StartMediaId, + UserContextId = value, + Username = user.LoginName + }); } } } diff --git a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs index 3979415dc7..be55343696 100644 --- a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs +++ b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs @@ -51,14 +51,6 @@ namespace Umbraco.Web.UI.Pages Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?redir=" + Server.UrlEncode(Request.RawUrl), true); } } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(ui.Culture(Security.CurrentUser)); - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture; - } /// /// Gets/sets the app that this page is assigned to diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 604e61e55f..4ef5bc99a7 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -2,13 +2,17 @@ using System.Collections; using System.IO; using System.Linq; +using System.Threading; using System.Web; using System.Web.Routing; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Security; using Umbraco.Web.Routing; using umbraco; +using umbraco.BasePages; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Web.Configuration; @@ -29,7 +33,7 @@ namespace Umbraco.Web /// Begins to process a request. /// /// - void BeginRequest(HttpContextBase httpContext) + static void BeginRequest(HttpContextBase httpContext) { //we need to set the initial url in our ApplicationContext, this is so our keep alive service works and this must //exist on a global context because the keep alive service doesn't run in a web context. @@ -119,6 +123,58 @@ namespace Umbraco.Web RewriteToUmbracoHandler(httpContext, pcr); } + /// + /// Checks if the request is authenticated, if it is it sets the thread culture to the currently logged in user + /// + /// + /// + static void AuthenticateRequest(object sender, EventArgs e) + { + var app = (HttpApplication)sender; + var http = new HttpContextWrapper(app.Context); + + // do not process if client-side request + if (http.Request.Url.IsClientSideRequest()) + return; + + if (app.Request.Url.IsBackOfficeRequest() || app.Request.Url.IsInstallerRequest()) + { + var ticket = http.GetUmbracoAuthTicket(); + if (ticket != null) + { + //create the Umbraco user identity + var identity = ticket.CreateUmbracoIdentity(); + if (identity != null) + { + + //We'll leave setting custom identies/principals for 6.2, for now we'll just ensure that the cultures, etc.. are set + ////set the principal object + ////now we need to see if their session is still valid + //var timeout = BasePage.GetTimeout(identity.UserContextId); + //if (timeout > DateTime.Now.Ticks) + //{ + //var principal = new GenericPrincipal(identity, identity.Roles); + ////It is actually not good enough to set this on the current app Context and the thread, it also needs + //// to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + //// an underlying fault of asp.net not propogating the User correctly. + //if (HttpContext.Current != null) + //{ + // HttpContext.Current.User = principal; + //} + //app.Context.User = principal; + //Thread.CurrentPrincipal = principal; + //} + + //This is a back office/installer request, we will also set the culture/ui culture + Thread.CurrentThread.CurrentCulture = + Thread.CurrentThread.CurrentUICulture = + new System.Globalization.CultureInfo(identity.Culture); + + } + } + } + } + // returns a value indicating whether redirection took place and the request has // been completed - because we don't want to Response.End() here to terminate // everything properly. @@ -433,6 +489,8 @@ namespace Umbraco.Web BeginRequest(new HttpContextWrapper(httpContext)); }; + app.AuthenticateRequest += AuthenticateRequest; + app.PostResolveRequestCache += (sender, e) => { var httpContext = ((HttpApplication)sender).Context; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 55303bc219..1569a2d110 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -348,16 +348,12 @@ namespace umbraco.cms.presentation.user } } } - - #region Web Form Designer generated code - + protected override void OnInit(EventArgs e) { - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - InitializeComponent(); - base.OnInit(e); + //lapps.SelectionMode = ListSelectionMode.Multiple; + lapps.RepeatLayout = RepeatLayout.Flow; + lapps.RepeatDirection = RepeatDirection.Vertical; } protected override void OnPreRender(EventArgs e) @@ -366,23 +362,9 @@ namespace umbraco.cms.presentation.user ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/CMSNode.asmx")); // ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); - - + } - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - //lapps.SelectionMode = ListSelectionMode.Multiple; - lapps.RepeatLayout = RepeatLayout.Flow; - lapps.RepeatDirection = RepeatDirection.Vertical; - } - - #endregion - /// /// Handles the Click event of the saveUser control. /// diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 99c1172b1c..c2aec995ce 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Services; using umbraco.BusinessLogic; using umbraco.DataLayer; using Umbraco.Core; +using Umbraco.Core.Security; namespace umbraco.BasePages { @@ -223,10 +224,10 @@ namespace umbraco.BasePages return false; } - private static long GetTimeout(string umbracoUserContextID) + internal static long GetTimeout(string umbracoUserContextId) { return ApplicationContext.Current.ApplicationCache.GetCacheItem( - CacheKeys.UserContextTimeoutCacheKey + umbracoUserContextID, + CacheKeys.UserContextTimeoutCacheKey + umbracoUserContextId, new TimeSpan(0, UmbracoTimeOutInMinutes / 10, 0), () => GetTimeout(true)); } @@ -257,50 +258,48 @@ namespace umbraco.BasePages { get { - if (StateHelper.Cookies.HasCookies && StateHelper.Cookies.UserContext.HasValue) + var authTicket = HttpContext.Current.GetUmbracoAuthTicket(); + if (authTicket == null) { - try - { - var encTicket = StateHelper.Cookies.UserContext.GetValue(); - if (string.IsNullOrEmpty(encTicket) == false) - { - return encTicket.DecryptWithMachineKey(); - } - } - catch (Exception ex) - { - if (ex is ArgumentException || ex is FormatException || ex is HttpException) - { - StateHelper.Cookies.UserContext.Clear(); - } - else - { - throw; - } - } + return ""; } - return ""; + var identity = authTicket.CreateUmbracoIdentity(); + if (identity == null) + { + HttpContext.Current.UmbracoLogout(); + return ""; + } + return identity.UserContextId; } set { - // zb-00004 #29956 : refactor cookies names & handling - if (StateHelper.Cookies.HasCookies) + if (value.IsNullOrWhiteSpace()) { - // Clearing all old cookies before setting a new one. - if (StateHelper.Cookies.UserContext.HasValue) - StateHelper.Cookies.ClearAll(); - - if (string.IsNullOrEmpty(value) == false) - { - // Encrypt the value - var encTicket = value.EncryptWithMachineKey(); - - // Create new cookie. - StateHelper.Cookies.UserContext.SetValue(encTicket, 1); + HttpContext.Current.UmbracoLogout(); + } + else + { + var uid = GetUserId(value); + if (uid == -1) + { + HttpContext.Current.UmbracoLogout(); } else { - StateHelper.Cookies.UserContext.Clear(); + var user = BusinessLogic.User.GetUser(uid); + HttpContext.Current.CreateUmbracoAuthTicket( + new UserData + { + Id = uid, + AllowedApplications = user.Applications.Select(x => x.alias).ToArray(), + Culture = ui.Culture(user), + RealName = user.Name, + Roles = new string[] {user.UserType.Alias}, + StartContentNode = user.StartNodeId, + StartMediaNode = user.StartMediaId, + UserContextId = value, + Username = user.LoginName + }); } } } diff --git a/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs b/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs index 870b1797b2..4fa4a3fea3 100644 --- a/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs +++ b/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs @@ -108,9 +108,6 @@ namespace umbraco.BasePages else Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?redir=" + Server.UrlEncode(Request.RawUrl), true); } - - System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(ui.Culture(this.getUser())); - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture; } } } \ No newline at end of file From 7e9cad34db0a1e745e7fe50e25bc2c191fb3418e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2013 16:01:54 +1000 Subject: [PATCH 05/10] Creates an SQL Server version check for bulk sql imports. --- src/Umbraco.Core/DatabaseContext.cs | 49 +++++++++++++++++++ .../Persistence/PetaPocoExtensions.cs | 5 +- .../SqlSyntax/SqlServerSyntaxProvider.cs | 19 +++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1311593e90..ed9b7c5a9d 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -303,6 +303,8 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); + + DetermineSqlServerVersion(); } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -339,6 +341,8 @@ namespace Umbraco.Core //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); + + DetermineSqlServerVersion(); } else { @@ -372,6 +376,49 @@ namespace Umbraco.Core Initialize(providerName); } + /// + /// Set the lazy resolution of determining the SQL server version if that is the db type we're using + /// + private void DetermineSqlServerVersion() + { + + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if (sqlServerSyntax != null) + { + //this will not execute now, it is lazy so will only execute when we need to actually know + // the sql server version. + sqlServerSyntax.VersionName = new Lazy(() => + { + try + { + var database = this._factory.CreateDatabase(); + + var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); + var firstPart = version.Split('.')[0]; + switch (firstPart) + { + case "11": + return SqlServerVersionName.V2012; + case "10": + return SqlServerVersionName.V2008; + case "9": + return SqlServerVersionName.V2005; + case "8": + return SqlServerVersionName.V2000; + case "7": + return SqlServerVersionName.V7; + default: + return SqlServerVersionName.Other; + } + } + catch (Exception) + { + return SqlServerVersionName.Invalid; + } + }); + } + } + internal DatabaseSchemaResult ValidateDatabaseSchema() { if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) @@ -463,6 +510,8 @@ namespace Umbraco.Core message = message + "

Upgrade completed!

"; } + //now that everything is done, we need to determine the version of SQL server that is executing + LogHelper.Info("Database configuration status: " + message); return new Result { Message = message, Success = true, Percentage = "100" }; diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index c962224689..eb10bf7f87 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -51,7 +51,10 @@ namespace Umbraco.Core.Persistence try { - if (SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) + //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + || SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { //SqlCe doesn't support bulk insert statements! diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 2de2209ab4..3e37674ddf 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -13,6 +13,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax public static ISqlSyntaxProvider Provider { get { return new SqlServerSyntaxProvider(); } } } + /// + /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) + /// + internal enum SqlServerVersionName + { + Invalid = -1, + V7 = 0, + V2000 = 1, + V2005 = 2, + V2008 = 3, + V2012 = 4, + Other = 5 + } + /// /// Represents an SqlSyntaxProvider for Sql Server /// @@ -36,6 +50,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax InitColumnTypeMap(); } + /// + /// Gets/sets the version of the current SQL server instance + /// + internal Lazy VersionName { get; set; } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); From b7a11233192100d18cb26cf43b9e5b5bddb7f99e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2013 16:23:32 +1000 Subject: [PATCH 06/10] U4-2579 BulkPublishController causes YSOD when there are invalid child elements due to InvalidProperties having a null reference --- src/Umbraco.Core/Publishing/PublishStatus.cs | 20 +++++++++++++------- src/Umbraco.Core/Services/ContentService.cs | 7 ++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index 415b758965..aee9a1fafe 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -8,15 +8,14 @@ namespace Umbraco.Core.Publishing ///
internal class PublishStatus { - public IContent ContentItem { get; private set; } - public PublishStatusType StatusType { get; internal set; } - - /// - /// Gets sets the invalid properties if the status failed due to validation. - /// - public IEnumerable InvalidProperties { get; set; } + public PublishStatus() + { + //initialize + InvalidProperties = new List(); + } public PublishStatus(IContent content, PublishStatusType statusType) + : this() { ContentItem = content; StatusType = statusType; @@ -29,6 +28,13 @@ namespace Umbraco.Core.Publishing : this(content, PublishStatusType.Success) { } + + public IContent ContentItem { get; private set; } + public PublishStatusType StatusType { get; internal set; } + /// + /// Gets sets the invalid properties if the status failed due to validation. + /// + public IEnumerable InvalidProperties { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3dbc03fab2..c4af37c34a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1460,7 +1460,12 @@ namespace Umbraco.Core.Services LogHelper.Info( string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", content.Name, content.Id)); - result.Add(new Attempt(false, new PublishStatus(content, PublishStatusType.FailedContentInvalid))); + result.Add( + new Attempt(false, + new PublishStatus(content, PublishStatusType.FailedContentInvalid) + { + InvalidProperties = ((ContentBase) content).LastInvalidProperties + })); return result; } From d78a03a6100fee7ce0bcaddf84b98235b28feb3b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 5 Aug 2013 13:11:47 +1000 Subject: [PATCH 07/10] Massively improves performance for the packaging service (converting docs to XML) which is used in republishing the tree. --- .gitignore | 2 + src/Umbraco.Core/Models/PropertyExtensions.cs | 21 +++- .../Models/PropertyTypeExtensions.cs | 8 +- .../Models/DataValueSetterTests.cs | 102 ++++++++++++++++++ .../Services/PackagingServiceTests.cs | 41 +++++++ .../TestHelpers/BaseDatabaseFactoryTest.cs | 11 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../businesslogic/datatype/DefaultData.cs | 25 ++++- src/umbraco.interfaces/IData.cs | 9 ++ .../Properties/AssemblyInfo.cs | 9 +- src/umbraco.sln | 8 ++ 12 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Tests/Models/DataValueSetterTests.cs create mode 100644 src/Umbraco.Tests/Services/PackagingServiceTests.cs diff --git a/.gitignore b/.gitignore index 67bcafd9ca..9496a86e70 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,5 @@ src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ build/_BuildOutput/ tools/NDepend/ +src/*.vspx +src/*.psess diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index b03f078a9f..ae01532c87 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -22,10 +22,15 @@ namespace Umbraco.Core.Models /// Xml of the property and its value public static XElement ToXml(this Property property) { - string nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); + return property.ToXml(ApplicationContext.Current.Services.DataTypeService); + } + + internal static XElement ToXml(this Property property, IDataTypeService dataTypeService) + { + var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); var xd = new XmlDocument(); - XmlNode xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); + var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); //Add the property alias to the legacy schema if (UmbracoSettings.UseLegacyXmlSchema) @@ -37,9 +42,17 @@ namespace Umbraco.Core.Models //This seems to fail during testing //SD: With the new null checks below, this shouldn't fail anymore. - var dt = property.PropertyType.DataType(property.Id); + var dt = property.PropertyType.DataType(property.Id, dataTypeService); if (dt != null && dt.Data != null) - { + { + //We've already got the value for the property so we're going to give it to the + // data type's data property so it doesn't go re-look up the value from the db again. + var defaultData = dt.Data as IDataValueSetter; + if (defaultData != null) + { + defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString()); + } + xmlNode.AppendChild(dt.Data.ToXMl(xd)); } diff --git a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs index b8ee04df33..304d7e7b9d 100644 --- a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs @@ -1,4 +1,5 @@ -using umbraco.interfaces; +using Umbraco.Core.Services; +using umbraco.interfaces; namespace Umbraco.Core.Models { @@ -9,6 +10,7 @@ namespace Umbraco.Core.Models /// /// PropertyType that references a DataType /// Id of the Property which references this DataType through its PropertyType + /// /// /// /// This extension method is left internal because we don't want to take @@ -16,10 +18,10 @@ namespace Umbraco.Core.Models /// be replaced by PropertyEditors. It is however needed to generate xml /// for a property/propertytype when publishing. /// - internal static IDataType DataType(this PropertyType propertyType, int propertyId) + internal static IDataType DataType(this PropertyType propertyType, int propertyId, IDataTypeService dataTypeService) { Mandate.ParameterNotNull(propertyType, "propertyType"); - var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeById(propertyType.DataTypeId); + var dataType = dataTypeService.GetDataTypeById(propertyType.DataTypeId); if (dataType == null) { return null; diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs new file mode 100644 index 0000000000..db59217166 --- /dev/null +++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; +using System.Xml; +using NUnit.Framework; +using Rhino.Mocks; +using Rhino.Mocks.Interfaces; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using umbraco.cms.businesslogic.datatype; +using umbraco.interfaces; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DataValueSetterTests : BaseUmbracoApplicationTest + { + protected override void FreezeResolution() + { + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper()); + base.FreezeResolution(); + } + + [Test] + public void LoadValueFromDatabase_Is_Not_Called_When_SetValue_Is_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + ((IDataValueSetter)dataTypeData).SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString()); + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasNotCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void LoadValueFromDatabase_Is_Called_When_SetValue_Is_Not_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData + .Stub(data => data.LoadValueFromDatabase()).WhenCalled(invocation => Debug.WriteLine("asdf")); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void SetValue_Is_Called_When_Executing_ToXml_On_A_Property_With_DataType_That_Implements_IDataValueSetter() + { + // Arrange + var dataTypeId = Guid.NewGuid(); + + var dataTypeData = MockRepository.GenerateMock(); + dataTypeData + .Stub(data => data.ToXMl(Arg.Is.Anything)) + .Return(null) // you have to call Return() even though we're about to override it + .WhenCalled(invocation => + { + var xmlDoc = (XmlDocument) invocation.Arguments[0]; + invocation.ReturnValue = xmlDoc.CreateElement("test"); + }); + + var dataType = MockRepository.GenerateStub(); + dataType.Stub(type => type.Data).Return(dataTypeData); + + var dataTypeSvc = MockRepository.GenerateStub(); + dataTypeSvc.Stub(service => service.GetDataTypeById(dataTypeId)).Return(dataType); + + var property = new Property( + 1234, + Guid.NewGuid(), + new PropertyType(dataTypeId, DataTypeDatabaseType.Nvarchar) + { + Alias = "test" + }, "Hello world"); + + // Act + + var xml = property.ToXml(dataTypeSvc); + + // Assert + + ((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString())); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs new file mode 100644 index 0000000000..abdfb2f9f3 --- /dev/null +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -0,0 +1,41 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + //[TestFixture] + //public class PackagingServiceTests : BaseServiceTest + //{ + // [Test] + // public void Export_Content() + // { + // var yesNo = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.TrueFalse)); + // var txtField = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.Textbox)); + + // var contentWithDataType = MockedContentTypes.CreateSimpleContentType( + // "test", + // "Test", + // new PropertyTypeCollection( + // new PropertyType[] + // { + // new PropertyType(new DataTypeDefinition(-1, txtField.Id) + // { + // Name = "Testing Textfield", DatabaseType = DataTypeDatabaseType.Ntext + // }), + // new PropertyType(new DataTypeDefinition(-1, yesNo.Id) + // { + // Name = "Testing intfield", DatabaseType = DataTypeDatabaseType.Integer + // }) + // })); + + // var content = MockedContent.CreateSimpleContent(contentWithDataType); + // content.Name = "Test"; + + // var exported = ServiceContext.PackagingService.Export(content); + + // } + //} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 5a98e303a4..b1a5b8b925 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -46,6 +46,8 @@ namespace Umbraco.Tests.TestHelpers //Used to flag if its the first test in the current fixture private bool _isFirstTestInFixture = false; + private ApplicationContext _appContext; + [SetUp] public override void Initialize() { @@ -59,7 +61,7 @@ namespace Umbraco.Tests.TestHelpers var dbFactory = new DefaultDatabaseFactory( GetDbConnectionString(), GetDbProviderName()); - ApplicationContext.Current = new ApplicationContext( + _appContext = new ApplicationContext( //assign the db context new DatabaseContext(dbFactory), //assign the service context @@ -79,7 +81,12 @@ namespace Umbraco.Tests.TestHelpers //ensure the configuration matches the current version for tests SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); } - + + protected override void SetupApplicationContext() + { + ApplicationContext.Current = _appContext; + } + /// /// The database behavior to use for the test/fixture /// diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index dbf87b4aee..d898ef6c5f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -25,6 +25,8 @@ namespace Umbraco.Tests.TestHelpers SetupPluginManager(); + SetupApplicationContext(); + FreezeResolution(); } @@ -40,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers ApplicationContext.Current = null; ResetPluginManager(); } - + /// /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 66764feae5..747cb52cee 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -185,9 +185,11 @@ + + diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index d72212aa5f..a974fda7c2 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -14,7 +14,7 @@ namespace umbraco.cms.businesslogic.datatype /// /// Default implementation of the IData interface that stores data inside the Umbraco database. /// - public class DefaultData : IData, IDataWithPreview + public class DefaultData : IData, IDataWithPreview, IDataValueSetter { private int _propertyId; private object _value; @@ -57,10 +57,29 @@ namespace umbraco.cms.businesslogic.datatype _value = InitValue; } + /// + /// This is here for performance reasons since in some cases we will have already resolved the value from the db + /// and want to just give this object the value so it doesn't go re-look it up from the database. + /// + /// + /// + void IDataValueSetter.SetValue(object val, string strDbType) + { + _value = val; + //now that we've set our value, we can update our BaseDataType object with the correct values from the db + //instead of making it query for itself. This is a peformance optimization enhancement. + var dbType = BaseDataType.GetDBType(strDbType); + var fieldName = BaseDataType.GetDataFieldName(dbType); + _dataType.SetDataTypeProperties(fieldName, dbType); + + //ensures that it doesn't go back to the db + _valueLoaded = true; + } + /// /// Loads the data value from the database. /// - protected virtual void LoadValueFromDatabase() + protected internal virtual void LoadValueFromDatabase() { var sql = new Sql(); sql.Select("*") @@ -243,5 +262,7 @@ namespace umbraco.cms.businesslogic.datatype } #endregion + + } } diff --git a/src/umbraco.interfaces/IData.cs b/src/umbraco.interfaces/IData.cs index c0580f0fee..17a374ff05 100644 --- a/src/umbraco.interfaces/IData.cs +++ b/src/umbraco.interfaces/IData.cs @@ -3,6 +3,15 @@ using System.Xml; namespace umbraco.interfaces { + /// + /// Internal interface used to decorate any IData that can be optimized when exporting + /// XML like in the packaging service. Instead of relying on the IData to go get the value + /// from the db, any IData that implements this can have it's value set from the packaging service. + /// + internal interface IDataValueSetter + { + void SetValue(object val, string strDbType); + } /// /// The IData is part of the IDataType interface for creating new data types in the umbraco backoffice. diff --git a/src/umbraco.interfaces/Properties/AssemblyInfo.cs b/src/umbraco.interfaces/Properties/AssemblyInfo.cs index 06f4dbdf64..709f231e00 100644 --- a/src/umbraco.interfaces/Properties/AssemblyInfo.cs +++ b/src/umbraco.interfaces/Properties/AssemblyInfo.cs @@ -9,4 +9,11 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("umbraco.interfaces")] [assembly: AssemblyDescription("Core assembly containing legacy interfaces")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] \ No newline at end of file +[assembly: AssemblyProduct("Umbraco CMS")] + +[assembly: InternalsVisibleTo("cms")] +[assembly: InternalsVisibleTo("Umbraco.Core")] +[assembly: InternalsVisibleTo("Umbraco.Tests")] + +//allow this to be mocked in our unit tests +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/umbraco.sln b/src/umbraco.sln index fee2486039..eba2ad7b9b 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -67,6 +67,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99794542-89EC-43BA-88BE-E31A9D61423B}" + ProjectSection(SolutionItems) = preProject + Performance1.psess = Performance1.psess + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -157,4 +162,7 @@ Global {73529637-28F5-419C-A6BB-D094E39DE614} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} {B555AAE6-0F56-442F-AC9F-EF497DB38DE7} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal From 226a13c6e14c0e241a9e8c7bcad0311464046b5d Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 5 Aug 2013 14:30:03 +0200 Subject: [PATCH 08/10] Minor adjustment related to U4-2560 Working with ContentService can give 2 versions of a document as "newest" --- .../Repositories/ContentRepository.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index e2c981f62d..921a7e75dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -338,19 +338,22 @@ namespace Umbraco.Core.Persistence.Repositories } } + //Look up (newest) entries by id in cmsDocument table to set newest = false + //NOTE: This should only be done for all other versions then the current one, so we don't cause the same entry to be updated multiple times. + var documentDtos = + Database.Query( + "WHERE nodeId = @Id AND newest = @IsNewest AND NOT(versionId = @VersionId)", + new {Id = entity.Id, IsNewest = true, VersionId = dto.ContentVersionDto.VersionId}); + foreach (var documentDto in documentDtos) + { + var docDto = documentDto; + docDto.Newest = false; + Database.Update(docDto); + } + var contentVersionDto = dto.ContentVersionDto; if (shouldCreateNewVersion) { - //Look up (newest) entries by id in cmsDocument table to set newest = false - //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) - { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); - } - //Create a new version - cmsContentVersion //Assumes a new Version guid and Version date (modified date) has been set Database.Insert(contentVersionDto); From d47b4517c106f50e8d53e56a7e83daee599577fa Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 6 Aug 2013 13:06:25 +1000 Subject: [PATCH 09/10] Fixes unit tests --- src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index b1a5b8b925..6152898f92 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -52,9 +52,7 @@ namespace Umbraco.Tests.TestHelpers public override void Initialize() { InitializeFirstRunFlags(); - - base.Initialize(); - + var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); @@ -72,6 +70,8 @@ namespace Umbraco.Tests.TestHelpers IsReady = true }; + base.Initialize(); + DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); CreateSqlCeDatabase(); From b11932240ca1a25a6f405f9afd6c0fe188f713fb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 6 Aug 2013 10:24:20 +0200 Subject: [PATCH 10/10] Fixes U4-2587 Regression: Hide Preview Button on nodes that have no template Now also checks if the template still exists and if it's allowed on that document to determine when to enable the preview button --- .../umbraco/editContent.aspx.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs index ab8579db27..91c6a8a4e0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs @@ -249,7 +249,7 @@ namespace umbraco.cms.presentation //update UI and set document properties PerformSaveLogic(); - + //persist the document _document.Save(); @@ -257,7 +257,7 @@ namespace umbraco.cms.presentation BusinessLogic.Actions.Action.RunActionHandlers(_document, ActionUpdate.Instance); ClientTools.ShowSpeechBubble( - speechBubbleIcon.save, ui.Text("speechBubbles", "editContentSavedHeader", null), + speechBubbleIcon.save, ui.Text("speechBubbles", "editContentSavedHeader", null), ui.Text("speechBubbles", "editContentSavedText", null)); ClientTools.SyncTree(_document.Path, true); @@ -291,7 +291,7 @@ namespace umbraco.cms.presentation { //update UI and set document properties PerformSaveLogic(); - + //the business logic here will check to see if the doc can actually be published and will return the // appropriate result so we can display the correct error messages (or success). var savePublishResult = _document.SaveAndPublishWithResult(UmbracoUser); @@ -309,7 +309,7 @@ namespace umbraco.cms.presentation _documentHasPublishedVersion = _document.Content.HasPublishedVersion(); } - + ClientTools.SyncTree(_document.Path, true); } @@ -320,8 +320,8 @@ namespace umbraco.cms.presentation case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: ClientTools.ShowSpeechBubble( - speechBubbleIcon.save, - ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), + speechBubbleIcon.save, + ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), ui.Text("speechBubbles", "editContentPublishedText", UmbracoUser)); break; case PublishStatusType.FailedPathNotPublished: @@ -550,23 +550,40 @@ namespace umbraco.cms.presentation } menuItem.ImageURL = SystemDirectories.Umbraco + "/images/editor/vis.gif"; - // Fix for U4-682, if there's no template, disable the preview button - if (_document.Template != -1) + + if (EnablePreviewButton()) { menuItem.AltText = ui.Text("buttons", "showPage", UmbracoUser); menuItem.OnClickCommand = "window.open('dialogs/preview.aspx?id=" + id + "','umbPreview')"; } else { - string showPageDisabledText = ui.Text("buttons", "showPageDisabled", UmbracoUser); + var showPageDisabledText = ui.Text("buttons", "showPageDisabled", UmbracoUser); if (showPageDisabledText.StartsWith("[")) - showPageDisabledText = ui.GetText("buttons", "showPageDisabled", null, "en"); ; + showPageDisabledText = ui.GetText("buttons", "showPageDisabled", null, "en"); menuItem.AltText = showPageDisabledText; - ((Image)menuItem).Attributes.Add("style", "opacity: 0.5"); + ((Image) menuItem).Attributes.Add("style", "opacity: 0.5"); } } + private bool EnablePreviewButton() + { + // Fix for U4-862, if there's no template, disable the preview button + // Fixed again for U4-2587, apparently at some point "no template" changed from -1 to 0? -SJ + // Now also catches when template doesn't exist any more or is not allowed any more + // Don't think there's a better way to check if the template exists besides trying to instantiate it.. + try + { + var template = new businesslogic.template.Template(_document.Template); + // If template is found check if it's in the list of allowed templates for this document + return _document.Content.ContentType.AllowedTemplates.ToList().Any(t => t.Id == template.Id); + } + catch (Exception) { } + + return false; + } + /// /// JsInclude1 control. ///