diff --git a/.editorconfig b/.editorconfig
index e99299d699..208052cbb7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,7 +3,7 @@ root=true
[*]
end_of_line = lf
indent_style = space
-indent_size = 2
+indent_size = 4
trim_trailing_whitespace = true
[*.{cs,cshtml,csx,vb,vbx,vbhtml,fs,fsx,txt,ps1,sql}]
diff --git a/.gitignore b/.gitignore
index 18239100ad..7f7fd2bf6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -143,5 +143,6 @@ build/csharp-docs.zip
build/msbuild.log
.vs/
src/packages/
+src/PrecompiledWeb/*
build/tools/
src/PrecompiledWeb/*
diff --git a/build/Build.bat b/build/Build.bat
index f26c4877cd..da34691c3d 100644
--- a/build/Build.bat
+++ b/build/Build.bat
@@ -125,22 +125,12 @@ IF NOT EXIST "%nuGetExecutable%" (
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '%nuGetExecutable%')"
)
-:: We need 7za.exe for BuildBelle.bat
-IF NOT EXIST "%toolsFolder%7za.exe" (
- ECHO 7zip not found - fetching now
- "%nuGetExecutable%" install 7-Zip.CommandLine -OutputDirectory tools -Verbosity quiet
-)
-
:: We need vswhere.exe for VS2017+
IF NOT EXIST "%toolsFolder%vswhere.exe" (
ECHO vswhere not found - fetching now
"%nuGetExecutable%" install vswhere -OutputDirectory tools -Verbosity quiet
)
-:: Put 7za.exe and vswhere.exe in a predictable path (not version specific)
-FOR /f "delims=" %%A in ('dir "%toolsFolder%7-Zip.CommandLine.*" /b') DO SET "sevenZipExePath=%toolsFolder%%%A\"
-MOVE "%sevenZipExePath%tools\7za.exe" "%toolsFolder%7za.exe"
-
FOR /f "delims=" %%A in ('dir "%toolsFolder%vswhere.*" /b') DO SET "vswhereExePath=%toolsFolder%%%A\"
MOVE "%vswhereExePath%tools\vswhere.exe" "%toolsFolder%vswhere.exe"
diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat
index 78b7736c2b..750ffae6b2 100644
--- a/build/BuildBelle.bat
+++ b/build/BuildBelle.bat
@@ -8,6 +8,22 @@ ECHO Current folder: %CD%
SET nodeFileName=node-v6.9.1-win-x86.7z
SET nodeExtractFolder=%toolsFolder%node.js.691
+SET nuGetExecutable=%CD%\tools\nuget.exe
+IF NOT EXIST "%nuGetExecutable%" (
+ ECHO Downloading https://dist.nuget.org/win-x86-commandline/latest/nuget.exe to %nuGetExecutable%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '%nuGetExecutable%')"
+)
+
+:: We need 7za.exe for BuildBelle.bat
+IF NOT EXIST "%toolsFolder%7za.exe" (
+ ECHO 7zip not found - fetching now
+ "%nuGetExecutable%" install 7-Zip.CommandLine -OutputDirectory tools -Verbosity quiet
+)
+
+:: Put 7za.exe and vswhere.exe in a predictable path (not version specific)
+FOR /f "delims=" %%A in ('dir "%toolsFolder%7-Zip.CommandLine.*" /b') DO SET "sevenZipExePath=%toolsFolder%%%A\"
+MOVE "%sevenZipExePath%tools\7za.exe" "%toolsFolder%7za.exe"
+
IF NOT EXIST "%nodeExtractFolder%" (
ECHO Downloading http://nodejs.org/dist/v6.9.1/%nodeFileName% to %toolsFolder%%nodeFileName%
powershell -Command "(New-Object Net.WebClient).DownloadFile('http://nodejs.org/dist/v6.9.1/%nodeFileName%', '%toolsFolder%%nodeFileName%')"
@@ -16,13 +32,6 @@ IF NOT EXIST "%nodeExtractFolder%" (
)
FOR /f "delims=" %%A in ('dir "%nodeExtractFolder%\node*" /b') DO SET "nodePath=%nodeExtractFolder%\%%A"
-
-SET nuGetExecutable=%CD%\tools\nuget.exe
-IF NOT EXIST "%nuGetExecutable%" (
- ECHO Downloading https://dist.nuget.org/win-x86-commandline/latest/nuget.exe to %nuGetExecutable%
- powershell -Command "(New-Object Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '%nuGetExecutable%')"
-)
-
SET drive=%CD:~0,2%
SET nuGetFolder=%drive%\packages\
FOR /f "delims=" %%A in ('dir "%nuGetFolder%npm.*" /b') DO SET "npmPath=%nuGetFolder%%%A\"
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 8fb7cdf2dc..e42b8cf237 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -21,9 +21,9 @@
-
-
-
+
+
+
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index 2ef6d38009..315fbf8890 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -370,19 +370,19 @@
-
+
-
+
-
+
-
+
diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt
index 7d41835fb1..65aa9c2b53 100644
--- a/build/NuSpecs/tools/trees.config.install.xdt
+++ b/build/NuSpecs/tools/trees.config.install.xdt
@@ -102,15 +102,16 @@
xdt:Transform="Remove" />
-
+
+
-
+
-
+ xdt:Transform="Remove" />
-
+
-
+
-
+
-
+
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index fe3be21eb7..faf7c348e2 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyFileVersion("7.6.4")]
-[assembly: AssemblyInformationalVersion("7.6.4")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("7.7.0")]
+[assembly: AssemblyInformationalVersion("7.7.0-beta001")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 81fe4dc2f2..c9991ba45a 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -55,8 +55,10 @@ namespace Umbraco.Core.Cache
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
[EditorBrowsable(EditorBrowsableState.Never)]
public const string UserCacheKey = "UmbracoUser";
-
- public const string UserPermissionsCacheKey = "UmbracoUserPermissions";
+
+ [Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public const string UserGroupPermissionsCacheKey = "UmbracoUserGroupPermissions";
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypeCacheKey = "UmbracoContentType";
diff --git a/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs b/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs
new file mode 100644
index 0000000000..697c6b7652
--- /dev/null
+++ b/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Umbraco.Core.CodeAnnotations
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ internal class ActionMetadataAttribute : Attribute
+ {
+ public string Category { get; private set; }
+ public string Name { get; private set; }
+
+ ///
+ /// Constructor used to assign a Category, since no name is assigned it will try to be translated from the language files based on the action's alias
+ ///
+ ///
+ public ActionMetadataAttribute(string category)
+ {
+ if (string.IsNullOrWhiteSpace(category)) throw new ArgumentException("Value cannot be null or whitespace.", "category");
+ Category = category;
+ }
+
+ ///
+ /// Constructor used to assign an explicit name and category
+ ///
+ ///
+ ///
+ public ActionMetadataAttribute(string category, string name)
+ {
+ if (string.IsNullOrWhiteSpace(category)) throw new ArgumentException("Value cannot be null or whitespace.", "category");
+ if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
+ Category = category;
+ Name = name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index acbf0065c0..02f3322ec9 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
+using System.Net.Configuration;
using System.Web;
using System.Web.Configuration;
using System.Web.Hosting;
@@ -42,7 +43,6 @@ namespace Umbraco.Core.Configuration
//ensure the built on (non-changeable) reserved paths are there at all times
private const string StaticReservedPaths = "~/app_plugins/,~/install/,";
private const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,";
-
#endregion
///
@@ -53,6 +53,7 @@ namespace Umbraco.Core.Configuration
_reservedUrlsCache = null;
_reservedPaths = null;
_reservedUrls = null;
+ HasSmtpServer = null;
}
///
@@ -64,7 +65,26 @@ namespace Umbraco.Core.Configuration
ResetInternal();
}
- ///
+ public static bool HasSmtpServerConfigured(string appPath)
+ {
+ if (HasSmtpServer.HasValue) return HasSmtpServer.Value;
+
+ var config = WebConfigurationManager.OpenWebConfiguration(appPath);
+ var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings");
+ if (settings == null || settings.Smtp == null) return false;
+ if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false)
+ return true;
+ if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false)
+ return true;
+ return false;
+ }
+
+ ///
+ /// For testing only
+ ///
+ internal static bool? HasSmtpServer { get; set; }
+
+ ///
/// Gets the reserved urls from web.config.
///
/// The reserved urls.
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs
index cc4d459359..eb1e452100 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs
@@ -1,8 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
internal class HelpElement : ConfigurationElement, IHelpSection
{
[ConfigurationProperty("defaultUrl", DefaultValue = "http://our.umbraco.org/wiki/umbraco-help/{0}/{1}")]
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs
index 61be2dfaf2..dc9b6af437 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs
@@ -1,7 +1,11 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
namespace Umbraco.Core.Configuration.UmbracoSettings
-{
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
public interface IHelpSection : IUmbracoConfigurationSection
{
string DefaultUrl { get; }
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs
index d2afec55f3..689878cf0d 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs
@@ -1,5 +1,10 @@
-namespace Umbraco.Core.Configuration.UmbracoSettings
+using System;
+using System.ComponentModel;
+
+namespace Umbraco.Core.Configuration.UmbracoSettings
{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
public interface ILink
{
string Application { get; }
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs
index 899de7d1f9..cd4db216ab 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs
@@ -1,4 +1,7 @@
-namespace Umbraco.Core.Configuration.UmbracoSettings
+using System;
+using System.ComponentModel;
+
+namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IUmbracoSettingsSection : IUmbracoConfigurationSection
{
@@ -24,6 +27,8 @@
IProvidersSection Providers { get; }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
IHelpSection Help { get; }
IWebRoutingSection WebRouting { get; }
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs
index 31b4aa3e93..ad1655dfb0 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs
@@ -1,7 +1,11 @@
-using System.Configuration;
+using System;
+using System.ComponentModel;
+using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
internal class LinkElement : ConfigurationElement, ILink
{
[ConfigurationProperty("application")]
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs
index 5c317790cb..485c8e4bbd 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs
@@ -1,8 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
internal class LinksCollection : ConfigurationElementCollection, IEnumerable
{
protected override ConfigurationElement CreateNewElement()
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs
index dd6fed5cd5..2c4751f580 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Configuration;
using System.Linq;
@@ -183,6 +184,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return Providers; }
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This is no longer used and will be removed in future versions")]
IHelpSection IUmbracoSettingsSection.Help
{
get { return Help; }
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index 85b9bf3685..707c241074 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
- private static readonly Version Version = new Version("7.6.4");
+ private static readonly Version Version = new Version("7.7.0");
///
/// Gets the current version of Umbraco.
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
///
/// The version comment.
- public static string CurrentComment { get { return ""; } }
+ public static string CurrentComment { get { return "beta001"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index d98c12aeb1..3911a6d204 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -123,6 +123,8 @@
public const string Scripts = "scripts";
+ public const string Users = "users";
+
//TODO: Fill in the rest!
}
}
diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index 2e3d652d7e..b59b7e487e 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -12,6 +12,14 @@ namespace Umbraco.Core
///
public static class Conventions
{
+ internal static class PermissionCategories
+ {
+ public const string ContentCategory = "content";
+ public const string AdministrationCategory = "administration";
+ public const string StructureCategory = "structure";
+ public const string OtherCategory = "other";
+ }
+
public static class PublicAccess
{
public const string MemberUsernameRuleType = "MemberUsername";
diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs
index bd2e1c5acf..83059dbcbb 100644
--- a/src/Umbraco.Core/Constants-Security.cs
+++ b/src/Umbraco.Core/Constants-Security.cs
@@ -8,12 +8,17 @@ namespace Umbraco.Core
public static class Security
{
+ public const string AdminGroupAlias = "admin";
+
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
+ internal const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__";
+ internal const string ForceReAuthFlag = "umbraco-force-auth";
+
///
/// The prefix used for external identity providers for their authentication type
///
diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs
index e8565f3bc7..79a703ca5f 100644
--- a/src/Umbraco.Core/EnumerableExtensions.cs
+++ b/src/Umbraco.Core/EnumerableExtensions.cs
@@ -111,6 +111,9 @@ namespace Umbraco.Core
///
public static bool ContainsAll(this IEnumerable source, IEnumerable other)
{
+ if (source == null) throw new ArgumentNullException("source");
+ if (other == null) throw new ArgumentNullException("other");
+
return other.Except(source).Any() == false;
}
diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs
index c34c4591b8..75dd63a0df 100644
--- a/src/Umbraco.Core/ExpressionHelper.cs
+++ b/src/Umbraco.Core/ExpressionHelper.cs
@@ -217,8 +217,21 @@ namespace Umbraco.Core
public static MemberInfo GetMemberInfo(Expression> fromExpression)
{
if (fromExpression == null) return null;
- var body = fromExpression.Body as MemberExpression;
- return body != null ? body.Member : null;
+
+ MemberExpression me;
+ switch (fromExpression.Body.NodeType)
+ {
+ case ExpressionType.Convert:
+ case ExpressionType.ConvertChecked:
+ var ue = fromExpression.Body as UnaryExpression;
+ me = ((ue != null) ? ue.Operand : null) as MemberExpression;
+ break;
+ default:
+ me = fromExpression.Body as MemberExpression;
+ break;
+ }
+
+ return me != null ? me.Member : null;
}
///
diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs
index df922fb2b0..d2ae8a0612 100644
--- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs
+++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs
@@ -19,6 +19,8 @@ namespace Umbraco.Core.IO
private ShadowWrapper _macroPartialFileSystem;
private ShadowWrapper _partialViewsFileSystem;
+ private ShadowWrapper _macroScriptsFileSystem;
+ private ShadowWrapper _userControlsFileSystem;
private ShadowWrapper _stylesheetsFileSystem;
private ShadowWrapper _scriptsFileSystem;
private ShadowWrapper _xsltFileSystem;
@@ -61,6 +63,8 @@ namespace Umbraco.Core.IO
{
var macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials);
var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews);
+ var macroScriptsFileSystem = new PhysicalFileSystem(SystemDirectories.MacroScripts);
+ var userControlsFileSystem = new PhysicalFileSystem(SystemDirectories.UserControls);
var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css);
var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
@@ -69,6 +73,8 @@ namespace Umbraco.Core.IO
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider);
+ _macroScriptsFileSystem = new ShadowWrapper(macroScriptsFileSystem, "macroScripts", ScopeProvider);
+ _userControlsFileSystem = new ShadowWrapper(userControlsFileSystem, "usercontrols", ScopeProvider);
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", ScopeProvider);
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", ScopeProvider);
_xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider);
@@ -85,6 +91,10 @@ namespace Umbraco.Core.IO
public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } }
public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } }
+ // Legacy /macroScripts folder
+ public IFileSystem2 MacroScriptsFileSystem { get { return _macroScriptsFileSystem; } }
+ // Legacy /usercontrols folder
+ public IFileSystem2 UserControlsFileSystem { get { return _userControlsFileSystem; } }
public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } }
public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } }
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
@@ -252,13 +262,15 @@ namespace Umbraco.Core.IO
internal ICompletable Shadow(Guid id)
{
var typed = _wrappers.ToArray();
- var wrappers = new ShadowWrapper[typed.Length + 7];
+ var wrappers = new ShadowWrapper[typed.Length + 9];
var i = 0;
while (i < typed.Length) wrappers[i] = typed[i++];
wrappers[i++] = _macroPartialFileSystem;
+ wrappers[i++] = _macroScriptsFileSystem;
wrappers[i++] = _partialViewsFileSystem;
wrappers[i++] = _stylesheetsFileSystem;
wrappers[i++] = _scriptsFileSystem;
+ wrappers[i++] = _userControlsFileSystem;
wrappers[i++] = _xsltFileSystem;
wrappers[i++] = _masterPagesFileSystem;
wrappers[i] = _mvcViewsFileSystem;
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index cef06ec4f7..70cb877183 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Models
private DateTime? _expireDate;
private int _writer;
private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name.
- private bool _permissionsChanged;
+
///
/// Constructor for creating a Content object
///
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Models
/// ContentType for the current Content object
public Content(string name, IContent parent, IContentType contentType)
: this(name, parent, contentType, new PropertyCollection())
- {
+ {
}
///
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Models
/// Id of the Parent content
/// ContentType for the current Content object
/// Collection of properties
- public Content(string name, int parentId, IContentType contentType, PropertyCollection properties)
+ public Content(string name, int parentId, IContentType contentType, PropertyCollection properties)
: base(name, parentId, contentType, properties)
{
Mandate.ParameterNotNull(contentType, "contentType");
@@ -87,7 +87,6 @@ namespace Umbraco.Core.Models
public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate);
public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId);
public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName);
- public readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo(x => x.PermissionsChanged);
}
///
@@ -95,7 +94,7 @@ namespace Umbraco.Core.Models
/// This is used to override the default one from the ContentType.
///
///
- /// If no template is explicitly set on the Content object,
+ /// If no template is explicitly set on the Content object,
/// the Default template from the ContentType will be returned.
///
[DataMember]
@@ -197,15 +196,6 @@ namespace Umbraco.Core.Models
set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); }
}
- ///
- /// Used internally to track if permissions have been changed during the saving process for this entity
- ///
- [IgnoreDataMember]
- internal bool PermissionsChanged
- {
- get { return _permissionsChanged; }
- set { SetPropertyValueAndDetectChanges(value, ref _permissionsChanged, Ps.Value.PermissionsChangedSelector); }
- }
///
/// Gets the ContentType used by this content object
@@ -293,7 +283,7 @@ namespace Umbraco.Core.Models
ChangePublishedState(PublishedState.Unpublished);
}
}
-
+
///
/// Method to call when Entity is being updated
///
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 88c498a147..81b5272a58 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -54,6 +54,11 @@ namespace Umbraco.Core.Models
{
public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId);
public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates);
+
+ //Custom comparer for enumerable
+ public readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>(
+ (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
+ templates => templates.GetHashCode());
}
///
@@ -91,10 +96,7 @@ namespace Umbraco.Core.Models
set
{
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector,
- //Custom comparer for enumerable
- new DelegateEqualityComparer>(
- (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
- templates => templates.GetHashCode()));
+ Ps.Value.TemplateComparer);
}
}
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index 88476f946d..db0bc0e900 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -88,6 +88,12 @@ namespace Umbraco.Core.Models
public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups);
public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes);
public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved);
+
+ //Custom comparer for enumerable
+ public readonly DelegateEqualityComparer> ContentTypeSortComparer =
+ new DelegateEqualityComparer>(
+ (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable),
+ sorts => sorts.GetHashCode());
}
@@ -254,7 +260,7 @@ namespace Umbraco.Core.Models
set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); }
}
- private IDictionary _additionalData;
+ private readonly IDictionary _additionalData;
///
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
///
@@ -273,11 +279,8 @@ namespace Umbraco.Core.Models
get { return _allowedContentTypes; }
set
{
- SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector,
- //Custom comparer for enumerable
- new DelegateEqualityComparer>(
- (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable),
- sorts => sorts.GetHashCode()));
+ SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector,
+ Ps.Value.ContentTypeSortComparer);
}
}
diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs
index 42b047e35b..d5fcc89994 100644
--- a/src/Umbraco.Core/Models/DictionaryItem.cs
+++ b/src/Umbraco.Core/Models/DictionaryItem.cs
@@ -37,6 +37,12 @@ namespace Umbraco.Core.Models
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId);
public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey);
public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations);
+
+ //Custom comparer for enumerable
+ public readonly DelegateEqualityComparer> DictionaryTranslationComparer =
+ new DelegateEqualityComparer>(
+ (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
+ enumerable => enumerable.GetHashCode());
}
///
@@ -79,10 +85,7 @@ namespace Umbraco.Core.Models
}
SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector,
- //Custom comparer for enumerable
- new DelegateEqualityComparer>(
- (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
- enumerable => enumerable.GetHashCode()));
+ Ps.Value.DictionaryTranslationComparer);
}
}
}
diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs
index d4da2676c1..3e7770088b 100644
--- a/src/Umbraco.Core/Models/EntityBase/Entity.cs
+++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs
@@ -119,7 +119,7 @@ namespace Umbraco.Core.Models.EntityBase
if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime))
CreateDate = DateTime.Now;
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
- UpdateDate = CreateDate;
+ UpdateDate = DateTime.Now;
}
///
@@ -129,6 +129,10 @@ namespace Umbraco.Core.Models.EntityBase
{
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
UpdateDate = DateTime.Now;
+
+ //this is just in case
+ if (_createDate == default(DateTime))
+ CreateDate = DateTime.Now;
}
///
diff --git a/src/Umbraco.Core/Models/EntityBase/EntityPath.cs b/src/Umbraco.Core/Models/EntityBase/EntityPath.cs
new file mode 100644
index 0000000000..368d6bd87b
--- /dev/null
+++ b/src/Umbraco.Core/Models/EntityBase/EntityPath.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Core.Models.EntityBase
+{
+ public class EntityPath
+ {
+ public int Id { get; set; }
+ public string Path { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/IUserControl.cs b/src/Umbraco.Core/Models/IUserControl.cs
new file mode 100644
index 0000000000..2660567258
--- /dev/null
+++ b/src/Umbraco.Core/Models/IUserControl.cs
@@ -0,0 +1,7 @@
+namespace Umbraco.Core.Models
+{
+ public interface IUserControl : IFile
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index 50dc7d06f8..1b0459781e 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -2,23 +2,69 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.Linq;
+using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
+using Umbraco.Core.Models.EntityBase;
+using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
namespace Umbraco.Core.Models.Identity
{
- public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>
+ public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>, IRememberBeingDirty
{
-
- public BackOfficeIdentityUser()
+ ///
+ /// Used to construct a new instance without an identity
+ ///
+ ///
+ /// This is allowed to be null (but would need to be filled in if trying to persist this instance)
+ ///
+ ///
+ public static BackOfficeIdentityUser CreateNew(string username, string email, string culture)
{
- StartMediaId = -1;
- StartContentId = -1;
- Culture = Configuration.GlobalSettings.DefaultUILanguage;
+ if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username");
+ if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", "culture");
+
+ var user = new BackOfficeIdentityUser();
+ user.DisableChangeTracking();
+ user._userName = username;
+ user._email = email;
+ //we are setting minvalue here because the default is "0" which is the id of the admin user
+ //which we cannot allow because the admin user will always exist
+ user._id = int.MinValue;
+ user._hasIdentity = false;
+ user._culture = culture;
+ user.EnableChangeTracking();
+ return user;
}
+ private BackOfficeIdentityUser()
+ {
+ }
+
+ ///
+ /// Creates an existing user with the specified groups
+ ///
+ ///
+ ///
+ public BackOfficeIdentityUser(int userId, IEnumerable groups)
+ {
+ _startMediaIds = new int[] { };
+ _startContentIds = new int[] { };
+ _groups = new IReadOnlyUserGroup[] { };
+ _allowedSections = new string[] { };
+ _culture = Configuration.GlobalSettings.DefaultUILanguage;
+ _groups = groups.ToArray();
+ _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole
+ {
+ RoleId = x.Alias,
+ UserId = userId.ToString()
+ }));
+ _roles.CollectionChanged += _roles_CollectionChanged;
+ }
+
public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager)
{
// NOTE the authenticationType must match the umbraco one
@@ -27,16 +73,155 @@ namespace Umbraco.Core.Models.Identity
return userIdentity;
}
+ ///
+ /// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database
+ ///
+ public bool HasIdentity
+ {
+ get { return _hasIdentity; }
+ }
+
+ public int[] CalculatedMediaStartNodeIds { get; internal set; }
+ public int[] CalculatedContentStartNodeIds { get; internal set; }
+
+ public override int Id
+ {
+ get { return _id; }
+ set
+ {
+ _id = value;
+ _hasIdentity = true;
+ }
+ }
+
+ ///
+ /// Override Email so we can track changes to it
+ ///
+ public override string Email
+ {
+ get { return _email; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); }
+ }
+
+ ///
+ /// Override UserName so we can track changes to it
+ ///
+ public override string UserName
+ {
+ get { return _userName; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); }
+ }
+
+ ///
+ /// Override LastLoginDateUtc so we can track changes to it
+ ///
+ public override DateTime? LastLoginDateUtc
+ {
+ get { return _lastLoginDateUtc; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); }
+ }
+
+ ///
+ /// Override EmailConfirmed so we can track changes to it
+ ///
+ public override bool EmailConfirmed
+ {
+ get { return _emailConfirmed; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); }
+ }
+
///
/// Gets/sets the user's real name
///
- public string Name { get; set; }
- public int StartContentId { get; set; }
- public int StartMediaId { get; set; }
- public string[] AllowedSections { get; set; }
- public string Culture { get; set; }
+ public string Name
+ {
+ get { return _name; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
+ }
- public string UserTypeAlias { get; set; }
+ ///
+ /// Override AccessFailedCount so we can track changes to it
+ ///
+ public override int AccessFailedCount
+ {
+ get { return _accessFailedCount; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); }
+ }
+
+ ///
+ /// Override PasswordHash so we can track changes to it
+ ///
+ public override string PasswordHash
+ {
+ get { return _passwordHash; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); }
+ }
+
+
+ ///
+ /// Content start nodes assigned to the User (not ones assigned to the user's groups)
+ ///
+ public int[] StartContentIds
+ {
+ get { return _startContentIds; }
+ set
+ {
+ if (value == null) value = new int[0];
+ _tracker.SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdsSelector, Ps.Value.StartIdsComparer);
+ }
+ }
+
+ ///
+ /// Media start nodes assigned to the User (not ones assigned to the user's groups)
+ ///
+ public int[] StartMediaIds
+ {
+ get { return _startMediaIds; }
+ set
+ {
+ if (value == null) value = new int[0];
+ _tracker.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdsSelector, Ps.Value.StartIdsComparer);
+ }
+ }
+
+ ///
+ /// This is a readonly list of the user's allowed sections which are based on it's user groups
+ ///
+ public string[] AllowedSections
+ {
+ get { return _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); }
+ }
+
+ public string Culture
+ {
+ get { return _culture; }
+ set { _tracker.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); }
+ }
+
+ public IReadOnlyUserGroup[] Groups
+ {
+ get { return _groups; }
+ set
+ {
+ //so they recalculate
+ _allowedSections = null;
+
+ //now clear all roles and re-add them
+ _roles.CollectionChanged -= _roles_CollectionChanged;
+ _roles.Clear();
+ foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole
+ {
+ RoleId = x.Alias,
+ UserId = Id.ToString()
+ }))
+ {
+ _roles.Add(identityUserRole);
+ }
+ _roles.CollectionChanged += _roles_CollectionChanged;
+
+ _tracker.SetPropertyValueAndDetectChanges(value, ref _groups, Ps.Value.GroupsSelector, Ps.Value.GroupsComparer);
+ }
+ }
///
/// Lockout is always enabled
@@ -44,7 +229,7 @@ namespace Umbraco.Core.Models.Identity
public override bool LockoutEnabled
{
get { return true; }
- set
+ set
{
//do nothing
}
@@ -82,16 +267,42 @@ namespace Umbraco.Core.Models.Identity
return _logins;
}
}
-
- public bool LoginsChanged { get; private set; }
-
+
void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
- LoginsChanged = true;
+ _tracker.OnPropertyChanged(Ps.Value.LoginsSelector);
}
- private ObservableCollection _logins;
- private Lazy> _getLogins;
+ private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ _tracker.OnPropertyChanged(Ps.Value.RolesSelector);
+ }
+
+ private readonly ObservableCollection> _roles;
+
+ ///
+ /// helper method to easily add a role without having to deal with IdentityUserRole{T}
+ ///
+ ///
+ ///
+ /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted
+ ///
+ public void AddRole(string role)
+ {
+ Roles.Add(new IdentityUserRole
+ {
+ UserId = this.Id.ToString(),
+ RoleId = role
+ });
+ }
+
+ ///
+ /// Override Roles because the value of these are the user's group aliases
+ ///
+ public override ICollection> Roles
+ {
+ get { return _roles; }
+ }
///
/// Used to set a lazy call back to populate the user's Login list
@@ -101,6 +312,128 @@ namespace Umbraco.Core.Models.Identity
{
if (callback == null) throw new ArgumentNullException("callback");
_getLogins = callback;
+ }
+
+ #region Change tracking
+
+ public void DisableChangeTracking()
+ {
+ _tracker.DisableChangeTracking();
}
+
+ public void EnableChangeTracking()
+ {
+ _tracker.EnableChangeTracking();
+ }
+
+ ///
+ /// Since this class only has change tracking turned on for Email/Username this will return true if either of those have changed
+ ///
+ ///
+ public bool IsDirty()
+ {
+ return _tracker.IsDirty();
+ }
+
+ ///
+ /// Returns true if the specified property is dirty
+ ///
+ ///
+ ///
+ public bool IsPropertyDirty(string propName)
+ {
+ return _tracker.IsPropertyDirty(propName);
+ }
+
+ ///
+ /// Resets dirty properties
+ ///
+ void ICanBeDirty.ResetDirtyProperties()
+ {
+ _tracker.ResetDirtyProperties();
+ }
+
+ bool IRememberBeingDirty.WasDirty()
+ {
+ return _tracker.WasDirty();
+ }
+
+ bool IRememberBeingDirty.WasPropertyDirty(string propertyName)
+ {
+ return _tracker.WasPropertyDirty(propertyName);
+ }
+
+ void IRememberBeingDirty.ForgetPreviouslyDirtyProperties()
+ {
+ _tracker.ForgetPreviouslyDirtyProperties();
+ }
+
+ public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
+ {
+ _tracker.ResetDirtyProperties(rememberPreviouslyChangedProperties);
+ }
+
+ private static readonly Lazy Ps = new Lazy();
+ private class PropertySelectors
+ {
+ public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email);
+ public readonly PropertyInfo UserNameSelector = ExpressionHelper.GetPropertyInfo(x => x.UserName);
+ public readonly PropertyInfo LastLoginDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDateUtc);
+ public readonly PropertyInfo EmailConfirmedSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmed);
+ public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name);
+ public readonly PropertyInfo AccessFailedCountSelector = ExpressionHelper.GetPropertyInfo(x => x.AccessFailedCount);
+ public readonly PropertyInfo PasswordHashSelector = ExpressionHelper.GetPropertyInfo(x => x.PasswordHash);
+ public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture);
+ public readonly PropertyInfo StartMediaIdsSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaIds);
+ public readonly PropertyInfo StartContentIdsSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentIds);
+ public readonly PropertyInfo GroupsSelector = ExpressionHelper.GetPropertyInfo(x => x.Groups);
+ public readonly PropertyInfo LoginsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Logins);
+ public readonly PropertyInfo RolesSelector = ExpressionHelper.GetPropertyInfo>>(x => x.Roles);
+
+ //Custom comparer for enumerables
+ public readonly DelegateEqualityComparer GroupsComparer = new DelegateEqualityComparer(
+ (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)),
+ groups => groups.GetHashCode());
+ public readonly DelegateEqualityComparer StartIdsComparer = new DelegateEqualityComparer(
+ (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable),
+ groups => groups.GetHashCode());
+
+ }
+
+ private readonly ChangeTracker _tracker = new ChangeTracker();
+ private string _email;
+ private string _userName;
+ private int _id;
+ private bool _hasIdentity = false;
+ private DateTime? _lastLoginDateUtc;
+ private bool _emailConfirmed;
+ private string _name;
+ private int _accessFailedCount;
+ private string _passwordHash;
+ private string _culture;
+ private ObservableCollection _logins;
+ private Lazy> _getLogins;
+ private IReadOnlyUserGroup[] _groups;
+ private string[] _allowedSections;
+ private int[] _startMediaIds;
+ private int[] _startContentIds;
+
+ ///
+ /// internal class used to track changes for properties that have it enabled
+ ///
+ private class ChangeTracker : TracksChangesEntityBase
+ {
+ ///
+ /// Make this public so that it's usable
+ ///
+ ///
+ public new void OnPropertyChanged(PropertyInfo propertyInfo)
+ {
+ base.OnPropertyChanged(propertyInfo);
+ }
+ }
+ #endregion
+
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
index fab34e5f17..1b73ed5821 100644
--- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
+++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
@@ -1,7 +1,7 @@
using System;
using System.Linq;
using AutoMapper;
-
+using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Mapping;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
@@ -13,28 +13,42 @@ namespace Umbraco.Core.Models.Identity
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
config.CreateMap()
+ .BeforeMap((user, identityUser) =>
+ {
+ identityUser.DisableChangeTracking();
+ })
+ .ConstructUsing(user => new BackOfficeIdentityUser(user.Id, user.Groups))
.ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime()))
.ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
+ .ForMember(user => user.EmailConfirmed, expression => expression.MapFrom(user => user.EmailConfirmedDate.HasValue))
.ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
.ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null))
.ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username))
.ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
.ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)))
.ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name))
- .ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId))
- .ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId))
- .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias))
+ .ForMember(user => user.StartMediaIds, expression => expression.MapFrom(user => user.StartMediaIds))
+ .ForMember(user => user.StartContentIds, expression => expression.MapFrom(user => user.StartContentIds))
.ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts))
- .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()));
-
+ .ForMember(user => user.CalculatedContentStartNodeIds, expression => expression.MapFrom(user => user.CalculateContentStartNodeIds(applicationContext.Services.EntityService)))
+ .ForMember(user => user.CalculatedMediaStartNodeIds, expression => expression.MapFrom(user => user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService)))
+ .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()))
+ .AfterMap((user, identityUser) =>
+ {
+ identityUser.ResetDirtyProperties(true);
+ identityUser.EnableChangeTracking();
+ });
+
config.CreateMap()
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
+ .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Roles.Select(x => x.RoleId).ToArray()))
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
- .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] { user.UserTypeAlias }))
- .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId))
- .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId))
+ //When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
+ .ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.CalculatedContentStartNodeIds))
+ //When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
+ .ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.CalculatedMediaStartNodeIds))
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName))
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture))
.ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
@@ -42,7 +56,7 @@ namespace Umbraco.Core.Models.Identity
private string GetPasswordHash(string storedPass)
{
- return storedPass.StartsWith("___UIDEMPTYPWORD__") ? null : storedPass;
+ return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs
index 02d6b9aece..1ed11af93e 100644
--- a/src/Umbraco.Core/Models/Member.cs
+++ b/src/Umbraco.Core/Models/Member.cs
@@ -109,6 +109,30 @@ namespace Umbraco.Core.Models
IsApproved = true;
}
+ ///
+ /// Constructor for creating a Member object
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
+ ///
+ ///
+ ///
+ public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved)
+ : base(name, -1, contentType, new PropertyCollection())
+ {
+ Mandate.ParameterNotNull(contentType, "contentType");
+
+ _contentTypeAlias = contentType.Alias;
+ _contentType = contentType;
+ _email = email;
+ _username = username;
+ _rawPasswordValue = rawPasswordValue;
+ IsApproved = isApproved;
+ }
+
private static readonly Lazy Ps = new Lazy();
private class PropertySelectors
diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs
new file mode 100644
index 0000000000..ca0a910c05
--- /dev/null
+++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models.Membership
+{
+ ///
+ /// Represents an -> user group & permission key value pair collection
+ ///
+ ///
+ /// This implements purely so it can be used with the repository layer which is why it's explicitly implemented.
+ ///
+ public class ContentPermissionSet : EntityPermissionSet, IAggregateRoot
+ {
+ private readonly IContent _content;
+
+ public ContentPermissionSet(IContent content, EntityPermissionCollection permissionsSet)
+ : base(content.Id, permissionsSet)
+ {
+ _content = content;
+ }
+
+ public override int EntityId
+ {
+ get { return _content.Id; }
+ }
+
+ #region Explicit implementation of IAggregateRoot
+ int IEntity.Id
+ {
+ get { return EntityId; }
+ set { throw new NotImplementedException(); }
+ }
+
+ bool IEntity.HasIdentity
+ {
+ get { return EntityId > 0; }
+ }
+
+ Guid IEntity.Key { get; set; }
+
+ DateTime IEntity.CreateDate { get; set; }
+
+ DateTime IEntity.UpdateDate { get; set; }
+
+ DateTime? IDeletableEntity.DeletedDate { get; set; }
+
+ object IDeepCloneable.DeepClone()
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs
index 7ab1ddc817..8f609c0e13 100644
--- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs
+++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs
@@ -1,26 +1,66 @@
-using System.Collections;
+using System;
namespace Umbraco.Core.Models.Membership
{
///
- /// Represents a user -> entity permission
+ /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user)
///
- public class EntityPermission
+ public class EntityPermission : IEquatable
{
- public EntityPermission(int userId, int entityId, string[] assignedPermissions)
+ public EntityPermission(int groupId, int entityId, string[] assignedPermissions)
{
- UserId = userId;
+ UserGroupId = groupId;
EntityId = entityId;
AssignedPermissions = assignedPermissions;
+ IsDefaultPermissions = false;
+ }
+
+ public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions)
+ {
+ UserGroupId = groupId;
+ EntityId = entityId;
+ AssignedPermissions = assignedPermissions;
+ IsDefaultPermissions = isDefaultPermissions;
}
- public int UserId { get; private set; }
public int EntityId { get; private set; }
+ public int UserGroupId { get; private set; }
///
/// The assigned permissions for the user/entity combo
///
public string[] AssignedPermissions { get; private set; }
+
+ ///
+ /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions
+ ///
+ ///
+ /// This will be the case when looking up entity permissions and falling back to the default permissions
+ ///
+ public bool IsDefaultPermissions { get; private set; }
+
+ public bool Equals(EntityPermission other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return EntityId == other.EntityId && UserGroupId == other.UserGroupId;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((EntityPermission) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (EntityId * 397) ^ UserGroupId;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs
new file mode 100644
index 0000000000..5fca079cfc
--- /dev/null
+++ b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Models.Membership
+{
+ ///
+ /// A of
+ ///
+ public class EntityPermissionCollection : HashSet
+ {
+ public EntityPermissionCollection()
+ {
+ }
+
+ public EntityPermissionCollection(IEnumerable collection) : base(collection)
+ {
+ }
+
+ ///
+ /// Returns the aggregate permissions in the permission set
+ ///
+ ///
+ ///
+ /// This value is only calculated once
+ ///
+ public IEnumerable GetAllPermissions()
+ {
+ return _aggregatePermissions ?? (_aggregatePermissions =
+ this.SelectMany(x => x.AssignedPermissions).Distinct().ToArray());
+ }
+
+ private string[] _aggregatePermissions;
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs
index c4669caf59..c33c4aa315 100644
--- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs
+++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs
@@ -1,59 +1,55 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Umbraco.Core.Models.Membership
{
///
- /// Represents an entity -> user & permission key value pair collection
- ///
+ /// Represents an entity -> user group & permission key value pair collection
+ ///
public class EntityPermissionSet
{
+ private static readonly Lazy EmptyInstance = new Lazy(() => new EntityPermissionSet(-1, new EntityPermissionCollection()));
+ ///
+ /// Returns an empty permission set
+ ///
+ ///
+ public static EntityPermissionSet Empty()
+ {
+ return EmptyInstance.Value;
+ }
+
+ public EntityPermissionSet(int entityId, EntityPermissionCollection permissionsSet)
+ {
+ EntityId = entityId;
+ PermissionsSet = permissionsSet;
+ }
+
///
/// The entity id with permissions assigned
///
- public int EntityId { get; private set; }
+ public virtual int EntityId { get; private set; }
///
- /// The key/value pairs of user id & single permission
+ /// The key/value pairs of user group id & single permission
///
- public IEnumerable UserPermissionsSet { get; private set; }
+ public EntityPermissionCollection PermissionsSet { get; private set; }
- public EntityPermissionSet(int entityId, IEnumerable userPermissionsSet)
+
+ ///
+ /// Returns the aggregate permissions in the permission set
+ ///
+ ///
+ ///
+ /// This value is only calculated once
+ ///
+ public IEnumerable GetAllPermissions()
{
- EntityId = entityId;
- UserPermissionsSet = userPermissionsSet;
+ return PermissionsSet.GetAllPermissions();
}
- public class UserPermission
- {
- public UserPermission(int userId, string permission)
- {
- UserId = userId;
- Permission = permission;
- }
- public int UserId { get; private set; }
- public string Permission { get; private set; }
- protected bool Equals(UserPermission other)
- {
- return UserId == other.UserId && string.Equals(Permission, other.Permission);
- }
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((UserPermission) obj);
- }
-
- public override int GetHashCode()
- {
- unchecked
- {
- return (UserId*397) ^ Permission.GetHashCode();
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs
index 749c3371b8..0e6267a10b 100644
--- a/src/Umbraco.Core/Models/Membership/IProfile.cs
+++ b/src/Umbraco.Core/Models/Membership/IProfile.cs
@@ -1,15 +1,11 @@
namespace Umbraco.Core.Models.Membership
{
///
- /// Defines the the Profile interface
- ///
- ///
- /// This interface is pretty useless but has been exposed publicly from 6.x so we're stuck with it. It would make more sense
- /// if the Id was an int but since it's not people have to cast it to int all of the time!
- ///
+ /// Defines the the User Profile interface
+ ///
public interface IProfile
{
- object Id { get; set; }
- string Name { get; set; }
+ int Id { get; }
+ string Name { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs
new file mode 100644
index 0000000000..deebc03401
--- /dev/null
+++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Models.Membership
+{
+ ///
+ /// A readonly user group providing basic information
+ ///
+ public interface IReadOnlyUserGroup
+ {
+ string Name { get; }
+ string Icon { get; }
+ int Id { get; }
+ int? StartContentId { get; }
+ int? StartMediaId { get; }
+
+ ///
+ /// The alias
+ ///
+ string Alias { get; }
+
+ ///
+ /// The set of default permissions
+ ///
+ ///
+ /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
+ ///
+ IEnumerable Permissions { get; set; }
+
+ IEnumerable AllowedSections { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs
index f1f9c23971..2a1d904ecc 100644
--- a/src/Umbraco.Core/Models/Membership/IUser.cs
+++ b/src/Umbraco.Core/Models/Membership/IUser.cs
@@ -1,6 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using Umbraco.Core.Models.EntityBase;
-using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models.Membership
{
@@ -10,29 +11,39 @@ namespace Umbraco.Core.Models.Membership
/// Will be left internal until a proper Membership implementation is part of the roadmap
public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty
{
+ UserState UserState { get; }
+
string Name { get; set; }
int SessionTimeout { get; set; }
- int StartContentId { get; set; }
- int StartMediaId { get; set; }
- string Language { get; set; }
+ int[] StartContentIds { get; set; }
+ int[] StartMediaIds { get; set; }
+ string Language { 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)]
+ IUserType UserType { get; set; }
+
+ DateTime? EmailConfirmedDate { get; set; }
+ DateTime? InvitedDate { get; set; }
+
+ ///
+ /// Gets the groups that user is part of
+ ///
+ IEnumerable Groups { get; }
+
+ void RemoveGroup(string group);
+ void ClearGroups();
+ void AddGroup(IReadOnlyUserGroup group);
- ///
- /// Gets/sets the user type for the user
- ///
- IUserType UserType { get; set; }
-
- //TODO: This should be a private set
- ///
- /// The default permission set for the user
- ///
- ///
- /// Currently in umbraco each permission is a single char but with an Enumerable{string} collection this allows for flexible changes to this in the future
- ///
- IEnumerable DefaultPermissions { get; set; }
-
IEnumerable AllowedSections { get; }
+
+ [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)]
void RemoveAllowedSection(string sectionAlias);
- void AddAllowedSection(string sectionAlias);
+
+ [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)]
+ void AddAllowedSection(string sectionAlias);
///
/// Exposes the basic profile data
@@ -43,5 +54,10 @@ namespace Umbraco.Core.Models.Membership
/// The security stamp used by ASP.Net identity
///
string SecurityStamp { get; set; }
+
+ ///
+ /// Will hold the media file system relative path of the users custom avatar if they uploaded one
+ ///
+ string Avatar { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs
new file mode 100644
index 0000000000..0282073a2e
--- /dev/null
+++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models.Membership
+{
+ public interface IUserGroup : IAggregateRoot
+ {
+ string Alias { get; set; }
+
+ int? StartContentId { get; set; }
+ int? StartMediaId { get; set; }
+
+ ///
+ /// The icon
+ ///
+ string Icon { get; set; }
+
+ ///
+ /// The name
+ ///
+ string Name { get; set; }
+
+ ///
+ /// The set of default permissions
+ ///
+ ///
+ /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
+ ///
+ IEnumerable Permissions { get; set; }
+
+ IEnumerable AllowedSections { get; }
+
+ void RemoveAllowedSection(string sectionAlias);
+
+ void AddAllowedSection(string sectionAlias);
+
+ void ClearAllowedSections();
+
+ ///
+ /// Specifies the number of users assigned to this group
+ ///
+ int UserCount { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/IUserType.cs b/src/Umbraco.Core/Models/Membership/IUserType.cs
index fe678afd2b..ba004cea4a 100644
--- a/src/Umbraco.Core/Models/Membership/IUserType.cs
+++ b/src/Umbraco.Core/Models/Membership/IUserType.cs
@@ -1,28 +1,17 @@
-using System.Collections.Generic;
-using Umbraco.Core.Models.EntityBase;
-using Umbraco.Core.Persistence.Mappers;
-
-namespace Umbraco.Core.Models.Membership
-{
-
- public interface IUserType : IAggregateRoot
- {
- ///
- /// The user type alias
- ///
- string Alias { get; set; }
-
- ///
- /// The user type name
- ///
- string Name { get; set; }
-
- ///
- /// The set of default permissions for the user type
- ///
- ///
- /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
- ///
- IEnumerable Permissions { get; set; }
- }
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Models.Membership
+{
+ [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)]
+ public interface IUserType : IAggregateRoot
+ {
+ string Alias { get; set; }
+ string Name { get; set; }
+ IEnumerable Permissions { get; set; }
+
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs
new file mode 100644
index 0000000000..ceb2671067
--- /dev/null
+++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Models.Membership
+{
+ public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable
+ {
+ public ReadOnlyUserGroup(int id, string name, string icon, int? startContentId, int? startMediaId, string @alias,
+ IEnumerable allowedSections, IEnumerable permissions)
+ {
+ Name = name;
+ Icon = icon;
+ Id = id;
+ Alias = alias;
+ AllowedSections = allowedSections.ToArray();
+ Permissions = permissions.ToArray();
+
+ //Zero is invalid and will be treated as Null
+ StartContentId = startContentId == 0 ? null : startContentId;
+ StartMediaId = startMediaId == 0 ? null : startMediaId;
+ }
+
+ public int Id { get; private set; }
+ public string Name { get; private set; }
+ public string Icon { get; private set; }
+ public int? StartContentId { get; private set; }
+ public int? StartMediaId { get; private set; }
+ public string Alias { get; private set; }
+
+ ///
+ /// The set of default permissions
+ ///
+ ///
+ /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
+ ///
+ public IEnumerable Permissions { get; set; }
+ public IEnumerable AllowedSections { get; private set; }
+
+ public bool Equals(ReadOnlyUserGroup other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return string.Equals(Alias, other.Alias);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((ReadOnlyUserGroup) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Alias.GetHashCode();
+ }
+
+ public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(ReadOnlyUserGroup left, ReadOnlyUserGroup right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs
index ada2a7102d..0ada5c3d0e 100644
--- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs
+++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs
@@ -34,12 +34,11 @@ namespace Umbraco.Core.Models.Membership
/// The last lockout date.
/// The full name.
/// The language.
- /// Type of the user.
///
public UmbracoMembershipUser(string providerName, string name, object providerUserKey, string email,
string passwordQuestion, string comment, bool isApproved, bool isLockedOut,
DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate,
- DateTime lastLockoutDate, string fullName, string language, IUserType userType, T user)
+ DateTime lastLockoutDate, string fullName, string language, T user)
: base( providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut,
creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate)
{
diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs
index cadda14508..aeb3148ceb 100644
--- a/src/Umbraco.Core/Models/Membership/User.cs
+++ b/src/Umbraco.Core/Models/Membership/User.cs
@@ -2,13 +2,14 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
-
namespace Umbraco.Core.Models.Membership
{
///
@@ -18,64 +19,107 @@ namespace Umbraco.Core.Models.Membership
[DataContract(IsReference = true)]
public class User : Entity, IUser
{
- public User(IUserType userType)
+ ///
+ /// Constructor for creating a new/empty user
+ ///
+ public User()
{
- if (userType == null) throw new ArgumentNullException("userType");
-
- _userType = userType;
- _defaultPermissions = _userType.Permissions == null ? Enumerable.Empty() : new List(_userType.Permissions);
- //Groups = new List