diff --git a/README.md b/README.md
index f90457cfe..6c9d11e21 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,10 @@ Orchard is a free, open source, community-focused Content Management System buil
[](https://gitter.im/OrchardCMS/Orchard?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+You can try it for free on [Dotnest.com](https://dotnest.com) or on Microsoft Azure by clicking on this button
+
+[](https://ms.portal.azure.com/#create/OutercurveFoundation.OrchardCMS.0.5.9)
+
## About The Orchard Project
#### Please visit our website at http://orchardproject.net for the most current information about this project.
diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Constants.cs b/src/Orchard.Web/Modules/Orchard.Azure/Constants.cs
index 406043cfa..64dd6ebf1 100644
--- a/src/Orchard.Web/Modules/Orchard.Azure/Constants.cs
+++ b/src/Orchard.Web/Modules/Orchard.Azure/Constants.cs
@@ -2,13 +2,15 @@
public class Constants {
public const string ShellSettingsStorageConnectionStringSettingName = "Orchard.Azure.Settings.StorageConnectionString";
- public const string ShellSettingsContainerName = "sites"; // Container names must be lower case.
+ public const string ShellSettingsContainerNameSettingName = "Orchard.Azure.Settings.ContainerName";
+ public const string ShellSettingsDefaultContainerName = "sites"; // Container names must be lower case.
public const string ShellSettingsFileName = "Settings.txt";
public const string MediaStorageFeatureName = "Orchard.Azure.Media";
public const string MediaStorageStorageConnectionStringSettingName = "Orchard.Azure.Media.StorageConnectionString";
public const string MediaStorageRootFolderPathSettingName = "Orchard.Azure.Media.RootFolderPath";
- public const string MediaStorageContainerName = "media"; // Container names must be lower case.
+ public const string MediaStorageContainerNameSettingName = "Orchard.Azure.Media.ContainerName";
+ public const string MediaStorageDefaultContainerName = "media"; // Container names must be lower case.
public const string MediaStoragePublicHostName = "Orchard.Azure.Media.StoragePublicHostName";
public const string OutputCacheFeatureName = "Orchard.Azure.OutputCache";
diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/Environment/Configuration/AzureBlobShellSettingsManager.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/Environment/Configuration/AzureBlobShellSettingsManager.cs
index 0e97198d2..5796ee592 100644
--- a/src/Orchard.Web/Modules/Orchard.Azure/Services/Environment/Configuration/AzureBlobShellSettingsManager.cs
+++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/Environment/Configuration/AzureBlobShellSettingsManager.cs
@@ -20,7 +20,11 @@ namespace Orchard.Azure.Services.Environment.Configuration {
private readonly IShellSettingsManagerEventHandler _events;
public AzureBlobShellSettingsManager(IMimeTypeProvider mimeTypeProvider, IShellSettingsManagerEventHandler events) {
- _fileSystem = new AzureFileSystem(CloudConfigurationManager.GetSetting(Constants.ShellSettingsStorageConnectionStringSettingName), Constants.ShellSettingsContainerName, String.Empty, true, mimeTypeProvider);
+ var connectionString = CloudConfigurationManager.GetSetting(Constants.ShellSettingsStorageConnectionStringSettingName);
+ var containerName = CloudConfigurationManager.GetSetting(Constants.ShellSettingsContainerNameSettingName);
+ if (String.IsNullOrEmpty(containerName))
+ containerName = Constants.ShellSettingsDefaultContainerName;
+ _fileSystem = new AzureFileSystem(connectionString, containerName, String.Empty, true, mimeTypeProvider);
_events = events;
Logger = NullLogger.Instance;
}
diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/Media/AzureBlobStorageProvider.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/Media/AzureBlobStorageProvider.cs
index 2e38b1cfa..6dd5795ba 100644
--- a/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/Media/AzureBlobStorageProvider.cs
+++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/FileSystems/Media/AzureBlobStorageProvider.cs
@@ -11,12 +11,15 @@ namespace Orchard.Azure.Services.FileSystems.Media {
[OrchardSuppressDependency("Orchard.FileSystems.Media.FileSystemStorageProvider")]
public class AzureBlobStorageProvider : AzureFileSystem, IStorageProvider {
- public AzureBlobStorageProvider(ShellSettings shellSettings, IMimeTypeProvider mimeTypeProvider, IPlatformConfigurationAccessor pca)
- : this(pca.GetSetting(Constants.MediaStorageStorageConnectionStringSettingName, shellSettings.Name, null),
- Constants.MediaStorageContainerName,
- pca.GetSetting(Constants.MediaStorageRootFolderPathSettingName, shellSettings.Name, null) ?? shellSettings.Name,
+ public AzureBlobStorageProvider(
+ ShellSettings shellSettings,
+ IMimeTypeProvider mimeTypeProvider,
+ IPlatformConfigurationAccessor platformConfigurationAccessor)
+ : this(platformConfigurationAccessor.GetSetting(Constants.MediaStorageStorageConnectionStringSettingName, shellSettings.Name, null),
+ platformConfigurationAccessor.GetSetting(Constants.MediaStorageContainerNameSettingName, shellSettings.Name, null) ?? Constants.MediaStorageDefaultContainerName,
+ platformConfigurationAccessor.GetSetting(Constants.MediaStorageRootFolderPathSettingName, shellSettings.Name, null) ?? shellSettings.Name,
mimeTypeProvider,
- pca.GetSetting(Constants.MediaStoragePublicHostName, shellSettings.Name, null))
+ platformConfigurationAccessor.GetSetting(Constants.MediaStoragePublicHostName, shellSettings.Name, null))
{
}
@@ -26,10 +29,10 @@ namespace Orchard.Azure.Services.FileSystems.Media {
public bool TrySaveStream(string path, Stream inputStream) {
try {
- if (FileExists(path)) {
- return false;
- }
-
+ if (FileExists(path)) {
+ return false;
+ }
+
SaveStream(path, inputStream);
}
catch {
diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Scripts/setup.js b/src/Orchard.Web/Modules/Orchard.Setup/Scripts/setup.js
index 5c34f3426..1c5054cde 100644
--- a/src/Orchard.Web/Modules/Orchard.Setup/Scripts/setup.js
+++ b/src/Orchard.Web/Modules/Orchard.Setup/Scripts/setup.js
@@ -16,7 +16,7 @@
(function($) {
$(document).ready(function() {
$("select.recipe").change(function() { // class="recipe" on the select element
- var description = $(this).find(":selected").attr("recipedescription"); // reads the html attribute of the selected option
+ var description = $(this).find(":selected").data("recipe-description"); // reads the html attribute of the selected option
$("#recipedescription").text(description); // make the contents of
be the escaped description string
});
$(".data").find('input[name=DatabaseProvider]:checked').click();
diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml
index 8221c1997..59a1f5bc7 100644
--- a/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.Setup/Views/Setup/Index.cshtml
@@ -1,4 +1,5 @@
@model Orchard.Setup.ViewModels.SetupViewModel
+@using Orchard.Recipes.Models;
@{
Script.Require("jQuery");
Script.Require("ShapesBase");
@@ -8,6 +9,18 @@
var groupedRecipes = Model.Recipes.Where(x => !String.IsNullOrWhiteSpace(x.Category)).GroupBy(x => x.Category);
var unspecifiedCategoryRecipes = Model.Recipes.Where(x => String.IsNullOrWhiteSpace(x.Category)).ToList();
}
+@helper RenderRecipeOptions(IEnumerable recipes) {
+ foreach (var recipe in recipes) {
+ var optionAttributes = new RouteValueDictionary {
+ { "data-recipe-description", recipe.Description }
+ };
+ if (Model.Recipe == null && recipe.Name == "Default") {
+ optionAttributes["selected"] = "selected";
+ }
+ @Html.SelectOption(Model.Recipe, recipe.Name, recipe.Name, optionAttributes)
+ }
+ }
+}
@Html.TitleForPage(T("Get Started").ToString())
@using (Html.BeginFormAntiForgeryPost()) {
@@ -83,17 +96,13 @@ if (!Model.DatabaseIsPreconfigured) {
if (groupedRecipes.Count() > 1) {
}
- foreach (var recipe in recipeGroup.OrderBy(x => x.Name)) {
- @Html.SelectOption(Model.Recipe, recipe.Name, recipe.Name, new { recipedescription = recipe.Description })
- }
+ @RenderRecipeOptions(recipeGroup.OrderBy(x => x.Name))
}
@if (unspecifiedCategoryRecipes.Any()) {
if (groupedRecipes.Any()) {
}
- foreach (var recipe in unspecifiedCategoryRecipes.OrderBy(x => x.Name)) {
- @Html.SelectOption(Model.Recipe, recipe.Name, recipe.Name, new { recipedescription = recipe.Description })
- }
+ @RenderRecipeOptions(unspecifiedCategoryRecipes.OrderBy(x => x.Name))
}
diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs
index 4395e0795..6d12087f2 100644
--- a/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs
+++ b/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs
@@ -15,8 +15,10 @@ using System.Collections.Generic;
using Orchard.Services;
using System.Web.Helpers;
using Orchard.Environment.Configuration;
+using Orchard.Environment.Extensions;
namespace Orchard.Users.Services {
+ [OrchardSuppressDependency("Orchard.Security.NullMembershipService")]
public class MembershipService : IMembershipService {
private const string PBKDF2 = "PBKDF2";
private const string DefaultHashAlgorithm = PBKDF2;
diff --git a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
index ac3d1d948..0e5571ff4 100644
--- a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
+++ b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
@@ -52,6 +52,10 @@ namespace Orchard.Mvc.Html {
}
public static MvcHtmlString SelectOption(this HtmlHelper html, T currentValue, T optionValue, string text, object htmlAttributes) {
+ return SelectOption(html, optionValue, object.Equals(optionValue, currentValue), text, new RouteValueDictionary(htmlAttributes));
+ }
+
+ public static MvcHtmlString SelectOption(this HtmlHelper html, T currentValue, T optionValue, string text, RouteValueDictionary htmlAttributes) {
return SelectOption(html, optionValue, object.Equals(optionValue, currentValue), text, htmlAttributes);
}
@@ -60,6 +64,10 @@ namespace Orchard.Mvc.Html {
}
public static MvcHtmlString SelectOption(this HtmlHelper html, object optionValue, bool selected, string text, object htmlAttributes) {
+ return SelectOption(html, optionValue, selected, text, new RouteValueDictionary(htmlAttributes));
+ }
+
+ public static MvcHtmlString SelectOption(this HtmlHelper html, object optionValue, bool selected, string text, RouteValueDictionary htmlAttributes) {
var builder = new TagBuilder("option");
if (optionValue != null)
@@ -71,7 +79,7 @@ namespace Orchard.Mvc.Html {
builder.SetInnerText(text);
if (htmlAttributes != null) {
- builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
+ builder.MergeAttributes(htmlAttributes);
}
return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index b1e994ca7..5d687207c 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -181,6 +181,7 @@
+
diff --git a/src/Orchard/Security/NullMembershipService.cs b/src/Orchard/Security/NullMembershipService.cs
new file mode 100644
index 000000000..bb3b763e8
--- /dev/null
+++ b/src/Orchard/Security/NullMembershipService.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Orchard.Security {
+ ///
+ /// Provides a default implementation of IMembershipService used only for dependency resolution
+ /// in a setup context. No members on this implementation will ever be called; at the time when this
+ /// interface is actually used in a tenant, another implementation is assumed to have suppressed it.
+ ///
+ public class NullMembershipService : IMembershipService {
+ public IUser CreateUser(CreateUserParams createUserParams) {
+ throw new NotImplementedException();
+ }
+
+ public MembershipSettings GetSettings() {
+ throw new NotImplementedException();
+ }
+
+ public IUser GetUser(string username) {
+ throw new NotImplementedException();
+ }
+
+ public void SetPassword(IUser user, string password) {
+ throw new NotImplementedException();
+ }
+
+ public IUser ValidateUser(string userNameOrEmail, string password) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Orchard/Security/Providers/FormsAuthenticationService.cs b/src/Orchard/Security/Providers/FormsAuthenticationService.cs
index ca1a82244..346e4059a 100644
--- a/src/Orchard/Security/Providers/FormsAuthenticationService.cs
+++ b/src/Orchard/Security/Providers/FormsAuthenticationService.cs
@@ -3,16 +3,18 @@ using System.Web;
using System.Web.Security;
using Orchard.Environment.Configuration;
using Orchard.Logging;
-using Orchard.ContentManagement;
using Orchard.Mvc;
using Orchard.Mvc.Extensions;
using Orchard.Services;
+using Orchard.Utility.Extensions;
namespace Orchard.Security.Providers {
public class FormsAuthenticationService : IAuthenticationService {
+ private const int _cookieVersion = 3;
+
private readonly ShellSettings _settings;
private readonly IClock _clock;
- private readonly IContentManager _contentManager;
+ private readonly IMembershipService _membershipService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISslSettingsProvider _sslSettingsProvider;
private readonly IMembershipValidationService _membershipValidationService;
@@ -23,13 +25,13 @@ namespace Orchard.Security.Providers {
public FormsAuthenticationService(
ShellSettings settings,
IClock clock,
- IContentManager contentManager,
+ IMembershipService membershipService,
IHttpContextAccessor httpContextAccessor,
ISslSettingsProvider sslSettingsProvider,
IMembershipValidationService membershipValidationService) {
_settings = settings;
_clock = clock;
- _contentManager = contentManager;
+ _membershipService = membershipService;
_httpContextAccessor = httpContextAccessor;
_sslSettingsProvider = sslSettingsProvider;
_membershipValidationService = membershipValidationService;
@@ -45,12 +47,13 @@ namespace Orchard.Security.Providers {
public void SignIn(IUser user, bool createPersistentCookie) {
var now = _clock.UtcNow.ToLocalTime();
-
- // the cookie user data is {userId};{tenant}
- var userData = String.Concat(Convert.ToString(user.Id), ";", _settings.Name);
+
+ // The cookie user data is "{userName.Base64};{tenant}".
+ // The username is encoded to Base64 to prevent collisions with the ';' seprarator.
+ var userData = String.Concat(user.UserName.ToBase64(), ";", _settings.Name);
var ticket = new FormsAuthenticationTicket(
- 1 /*version*/,
+ _cookieVersion,
user.UserName,
now,
now.Add(ExpirationTimeSpan),
@@ -121,29 +124,28 @@ namespace Orchard.Security.Providers {
var formsIdentity = (FormsIdentity)httpContext.User.Identity;
var userData = formsIdentity.Ticket.UserData ?? "";
- // the cookie user data is {userId};{tenant}
+ // The cookie user data is {userName.Base64};{tenant}.
var userDataSegments = userData.Split(';');
if (userDataSegments.Length < 2) {
return null;
}
- var userDataId = userDataSegments[0];
+ var userDataName = userDataSegments[0];
var userDataTenant = userDataSegments[1];
+ try {
+ userDataName = userDataName.FromBase64();
+ }
+ catch {
+ return null;
+ }
+
if (!String.Equals(userDataTenant, _settings.Name, StringComparison.Ordinal)) {
return null;
}
- int userId;
- if (!int.TryParse(userDataId, out userId)) {
- Logger.Error("User id not a parsable integer");
- return null;
- }
-
- // todo: this issues a sql query for each authenticated request
- _signedInUser = _contentManager.Get(userId).As();
-
+ _signedInUser = _membershipService.GetUser(userDataName);
if (_signedInUser == null || !_membershipValidationService.CanAuthenticateWithCookie(_signedInUser)) {
return null;
}