diff --git a/src/Orchard.Tests/Utility/Extensions/StringExtensionsTests.cs b/src/Orchard.Tests/Utility/Extensions/StringExtensionsTests.cs
index 66848124a..d266d85f8 100644
--- a/src/Orchard.Tests/Utility/Extensions/StringExtensionsTests.cs
+++ b/src/Orchard.Tests/Utility/Extensions/StringExtensionsTests.cs
@@ -282,5 +282,21 @@ namespace Orchard.Tests.Utility.Extensions {
Assert.That("abc".Translate("d".ToCharArray(), "d".ToCharArray()), Is.StringMatching("abc"));
Assert.That("abc".Translate("abc".ToCharArray(), "def".ToCharArray()), Is.StringMatching("def"));
}
+
+ [Test]
+ public void ShouldEncodeToBase64() {
+ Assert.That("abc".ToBase64(), Is.EqualTo("YWJj"));
+ }
+
+ [Test]
+ public void ShouldDecodeFromBase64() {
+ Assert.That("YWJj".FromBase64(), Is.EqualTo("abc"));
+ }
+
+ [Test]
+ public void ShouldRoundtripBase64() {
+ Assert.That("abc".ToBase64().FromBase64(), Is.EqualTo("abc"));
+ Assert.That("YWJj".FromBase64().ToBase64(), Is.EqualTo("YWJj"));
+ }
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Orchard.SecureSocketsLayer.csproj b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Orchard.SecureSocketsLayer.csproj
index ce79a3d37..779e40f5a 100644
--- a/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Orchard.SecureSocketsLayer.csproj
+++ b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Orchard.SecureSocketsLayer.csproj
@@ -90,6 +90,7 @@
+
diff --git a/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Services/SecureSocketsLayerSettingsProvider.cs b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Services/SecureSocketsLayerSettingsProvider.cs
new file mode 100644
index 000000000..d99890e67
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Services/SecureSocketsLayerSettingsProvider.cs
@@ -0,0 +1,17 @@
+using Orchard.Environment.Extensions;
+using Orchard.Security;
+
+namespace Orchard.SecureSocketsLayer.Services {
+ [OrchardSuppressDependency("Orchard.Security.Providers.DefaultSslSettingsProvider")]
+ public class SecureSocketsLayerSettingsProvider : ISslSettingsProvider {
+ private readonly ISecureSocketsLayerService _secureSocketsLayerService;
+
+ public SecureSocketsLayerSettingsProvider(ISecureSocketsLayerService secureSocketsLayerService) {
+ _secureSocketsLayerService = secureSocketsLayerService;
+ }
+
+ public bool GetRequiresSSL() {
+ return _secureSocketsLayerService.GetSettings().Enabled;
+ }
+ }
+}
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index 02519fce4..9a7a87cda 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -148,6 +148,8 @@
+
+
diff --git a/src/Orchard/Security/ISslSettingsProvider.cs b/src/Orchard/Security/ISslSettingsProvider.cs
new file mode 100644
index 000000000..9e411b178
--- /dev/null
+++ b/src/Orchard/Security/ISslSettingsProvider.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Orchard.Security {
+ public interface ISslSettingsProvider : IDependency {
+
+ ///
+ /// Gets whether authentication cookies should only be transmitted over SSL or not.
+ ///
+ bool GetRequiresSSL();
+ }
+}
diff --git a/src/Orchard/Security/Providers/DefaultSslSettingsProvider.cs b/src/Orchard/Security/Providers/DefaultSslSettingsProvider.cs
new file mode 100644
index 000000000..624ec7b62
--- /dev/null
+++ b/src/Orchard/Security/Providers/DefaultSslSettingsProvider.cs
@@ -0,0 +1,15 @@
+using System.Web.Security;
+
+namespace Orchard.Security.Providers {
+ public class DefaultSslSettingsProvider : ISslSettingsProvider {
+ public bool RequireSSL { get; set; }
+
+ public DefaultSslSettingsProvider() {
+ RequireSSL = FormsAuthentication.RequireSSL;
+ }
+
+ public bool GetRequiresSSL() {
+ return RequireSSL;
+ }
+ }
+}
diff --git a/src/Orchard/Security/Providers/FormsAuthenticationService.cs b/src/Orchard/Security/Providers/FormsAuthenticationService.cs
index b31b04b5c..3619c2ed5 100644
--- a/src/Orchard/Security/Providers/FormsAuthenticationService.cs
+++ b/src/Orchard/Security/Providers/FormsAuthenticationService.cs
@@ -3,25 +3,34 @@ 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 _version = 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 IUser _signedInUser;
private bool _isAuthenticated;
- public FormsAuthenticationService(ShellSettings settings, IClock clock, IContentManager contentManager, IHttpContextAccessor httpContextAccessor) {
+ public FormsAuthenticationService(
+ ShellSettings settings,
+ IClock clock,
+ IMembershipService membershipService,
+ IHttpContextAccessor httpContextAccessor,
+ ISslSettingsProvider sslSettingsProvider) {
_settings = settings;
_clock = clock;
- _contentManager = contentManager;
+ _membershipService = membershipService;
_httpContextAccessor = httpContextAccessor;
+ _sslSettingsProvider = sslSettingsProvider;
Logger = NullLogger.Instance;
@@ -35,11 +44,12 @@ 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(Convert.ToString(user.UserName).ToBase64(), ";", _settings.Name);
var ticket = new FormsAuthenticationTicket(
- 1 /*version*/,
+ _version,
user.UserName,
now,
now.Add(ExpirationTimeSpan),
@@ -50,8 +60,8 @@ namespace Orchard.Security.Providers {
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) {
- HttpOnly = true,
- Secure = FormsAuthentication.RequireSSL,
+ HttpOnly = true, // can't retrieve the cookie from JavaScript
+ Secure = _sslSettingsProvider.GetRequiresSSL(),
Path = FormsAuthentication.FormsCookiePath
};
@@ -108,30 +118,37 @@ namespace Orchard.Security.Providers {
}
var formsIdentity = (FormsIdentity)httpContext.User.Identity;
+
+ // if the cookie is from a previous format, ignore
+ if (formsIdentity.Ticket.Version != _version) {
+ return null;
+ }
+
var userData = formsIdentity.Ticket.UserData ?? "";
- // the cookie user data is {userId};{tenant}
+ // the cookie user data is {userName};{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;
- }
-
_isAuthenticated = true;
- return _signedInUser = _contentManager.Get(userId).As();
+ return _signedInUser = _membershipService.GetUser(userDataName);
}
private string GetCookiePath(HttpContextBase httpContext) {
diff --git a/src/Orchard/Utility/Extensions/StringExtensions.cs b/src/Orchard/Utility/Extensions/StringExtensions.cs
index d1290b794..1e66a8248 100644
--- a/src/Orchard/Utility/Extensions/StringExtensions.cs
+++ b/src/Orchard/Utility/Extensions/StringExtensions.cs
@@ -348,5 +348,13 @@ namespace Orchard.Utility.Extensions {
var pattern = String.Format("{0}", String.Join("|", replacements.Keys));
return Regex.Replace(original, pattern, match => replacements[match.Value]);
}
+
+ public static string ToBase64(this string value) {
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
+ }
+
+ public static string FromBase64(this string value) {
+ return Encoding.UTF8.GetString(Convert.FromBase64String(value));
+ }
}
}
\ No newline at end of file