diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 52b3533bc7..9140ee03f2 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -171,6 +171,11 @@ namespace Umbraco.Core public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; + /// + /// Group name to put the membership properties on + /// + internal const string StandardPropertiesGroupName = "Membership"; + internal static Dictionary GetStandardPropertyTypeStubs() { return new Dictionary diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 6a54bddc70..d9f41b84d9 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -145,12 +145,22 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.PasswordQuestion) == false) + { + return default(string); + } + return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null ? string.Empty : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); } set { + if (Properties.Contains(Constants.Conventions.Member.PasswordQuestion) == false) + { + return; + } + Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; } } @@ -167,12 +177,22 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.PasswordAnswer) == false) + { + return default(string); + } + return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null ? string.Empty : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); } set { + if (Properties.Contains(Constants.Conventions.Member.PasswordAnswer) == false) + { + return; + } + Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; } } @@ -189,12 +209,22 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.Comments) == false) + { + return default(string); + } + return Properties[Constants.Conventions.Member.Comments].Value == null ? string.Empty : Properties[Constants.Conventions.Member.Comments].Value.ToString(); } set { + if (Properties.Contains(Constants.Conventions.Member.Comments) == false) + { + return; + } + Properties[Constants.Conventions.Member.Comments].Value = value; } } @@ -211,6 +241,12 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.IsApproved) == false) + { + //I guess we'll leave them approved by default! + return true; + } + var tryConvert = Properties[Constants.Conventions.Member.IsApproved].Value.TryConvertTo(); if (tryConvert.Success) { @@ -221,6 +257,11 @@ namespace Umbraco.Core.Models } set { + if (Properties.Contains(Constants.Conventions.Member.IsApproved) == false) + { + return; + } + Properties[Constants.Conventions.Member.IsApproved].Value = value; } } @@ -237,6 +278,12 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.IsLockedOut) == false) + { + //I guess we'll not lock them out by default! + return false; + } + var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].Value.TryConvertTo(); if (tryConvert.Success) { @@ -247,6 +294,11 @@ namespace Umbraco.Core.Models } set { + if (Properties.Contains(Constants.Conventions.Member.IsLockedOut) == false) + { + return; + } + Properties[Constants.Conventions.Member.IsLockedOut].Value = value; } } @@ -263,6 +315,11 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.LastLoginDate) == false) + { + return default(DateTime); + } + var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -273,6 +330,11 @@ namespace Umbraco.Core.Models } set { + if (Properties.Contains(Constants.Conventions.Member.LastLoginDate) == false) + { + return; + } + Properties[Constants.Conventions.Member.LastLoginDate].Value = value; } } @@ -289,6 +351,11 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.LastPasswordChangeDate) == false) + { + return default(DateTime); + } + var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -299,6 +366,11 @@ namespace Umbraco.Core.Models } set { + if (Properties.Contains(Constants.Conventions.Member.LastPasswordChangeDate) == false) + { + return; + } + Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; } } @@ -315,6 +387,11 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.LastLockoutDate) == false) + { + return default(DateTime); + } + var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].Value.TryConvertTo(); if (tryConvert.Success) { @@ -325,6 +402,11 @@ namespace Umbraco.Core.Models } set { + if (Properties.Contains(Constants.Conventions.Member.LastLockoutDate) == false) + { + return; + } + Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; } } @@ -342,6 +424,11 @@ namespace Umbraco.Core.Models { get { + if (Properties.Contains(Constants.Conventions.Member.FailedPasswordAttempts) == false) + { + return 0; + } + var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value.TryConvertTo(); if (tryConvert.Success) { @@ -352,7 +439,12 @@ namespace Umbraco.Core.Models } set { - Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + if (Properties.Contains(Constants.Conventions.Member.FailedPasswordAttempts) == false) + { + return; + } + + Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value = value; } } diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index aff816515d..0c8ab00eda 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { @@ -25,7 +26,7 @@ namespace Umbraco.Core.Persistence.Querying { //if (TypeSerializer.CanCreateFromString(fieldType)) //{ - // return "'" + EscapeParam(TypeSerializer.SerializeToString(value)) + "'"; + // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; //} throw new NotSupportedException( @@ -46,27 +47,24 @@ namespace Umbraco.Core.Persistence.Querying if (fieldType == typeof(DateTime)) { - return "'" + EscapeParam(((DateTime)value).ToIsoString()) + "'"; + return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; } if (fieldType == typeof(bool)) return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); - return ShouldQuoteValue(fieldType) - ? "'" + EscapeParam(value) + "'" + return shouldQuoteCallback(fieldType) + ? "'" + escapeCallback(value) + "'" : value.ToString(); } public virtual string EscapeParam(object paramValue) { - return paramValue.ToString().Replace("'", "''"); + return paramValue == null + ? string.Empty + : SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString()); } - - public virtual string EscapeAtArgument(string exp) - { - return PetaPocoExtensions.EscapeAtSymbols(exp); - } - + public virtual bool ShouldQuoteValue(Type fieldType) { return true; diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 4dba620ab0..6ab3fcc592 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -205,13 +205,12 @@ namespace Umbraco.Core.Persistence.Querying { if (c.Value == null) return "null"; - else if (c.Value.GetType() == typeof(bool)) + if (c.Value is bool) { object o = GetQuotedValue(c.Value, c.Value.GetType()); return string.Format("({0}={1})", GetQuotedTrueValue(), o); } - else - return GetQuotedValue(c.Value, c.Value.GetType()); + return GetQuotedValue(c.Value, c.Value.GetType()); } protected virtual string VisitUnary(UnaryExpression u) @@ -233,15 +232,15 @@ namespace Umbraco.Core.Persistence.Querying switch (verb) { case "SqlWildcard": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, RemoveQuote(val), columnType); case "Equals": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, RemoveQuote(val), columnType); case "StartsWith": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, RemoveQuote(val), columnType); case "EndsWith": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, RemoveQuote(val), columnType); case "Contains": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, RemoveQuote(val), columnType); case "InvariantEquals": case "SqlEquals": //recurse diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index 6c2f7e6727..ecd5bb0087 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -254,11 +254,11 @@ namespace Umbraco.Core.Persistence.Querying case "ToLower": return string.Format("lower({0})", r); case "StartsWith": - return string.Format("upper({0}) like '{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString().ToUpper()))); + return string.Format("upper({0}) like '{1}%'", r, RemoveQuote(args[0].ToString().ToUpper())); case "EndsWith": - return string.Format("upper({0}) like '%{1}'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); + return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Contains": - return string.Format("upper({0}) like '%{1}%'", r, EscapeAtArgument(RemoveQuote(args[0].ToString()).ToUpper())); + return string.Format("upper({0}) like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); case "Substring": var startIndex = Int32.Parse(args[0].ToString()) + 1; if (args.Count == 2) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 08bed74923..aecbe77a96 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -168,10 +168,11 @@ namespace Umbraco.Core.Persistence.Repositories ((MemberType)entity).AddingEntity(); //By Convention we add 9 stnd PropertyTypes to an Umbraco MemberType + entity.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupName); var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); foreach (var standardPropertyType in standardPropertyTypes) { - entity.AddPropertyType(standardPropertyType.Value); + entity.AddPropertyType(standardPropertyType.Value, Constants.Conventions.Member.StandardPropertiesGroupName); } var factory = new MemberTypeFactory(NodeObjectTypeId); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 2b31fb2032..c62fa1a923 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public interface ISqlSyntaxProvider { + string EscapeString(string val); + string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 5f5d412ab3..0b8d80a21f 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -327,5 +327,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax // add message to check with their hosting provider return supportsCaseInsensitiveQueries; } + + public override string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 8f9a84437c..004aabfc70 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -103,6 +103,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); } + public virtual string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); + } + public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bca0b0adce..1367cdcca3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -58,10 +58,15 @@ ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - + + False + ..\packages\MySql.Data.6.6.5\lib\net40\MySql.Data.dll + + False ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index cc673f88a1..c2c45f4195 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index c0316e979e..ba1592635f 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -2,7 +2,10 @@ using System.Linq.Expressions; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence.Querying @@ -35,5 +38,60 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual("[umbracoNode].[parentID] = -1", result); } + + [Test] + public void Equals_Operator_For_Value_Gets_Escaped() + { + Expression> predicate = user => user.Username == "hello@world.com"; + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("[umbracoUser].[userLogin] = 'hello@@world.com'", result); + } + + [Test] + public void Equals_Method_For_Value_Gets_Escaped() + { + Expression> predicate = user => user.Username.Equals("hello@world.com"); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("upper([umbracoUser].[userLogin]) = 'HELLO@@WORLD.COM'", result); + } + + [Test] + public void Model_Expression_Value_Does_Not_Get_Double_Escaped() + { + //mysql escapes backslashes, so we'll test with that + SqlSyntaxContext.SqlSyntaxProvider = MySqlSyntax.Provider; + + Expression> predicate = user => user.Username.Equals("mydomain\\myuser"); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = 'MYDOMAIN\\\\MYUSER'", result); + } + + [Test] + public void Poco_Expression_Value_Does_Not_Get_Double_Escaped() + { + //mysql escapes backslashes, so we'll test with that + SqlSyntaxContext.SqlSyntaxProvider = MySqlSyntax.Provider; + + Expression> predicate = user => user.Login.StartsWith("mydomain\\myuser"); + var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("upper(`umbracoUser`.`userLogin`) like 'MYDOMAIN\\\\MYUSER%'", result); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 3bd9c2001f..bb9cd553a9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -224,10 +224,17 @@ namespace Umbraco.Tests.Persistence.Repositories var sut = repository.Get(member.Id); - Assert.That(sut.ContentType.PropertyGroups.Count(), Is.EqualTo(1)); + Assert.That(sut.ContentType.PropertyGroups.Count(), Is.EqualTo(2)); Assert.That(sut.ContentType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); Assert.That(sut.Properties.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count)); Assert.That(sut.Properties.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); + var grp = sut.PropertyGroups.FirstOrDefault(x => x.Name == Constants.Conventions.Member.StandardPropertiesGroupName); + Assert.IsNotNull(grp); + var aliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + foreach (var p in sut.PropertyTypes.Where(x => aliases.Contains(x.Alias))) + { + Assert.AreEqual(grp.Id, p.PropertyGroupId.Value); + } } } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index f247296790..96238cff03 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -160,7 +160,7 @@ False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - + False ..\packages\MySql.Data.6.6.5\lib\net40\MySql.Data.dll diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 1c576e7e4e..10b06e9f93 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - +