From 86be175d63f23231ec25a3a0dff1013160ea06e2 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Fri, 3 Dec 2010 12:28:52 -0800 Subject: [PATCH 1/6] Setting trust level back to Full in perf. --HG-- branch : perf --- src/Orchard.Web/Web.config | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 9b51854ad..3613a7321 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -40,8 +40,6 @@ - - "); + else { + result = Display.Resource(Url: path, Condition: condition, Resource: context.Resource); } + Output.Write(result); } } diff --git a/src/Orchard/DisplayManagement/Descriptors/ResourceBindingStrategy/StylesheetBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ResourceBindingStrategy/StylesheetBindingStrategy.cs new file mode 100644 index 000000000..910364b13 --- /dev/null +++ b/src/Orchard/DisplayManagement/Descriptors/ResourceBindingStrategy/StylesheetBindingStrategy.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Orchard.Environment.Descriptor.Models; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.VirtualPath; +using Orchard.UI.Resources; + +namespace Orchard.DisplayManagement.Descriptors.ResourceBindingStrategy { + // discovers .css files and turns them into Style__ shapes. + public class StylesheetBindingStrategy : IShapeTableProvider { + private readonly IExtensionManager _extensionManager; + private readonly ShellDescriptor _shellDescriptor; + private readonly IVirtualPathProvider _virtualPathProvider; + private static readonly Regex _safeName = new Regex(@"[/:?#\[\]@!&'()*+,;=\s\""<>\.\-_]+", RegexOptions.Compiled); + + public StylesheetBindingStrategy(IExtensionManager extensionManager, ShellDescriptor shellDescriptor, IVirtualPathProvider virtualPathProvider) { + _extensionManager = extensionManager; + _shellDescriptor = shellDescriptor; + _virtualPathProvider = virtualPathProvider; + } + + private static string SafeName(string name) { + if (string.IsNullOrWhiteSpace(name)) + return String.Empty; + return _safeName.Replace(name, String.Empty); + } + + public static string GetAlternateShapeNameFromFileName(string fileName) { + if (fileName == null) { + throw new ArgumentNullException("fileName"); + } + string shapeName; + if (Uri.IsWellFormedUriString(fileName, UriKind.Absolute)) { + var uri = new Uri(fileName); + shapeName = uri.Authority + "$" + uri.AbsolutePath + "$" + uri.Query; + } + else { + shapeName = Path.GetFileNameWithoutExtension(fileName); + } + return SafeName(shapeName); + } + + private static IEnumerable Once(IEnumerable featureDescriptors) { + var once = new ConcurrentDictionary(); + return featureDescriptors.Select(fd => fd.Extension).Where(ed => once.TryAdd(ed.Id, null)).ToList(); + } + + public void Discover(ShapeTableBuilder builder) { + var availableFeatures = _extensionManager.AvailableFeatures(); + var activeFeatures = availableFeatures.Where(FeatureIsEnabled); + var activeExtensions = Once(activeFeatures); + + var hits = activeExtensions.SelectMany(extensionDescriptor => { + var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/'); + var virtualPath = Path.Combine(basePath, "Styles").Replace(Path.DirectorySeparatorChar, '/'); + var shapes = _virtualPathProvider.ListFiles(virtualPath) + .Select(Path.GetFileName) + .Where(fileName => string.Equals(Path.GetExtension(fileName), ".css", System.StringComparison.OrdinalIgnoreCase)) + .Select(cssFileName => new { + fileName = Path.GetFileNameWithoutExtension(cssFileName), + fileVirtualPath = Path.Combine(virtualPath, cssFileName).Replace(Path.DirectorySeparatorChar, '/'), + shapeType = "Style__" + GetAlternateShapeNameFromFileName(cssFileName), + extensionDescriptor + }); + return shapes; + }); + + foreach (var iter in hits) { + var hit = iter; + var featureDescriptors = hit.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id); + foreach (var featureDescriptor in featureDescriptors) { + builder.Describe(iter.shapeType) + .From(new Feature {Descriptor = featureDescriptor}) + .BoundAs( + hit.fileVirtualPath, + shapeDescriptor => displayContext => { + var shape = ((dynamic) displayContext.Value); + var output = displayContext.ViewContext.Writer; + ResourceDefinition resource = shape.Resource; + string condition = shape.Condition; + ResourceManager.WriteResource(output, resource, hit.fileVirtualPath, condition); + return null; + }); + } + } + } + + private bool FeatureIsEnabled(FeatureDescriptor fd) { + return (DefaultExtensionTypes.IsTheme(fd.Extension.ExtensionType) && (fd.Id == "TheAdmin" || fd.Id == "SafeMode")) || + _shellDescriptor.Features.Any(sf => sf.Name == fd.Id); + } + } +} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 40d72d553..d89393040 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -142,6 +142,7 @@ + diff --git a/src/Orchard/UI/Resources/ResourceFilter.cs b/src/Orchard/UI/Resources/ResourceFilter.cs index 990184f1b..fa291fe08 100644 --- a/src/Orchard/UI/Resources/ResourceFilter.cs +++ b/src/Orchard/UI/Resources/ResourceFilter.cs @@ -4,20 +4,16 @@ using Orchard.Mvc.Filters; namespace Orchard.UI.Resources { public class ResourceFilter : FilterProvider, IResultFilter { - private readonly IResourceManager _resourceManager; private readonly IWorkContextAccessor _workContextAccessor; private readonly dynamic _shapeFactory; public ResourceFilter( - IResourceManager resourceManager, IWorkContextAccessor workContextAccessor, IShapeFactory shapeFactory) { - _resourceManager = resourceManager; _workContextAccessor = workContextAccessor; _shapeFactory = shapeFactory; } - public void OnResultExecuting(ResultExecutingContext filterContext) { // should only run on a full view rendering result if (!(filterContext.Result is ViewResult)) @@ -26,11 +22,11 @@ namespace Orchard.UI.Resources { var ctx = _workContextAccessor.GetContext(); var head = ctx.Layout.Head; var tail = ctx.Layout.Tail; - head.Add(_shapeFactory.Metas().ResourceManager(_resourceManager)); - head.Add(_shapeFactory.HeadLinks().ResourceManager(_resourceManager)); - head.Add(_shapeFactory.StylesheetLinks().ResourceManager(_resourceManager)); - head.Add(_shapeFactory.HeadScripts().ResourceManager(_resourceManager)); - tail.Add(_shapeFactory.FootScripts().ResourceManager(_resourceManager)); + head.Add(_shapeFactory.Metas()); + head.Add(_shapeFactory.HeadLinks()); + head.Add(_shapeFactory.StylesheetLinks()); + head.Add(_shapeFactory.HeadScripts()); + tail.Add(_shapeFactory.FootScripts()); } public void OnResultExecuted(ResultExecutedContext filterContext) { diff --git a/src/Orchard/UI/Resources/ResourceManager.cs b/src/Orchard/UI/Resources/ResourceManager.cs index e5f36502b..429332006 100644 --- a/src/Orchard/UI/Resources/ResourceManager.cs +++ b/src/Orchard/UI/Resources/ResourceManager.cs @@ -3,8 +3,10 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; +using System.IO; using System.Linq; using System.Web; +using System.Web.Mvc; using Autofac.Features.Metadata; using Orchard.Environment.Extensions.Models; @@ -40,6 +42,30 @@ namespace Orchard.UI.Resources { return resourcePath; } + private static TagBuilder GetTagBuilder(ResourceDefinition resource, string url) { + var tagBuilder = new TagBuilder(resource.TagName); + tagBuilder.MergeAttributes(resource.TagBuilder.Attributes); + if (!String.IsNullOrEmpty(resource.FilePathAttributeName)) { + if (!String.IsNullOrEmpty(url)) { + if (VirtualPathUtility.IsAppRelative(url)) { + url = VirtualPathUtility.ToAbsolute(url); + } + tagBuilder.MergeAttribute(resource.FilePathAttributeName, url, true); + } + } + return tagBuilder; + } + + public static void WriteResource(TextWriter writer, ResourceDefinition resource, string url, string condition) { + if (!string.IsNullOrEmpty(condition)) { + writer.WriteLine(""); + } + } + public ResourceManager(IEnumerable> resourceProviders) { _providers = resourceProviders; } diff --git a/src/Orchard/UI/Resources/ResourceRequiredContext.cs b/src/Orchard/UI/Resources/ResourceRequiredContext.cs index 1cbe31f31..8475613ad 100644 --- a/src/Orchard/UI/Resources/ResourceRequiredContext.cs +++ b/src/Orchard/UI/Resources/ResourceRequiredContext.cs @@ -6,11 +6,15 @@ namespace Orchard.UI.Resources { public ResourceDefinition Resource { get; set; } public RequireSettings Settings { get; set; } + public string GetResourceUrl(RequireSettings baseSettings, string appPath) { + return Resource.ResolveUrl(baseSettings == null ? Settings : baseSettings.Combine(Settings), appPath); + } + public TagBuilder GetTagBuilder(RequireSettings baseSettings, string appPath) { var tagBuilder = new TagBuilder(Resource.TagName); tagBuilder.MergeAttributes(Resource.TagBuilder.Attributes); if (!String.IsNullOrEmpty(Resource.FilePathAttributeName)) { - var resolvedUrl = Resource.ResolveUrl(baseSettings == null ? Settings : baseSettings.Combine(Settings), appPath); + var resolvedUrl = GetResourceUrl(baseSettings, appPath); if (!String.IsNullOrEmpty(resolvedUrl)) { tagBuilder.MergeAttribute(Resource.FilePathAttributeName, resolvedUrl, true); } From fadcc4ef6e9054b72bda6763bbc77d14f3c8172d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 3 Dec 2010 16:14:17 -0800 Subject: [PATCH 3/6] Adding IEncryptionServices Implements symetric encoding/decoding services based on a per-tenant key generated randomly during setup in the ShellSettings. Replaces MachineKey.Encode/Decode usages. Adding ComputedField to wrap get/set calls from parts, making the Smtp password encrypted in the db automatically. --HG-- branch : dev --- .../Orchard.Tests.Modules.csproj | 1 + .../Controllers/AccountControllerTests.cs | 9 ++- .../Users/Controllers/AdminControllerTests.cs | 4 +- .../Users/Services/UserServiceTests.cs | 26 ++------ .../Users/ShellSettingsUtility.cs | 27 ++++++++ .../DefaultTenantManagerTests.cs | 16 ++++- .../Orchard.Framework.Tests.csproj | 1 + .../Security/DefaultEncryptionServiceTests.cs | 53 ++++++++++++++++ .../Services/ContentDefinitionService.cs | 2 - .../Handlers/SmtpSettingsPartHandler.cs | 17 ++++- .../Orchard.Email/Models/SmtpSettingsPart.cs | 23 ++++--- .../Orchard.Setup/Services/SetupService.cs | 17 +++++ .../Orchard.Users/Services/UserService.cs | 20 +++--- .../Utilities/ComputedField.cs | 29 +++++++++ .../Configuration/ShellSettings.cs | 7 +++ .../Configuration/ShellSettingsManager.cs | 17 ++++- src/Orchard/Orchard.Framework.csproj | 3 + src/Orchard/Security/IEncryptionService.cs | 20 ++++++ .../Providers/DefaultEncryptionService.cs | 62 +++++++++++++++++++ .../Utility/Extensions/StringExtensions.cs | 14 ++++- 20 files changed, 315 insertions(+), 53 deletions(-) create mode 100644 src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs create mode 100644 src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs create mode 100644 src/Orchard/ContentManagement/Utilities/ComputedField.cs create mode 100644 src/Orchard/Security/IEncryptionService.cs create mode 100644 src/Orchard/Security/Providers/DefaultEncryptionService.cs diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj index acfe2486b..8b3915057 100644 --- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj +++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj @@ -154,6 +154,7 @@ + diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs index 9e35a845a..601bbd69d 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -26,20 +27,19 @@ using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; using Orchard.Security.Permissions; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.UI.Notify; using Orchard.Users.Controllers; using Orchard.Users.Handlers; using Orchard.Users.Models; using Orchard.Users.Services; -using Orchard.Users.ViewModels; using Orchard.Settings; using Orchard.Core.Settings.Services; using Orchard.Tests.Messaging; using Orchard.Environment.Configuration; using Orchard.Core.Settings.Models; using Orchard.Core.Settings.Handlers; -using Orchard.Messaging.Models; using System.Collections.Specialized; namespace Orchard.Tests.Modules.Users.Controllers { @@ -74,11 +74,14 @@ namespace Orchard.Tests.Modules.Users.Controllers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _authorizer = new Mock(); builder.RegisterInstance(_authorizer.Object); diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs index 6f6d395cb..ca2f20493 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs @@ -26,6 +26,7 @@ using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; using Orchard.Security.Permissions; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.UI.Notify; using Orchard.Users.Controllers; @@ -69,7 +70,8 @@ namespace Orchard.Tests.Modules.Users.Controllers { builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _authorizer = new Mock(); builder.RegisterInstance(_authorizer.Object); diff --git a/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs b/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs index efa354cc8..34b572dc7 100644 --- a/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs +++ b/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs @@ -1,5 +1,4 @@ using System; -using System.Web.Security; using System.Xml.Linq; using Autofac; using Moq; @@ -21,6 +20,7 @@ using Orchard.Environment.Extensions; using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.Tests.Utility; using Orchard.Users.Handlers; @@ -96,7 +96,9 @@ namespace Orchard.Tests.Modules.Users.Services { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _session = _sessionFactory.OpenSession(); builder.RegisterInstance(new TestSessionLocator(_session)).As(); @@ -121,25 +123,5 @@ namespace Orchard.Tests.Modules.Users.Services { Assert.That(username, Is.EqualTo("foo")); Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow)); } - - [Test] - public void NonceShouldNotBeUsedOnAnotherTenant() { - var user = _membershipService.CreateUser(new CreateUserParams("foo", "66554321", "foo@bar.com", "", "", true)); - var nonce = _userService.CreateNonce(user, new TimeSpan(1, 0, 0)); - - Assert.That(nonce, Is.Not.Empty); - - string username; - DateTime validateByUtc; - - _container.Resolve().Name = "Beta"; - - var result = _userService.DecryptNonce(nonce, out username, out validateByUtc); - - Assert.That(result, Is.False); - Assert.That(username, Is.EqualTo("foo")); - Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow)); - } - } } diff --git a/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs b/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs new file mode 100644 index 000000000..ff2a020be --- /dev/null +++ b/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs @@ -0,0 +1,27 @@ +using System; +using System.Security.Cryptography; +using Orchard.Environment.Configuration; +using Orchard.Utility.Extensions; + +namespace Orchard.Tests.Modules.Users { + public class ShellSettingsUtility { + public static ShellSettings CreateEncryptionEnabled() { + // generate random keys for encryption + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + return new ShellSettings { + Name = "Alpha", + RequestUrlHost = "wiki.example.com", + RequestUrlPrefix = "~/foo", + EncryptionAlgorithm = "AES", + EncryptionKey = key.ToHexString(), + EncryptionIV = iv.ToHexString() + }; + } + } +} diff --git a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs index e50dd1567..6f1d02806 100644 --- a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs +++ b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs @@ -58,7 +58,7 @@ namespace Orchard.Tests.Environment.Configuration { _appDataFolder.CreateFile("Sites\\Default\\Settings.txt", "Name: Default\r\nDataProvider: SqlCe\r\nDataConnectionString: something else"); IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock().Object); - var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux" }; + var foo = new ShellSettings {Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux"}; Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); loader.SaveSettings(foo); @@ -69,5 +69,19 @@ namespace Orchard.Tests.Environment.Configuration { Assert.That(text, Is.StringContaining("Bar")); Assert.That(text, Is.StringContaining("Quux")); } + + [Test] + public void EncryptionSettingsAreStoredAndReadable() { + IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock().Object); + var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux", EncryptionAlgorithm = "AES", EncryptionKey = "ABCDEFG", EncryptionIV= "HIJKL" }; + loader.SaveSettings(foo); + Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); + + var settings = loader.LoadSettings().First(); + + Assert.That(settings.EncryptionAlgorithm, Is.EqualTo("AES")); + Assert.That(settings.EncryptionKey, Is.EqualTo("ABCDEFG")); + Assert.That(settings.EncryptionIV, Is.EqualTo("HIJKL")); + } } } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index c1537c8af..0fcaf7102 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -245,6 +245,7 @@ + diff --git a/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs b/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs new file mode 100644 index 000000000..37a9fbc45 --- /dev/null +++ b/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs @@ -0,0 +1,53 @@ +using System.Security.Cryptography; +using System.Text; +using Autofac; +using NUnit.Framework; +using Orchard.Environment.Configuration; +using Orchard.Security; +using Orchard.Security.Providers; +using Orchard.Utility.Extensions; + +namespace Orchard.Tests.Security { + [TestFixture] + public class DefaultEncryptionServiceTests { + private IContainer container; + + [SetUp] + public void Init() { + + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + var shellSettings = new ShellSettings { + Name = "Foo", + DataProvider = "Bar", + DataConnectionString = "Quux", + EncryptionAlgorithm = "AES", + EncryptionKey = key.ToHexString(), + EncryptionIV = iv.ToHexString() + }; + + var builder = new ContainerBuilder(); + builder.RegisterInstance(shellSettings); + builder.RegisterType().As(); + container = builder.Build(); + } + + [Test] + public void CanEncodeAndDecodeData() { + var encryptionService = container.Resolve(); + + var secretData = Encoding.Unicode.GetBytes("this is secret data"); + var encrypted = encryptionService.Encode(secretData); + var decrypted = encryptionService.Decode(encrypted); + + Assert.That(encrypted, Is.Not.EqualTo(decrypted)); + Assert.That(decrypted, Is.EqualTo(secretData)); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs index d00d99d7b..6fdb834a1 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs @@ -6,10 +6,8 @@ using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData.Models; -using Orchard.ContentTypes.Extensions; using Orchard.ContentTypes.ViewModels; using Orchard.Core.Contents.Extensions; -using Orchard.Core.Contents.Settings; using Orchard.Localization; namespace Orchard.ContentTypes.Services { diff --git a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs index dda95b909..c7a2e1e20 100644 --- a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs @@ -1,14 +1,27 @@ -using JetBrains.Annotations; +using System; +using System.Text; +using JetBrains.Annotations; using Orchard.Email.Models; using Orchard.Data; using Orchard.ContentManagement.Handlers; +using Orchard.Security; namespace Orchard.Email.Handlers { [UsedImplicitly] public class SmtpSettingsPartHandler : ContentHandler { - public SmtpSettingsPartHandler(IRepository repository) { + private readonly IEncryptionService _encryptionService; + + public SmtpSettingsPartHandler(IRepository repository, IEncryptionService encryptionService) { + _encryptionService = encryptionService; Filters.Add(new ActivatingFilter("Site")); Filters.Add(StorageFilter.For(repository)); + + OnLoaded(LazyLoadHandlers); + } + + void LazyLoadHandlers(LoadContentContext context, SmtpSettingsPart part) { + part.PasswordField.Getter(() => String.IsNullOrWhiteSpace(part.Record.Password) ? String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.Password)))); + part.PasswordField.Setter(value => part.Record.Password = String.IsNullOrWhiteSpace(value) ? String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value)))); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs index 964d022df..e371459b4 100644 --- a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs @@ -1,14 +1,13 @@ -using System.Text; -using System.Web.Security; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using System; +using Orchard.ContentManagement.Utilities; namespace Orchard.Email.Models { public class SmtpSettingsPart : ContentPart { - public bool IsValid() { - return !String.IsNullOrWhiteSpace(Record.Host) - && Record.Port > 0 - && !String.IsNullOrWhiteSpace(Record.Address); + private readonly ComputedField _password = new ComputedField(); + + public ComputedField PasswordField { + get { return _password; } } public string Address { @@ -42,8 +41,14 @@ namespace Orchard.Email.Models { } public string Password { - get { return String.IsNullOrWhiteSpace(Record.Password) ? String.Empty : Encoding.UTF8.GetString(MachineKey.Decode(Record.Password, MachineKeyProtection.All)); ; } - set { Record.Password = String.IsNullOrWhiteSpace(value) ? String.Empty : MachineKey.Encode(Encoding.UTF8.GetBytes(value), MachineKeyProtection.All); } + get { return _password.Value; } + set { _password.Value = value; } + } + + public bool IsValid() { + return !String.IsNullOrWhiteSpace(Record.Host) + && Record.Port > 0 + && !String.IsNullOrWhiteSpace(Record.Address); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index 4c68417fc..2d26c4bbb 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq; +using System.Security.Cryptography; using System.Web; using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData; @@ -29,6 +30,7 @@ using Orchard.Settings; using Orchard.Environment.State; using Orchard.Data.Migration; using Orchard.Themes.Services; +using Orchard.Utility.Extensions; using Orchard.Widgets.Models; using Orchard.Widgets; @@ -118,6 +120,21 @@ namespace Orchard.Setup.Services { shellSettings.DataTablePrefix = context.DatabaseTablePrefix; } + #region Encryption Settings + + // generate random keys for encryption + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + shellSettings.EncryptionAlgorithm = "AES"; + shellSettings.EncryptionKey = key.ToHexString(); + shellSettings.EncryptionIV = iv.ToHexString(); + #endregion + var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(name => new ShellFeature { Name = name }) }; diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs index a1ed2f3fc..712936452 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs @@ -25,13 +25,15 @@ namespace Orchard.Users.Services { private readonly IClock _clock; private readonly IMessageManager _messageManager; private readonly ShellSettings _shellSettings; + private readonly IEncryptionService _encryptionService; - public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings) { + public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings, IEncryptionService encryptionService) { _contentManager = contentManager; _membershipService = membershipService; _clock = clock; _messageManager = messageManager; _shellSettings = shellSettings; + _encryptionService = encryptionService; Logger = NullLogger.Instance; } @@ -66,24 +68,22 @@ namespace Orchard.Users.Services { } public string CreateNonce(IUser user, TimeSpan delay) { - // the tenant's name is added to the token to prevent cross-tenant requests - var challengeToken = new XElement("n", new XAttribute("s", _shellSettings.Name), new XAttribute("un", user.UserName), new XAttribute("utc", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString(); - var data = Encoding.Unicode.GetBytes(challengeToken); - return MachineKey.Encode(data, MachineKeyProtection.All); + var challengeToken = new XElement("n", new XAttribute("un", user.UserName), new XAttribute("utc", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString(); + var data = Encoding.UTF8.GetBytes(challengeToken); + return Convert.ToBase64String(_encryptionService.Encode(data)); } - public bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc) { + public bool DecryptNonce(string nonce, out string username, out DateTime validateByUtc) { username = null; validateByUtc = _clock.UtcNow; try { - var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All); - var xml = Encoding.Unicode.GetString(data); + var data = _encryptionService.Decode(Convert.FromBase64String(nonce)); + var xml = Encoding.UTF8.GetString(data); var element = XElement.Parse(xml); - var tenant = element.Attribute("s").Value; username = element.Attribute("un").Value; validateByUtc = DateTime.Parse(element.Attribute("utc").Value, CultureInfo.InvariantCulture); - return String.Equals(_shellSettings.Name, tenant, StringComparison.Ordinal) && _clock.UtcNow <= validateByUtc; + return _clock.UtcNow <= validateByUtc; } catch { return false; diff --git a/src/Orchard/ContentManagement/Utilities/ComputedField.cs b/src/Orchard/ContentManagement/Utilities/ComputedField.cs new file mode 100644 index 000000000..3bbd33b7a --- /dev/null +++ b/src/Orchard/ContentManagement/Utilities/ComputedField.cs @@ -0,0 +1,29 @@ +using System; + +namespace Orchard.ContentManagement.Utilities { + public class ComputedField { + private Func _getter; + private Action _setter; + + public T Value { + get { return GetValue(); } + set { SetValue(value); } + } + + public void Getter(Func loader) { + _getter = loader; + } + + public void Setter(Action setter) { + _setter = setter; + } + + private T GetValue() { + return _getter(); + } + + private void SetValue(T value) { + _setter(value); + } + } +} diff --git a/src/Orchard/Environment/Configuration/ShellSettings.cs b/src/Orchard/Environment/Configuration/ShellSettings.cs index c6717938f..65795783f 100644 --- a/src/Orchard/Environment/Configuration/ShellSettings.cs +++ b/src/Orchard/Environment/Configuration/ShellSettings.cs @@ -16,6 +16,9 @@ DataTablePrefix = settings.DataTablePrefix; RequestUrlHost = settings.RequestUrlHost; RequestUrlPrefix = settings.RequestUrlPrefix; + EncryptionAlgorithm = settings.EncryptionAlgorithm; + EncryptionKey = settings.EncryptionKey; + EncryptionIV = settings.EncryptionIV; State = settings.State; } @@ -28,6 +31,10 @@ public string RequestUrlHost { get; set; } public string RequestUrlPrefix { get; set; } + public string EncryptionAlgorithm { get; set; } + public string EncryptionKey { get; set; } + public string EncryptionIV { get; set; } + public TenantState State { get; set; } } } diff --git a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs index 9e819b36e..6e6d24044 100644 --- a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs +++ b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs @@ -84,6 +84,15 @@ namespace Orchard.Environment.Configuration { case "RequestUrlPrefix": shellSettings.RequestUrlPrefix = settingFields[1]; break; + case "EncryptionAlgorithm": + shellSettings.EncryptionAlgorithm = settingFields[1]; + break; + case "EncryptionKey": + shellSettings.EncryptionKey = settingFields[1]; + break; + case "EncryptionIV": + shellSettings.EncryptionIV = settingFields[1]; + break; } } } @@ -94,14 +103,18 @@ namespace Orchard.Environment.Configuration { if (settings == null) return ""; - return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\n", + return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nEncryptionIV: {9}\r\n", settings.Name, settings.DataProvider, settings.DataConnectionString ?? "null", settings.DataTablePrefix ?? "null", settings.RequestUrlHost ?? "null", settings.RequestUrlPrefix ?? "null", - settings.State != null ? settings.State.ToString() : String.Empty); + settings.State != null ? settings.State.ToString() : String.Empty, + settings.EncryptionAlgorithm ?? "null", + settings.EncryptionKey ?? "null", + settings.EncryptionIV ?? "null" + ); } } } diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index d89393040..a8350ed09 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -142,6 +142,7 @@ + @@ -171,7 +172,9 @@ + + diff --git a/src/Orchard/Security/IEncryptionService.cs b/src/Orchard/Security/IEncryptionService.cs new file mode 100644 index 000000000..e2d0471ce --- /dev/null +++ b/src/Orchard/Security/IEncryptionService.cs @@ -0,0 +1,20 @@ +namespace Orchard.Security { + /// + /// Provides encryption services adapted to securing tenant level information + /// + public interface IEncryptionService : ISingletonDependency { + /// + /// Decodes data that has been encrypted. + /// + /// The encrypted data to decrypt. + /// A Byte[] array that represents the decrypted data. + byte[] Decode(byte[] encodedData); + + /// + /// Encrypts data. + /// + /// The data to encrypt. + /// The encrypted value. + byte[] Encode(byte[] data); + } +} diff --git a/src/Orchard/Security/Providers/DefaultEncryptionService.cs b/src/Orchard/Security/Providers/DefaultEncryptionService.cs new file mode 100644 index 000000000..9620f4157 --- /dev/null +++ b/src/Orchard/Security/Providers/DefaultEncryptionService.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Orchard.Environment.Configuration; +using Orchard.Utility.Extensions; + +namespace Orchard.Security.Providers { + public class DefaultEncryptionService : IEncryptionService { + private readonly ShellSettings _shellSettings; + private const int SaltSize = 16; + + public DefaultEncryptionService(ShellSettings shellSettings ) { + _shellSettings = shellSettings; + } + + public byte[] Decode(byte[] encodedData) { + using ( var ms = new MemoryStream() ) { + using (var algorithm = CreateAlgorithm()) { + using ( var cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write) ) { + cs.Write(encodedData, 0, encodedData.Length); + cs.FlushFinalBlock(); + } + + // remove the salt part + return ms.ToArray().Skip(SaltSize).ToArray(); + } + } + } + + public byte[] Encode(byte[] data) { + var salt = new byte[SaltSize]; + + // generate a random salt to happend to encoded data + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(salt); + } + using ( var ms = new MemoryStream() ) { + using (var algorithm = CreateAlgorithm()) { + using ( var cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write) ) { + // append the salt to the data and encrypt + var salted = salt.Concat(data).ToArray(); + cs.Write(salted, 0, salted.Length); + cs.FlushFinalBlock(); + } + + return ms.ToArray(); + } + } + } + + private SymmetricAlgorithm CreateAlgorithm() { + var encryptionAlgorithm = SymmetricAlgorithm.Create(_shellSettings.EncryptionAlgorithm); + + encryptionAlgorithm.Key = _shellSettings.EncryptionKey.ToByteArray(); + encryptionAlgorithm.IV = _shellSettings.EncryptionIV.ToByteArray(); + + return encryptionAlgorithm; + + } + } +} diff --git a/src/Orchard/Utility/Extensions/StringExtensions.cs b/src/Orchard/Utility/Extensions/StringExtensions.cs index bc1225b1e..3a11a1c65 100644 --- a/src/Orchard/Utility/Extensions/StringExtensions.cs +++ b/src/Orchard/Utility/Extensions/StringExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Text.RegularExpressions; using Orchard.Localization; @@ -55,5 +56,16 @@ namespace Orchard.Utility.Extensions { ? "" : Regex.Replace(text, @"(\r?\n)", replacement, RegexOptions.Singleline); } + + public static string ToHexString(this byte[] bytes) { + return BitConverter.ToString(bytes).Replace("-", ""); + } + + public static byte[] ToByteArray(this string hex) { + return Enumerable.Range(0, hex.Length). + Where(x => 0 == x % 2). + Select(x => Convert.ToByte(hex.Substring(x, 2), 16)). + ToArray(); + } } } \ No newline at end of file From 918b9c611b88590f1166e87a830a35456b7551cb Mon Sep 17 00:00:00 2001 From: Suha Can Date: Fri, 3 Dec 2010 16:48:41 -0800 Subject: [PATCH 4/6] Fixing an issue that caused loggers to be registered twice in some conditions. --HG-- branch : dev --- src/Orchard/Logging/ILogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard/Logging/ILogger.cs b/src/Orchard/Logging/ILogger.cs index 6e7963609..045db2ef8 100644 --- a/src/Orchard/Logging/ILogger.cs +++ b/src/Orchard/Logging/ILogger.cs @@ -9,7 +9,7 @@ namespace Orchard.Logging { Fatal } - public interface ILogger : ISingletonDependency { + public interface ILogger { bool IsEnabled(LogLevel level); void Log(LogLevel level, Exception exception, string format, params object[] args); } From 1aab29344dca57e9f637046b88af0ad71d3389f0 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Fri, 3 Dec 2010 16:58:00 -0800 Subject: [PATCH 5/6] Porting the logger fix to the branch. --HG-- branch : perf --- src/Orchard/Logging/ILogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orchard/Logging/ILogger.cs b/src/Orchard/Logging/ILogger.cs index 6e7963609..045db2ef8 100644 --- a/src/Orchard/Logging/ILogger.cs +++ b/src/Orchard/Logging/ILogger.cs @@ -9,7 +9,7 @@ namespace Orchard.Logging { Fatal } - public interface ILogger : ISingletonDependency { + public interface ILogger { bool IsEnabled(LogLevel level); void Log(LogLevel level, Exception exception, string format, params object[] args); } From d4b07855db1b2cb56f93013a9237faab0967062c Mon Sep 17 00:00:00 2001 From: Suha Can Date: Fri, 3 Dec 2010 18:37:06 -0800 Subject: [PATCH 6/6] PERF: 16858 Fixing ShapeTable caching issues by adding monitors. --HG-- branch : perf --- .../ShapeTemplateBindingStrategyTests.cs | 8 +++++-- src/Orchard/Data/SessionFactoryHolder.cs | 6 +---- .../ShapeTemplateBindingStrategy.cs | 22 ++++++++++++++++--- .../VirtualPath/DefaultVirtualPathMonitor.cs | 12 ++++++---- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs index ec16aa7cb..eb35adff9 100644 --- a/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs +++ b/src/Orchard.Tests/DisplayManagement/Descriptors/ShapeTemplateBindingStrategyTests.cs @@ -5,12 +5,14 @@ using System.Linq; using Autofac; using Moq; using NUnit.Framework; +using Orchard.Caching; using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy; using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.VirtualPath; +using Orchard.Tests.Stubs; namespace Orchard.Tests.DisplayManagement.Descriptors { [TestFixture] @@ -21,12 +23,14 @@ namespace Orchard.Tests.DisplayManagement.Descriptors { private TestVirtualPathProvider _testVirtualPathProvider; - protected override void Register(Autofac.ContainerBuilder builder) { - _descriptor = new ShellDescriptor { }; + protected override void Register(ContainerBuilder builder) { + _descriptor = new ShellDescriptor(); _testViewEngine = new TestViewEngine(); _testVirtualPathProvider = new TestVirtualPathProvider(); builder.Register(ctx => _descriptor); + builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterInstance(_testViewEngine).As(); diff --git a/src/Orchard/Data/SessionFactoryHolder.cs b/src/Orchard/Data/SessionFactoryHolder.cs index f7ba7389a..bc39d6e2f 100644 --- a/src/Orchard/Data/SessionFactoryHolder.cs +++ b/src/Orchard/Data/SessionFactoryHolder.cs @@ -1,9 +1,5 @@ -using System; -using System.IO; -using System.Xml.Serialization; -using NHibernate; +using NHibernate; using NHibernate.Cfg; -using Orchard.Data; using Orchard.Data.Providers; using Orchard.Environment; using Orchard.Environment.Configuration; diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs index 921ffc803..f038ed61e 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs @@ -1,10 +1,12 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; +using Orchard.Caching; using Orchard.DisplayManagement.Implementation; using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; @@ -15,6 +17,8 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { public class ShapeTemplateBindingStrategy : IShapeTableProvider { private readonly ShellDescriptor _shellDescriptor; private readonly IExtensionManager _extensionManager; + private readonly ICacheManager _cacheManager; + private readonly IVirtualPathMonitor _virtualPathMonitor; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IEnumerable _harvesters; private readonly IEnumerable _shapeTemplateViewEngines; @@ -24,11 +28,15 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { IEnumerable harvesters, ShellDescriptor shellDescriptor, IExtensionManager extensionManager, + ICacheManager cacheManager, + IVirtualPathMonitor virtualPathMonitor, IVirtualPathProvider virtualPathProvider, IEnumerable shapeTemplateViewEngines) { _harvesters = harvesters; _shellDescriptor = shellDescriptor; _extensionManager = extensionManager; + _cacheManager = cacheManager; + _virtualPathMonitor = virtualPathMonitor; _virtualPathProvider = virtualPathProvider; _shapeTemplateViewEngines = shapeTemplateViewEngines; } @@ -49,13 +57,21 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => { var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/'); var virtualPath = Path.Combine(basePath, subPath).Replace(Path.DirectorySeparatorChar, '/'); - var fileNames = _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName); + var fileNames = _cacheManager.Get(virtualPath, ctx => { + ctx.Monitor(_virtualPathMonitor.WhenPathChanges(virtualPath)); + return _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName); + }); return new { harvesterInfo.harvester, basePath, subPath, virtualPath, fileNames }; })); var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => { var fileNames = ve.DetectTemplateFileNames(pathContext.fileNames); - return fileNames.Select(fileName => new { fileName = Path.GetFileNameWithoutExtension(fileName), fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'), pathContext }); + return fileNames.Select( + fileName => new { + fileName = Path.GetFileNameWithoutExtension(fileName), + fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'), + pathContext + }); })); var shapeContexts = fileContexts.SelectMany(fileContext => { diff --git a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs index 12fa9ec42..486cc4153 100644 --- a/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs +++ b/src/Orchard/FileSystems/VirtualPath/DefaultVirtualPathMonitor.cs @@ -11,16 +11,21 @@ namespace Orchard.FileSystems.VirtualPath { private readonly string _prefix = Guid.NewGuid().ToString("n"); private readonly IDictionary> _tokens = new Dictionary>(); private readonly IClock _clock; + private readonly IVirtualPathProvider _virtualPathProvider; - public DefaultVirtualPathMonitor(IClock clock) { + public DefaultVirtualPathMonitor(IClock clock, IVirtualPathProvider virtualPathProvider) { _clock = clock; + _virtualPathProvider = virtualPathProvider; _thunk = new Thunk(this); } public IVolatileToken WhenPathChanges(string virtualPath) { // Fix this to monitor first existing parent directory. - var token = BindToken(virtualPath); - BindSignal(virtualPath); + IVolatileToken token = new Token(virtualPath); + if (_virtualPathProvider.DirectoryExists(virtualPath)) { + token = BindToken(virtualPath); + BindSignal(virtualPath); + } return token; } @@ -108,6 +113,5 @@ namespace Orchard.FileSystems.VirtualPath { provider.Signal(key, value, reason); } } - } } \ No newline at end of file