From 76408da930207b48ab04f6abe886cb1385e3c6d6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 16 Jun 2015 18:10:58 -0700 Subject: [PATCH] #5315: Adding new settings to configure SSL cookies - Creating ISslSettingsProvider to define whether cookies can only be sent over SSL or not - Implementing DefaultSslSettingsProvider based on Sites.config and web.config making it configurable per tenant or for the whole site - Implementing SecureSocketsLayerSettingsProvider in Orchard.SecureSocketsLayerSettingsProvider Fixes 5315 --- .../Extensions/StringExtensionsTests.cs | 16 ++++++ .../Orchard.SecureSocketsLayer.csproj | 1 + .../SecureSocketsLayerSettingsProvider.cs | 17 ++++++ src/Orchard/Orchard.Framework.csproj | 2 + src/Orchard/Security/ISslSettingsProvider.cs | 11 ++++ .../Providers/DefaultSslSettingsProvider.cs | 15 ++++++ .../Providers/FormsAuthenticationService.cs | 53 ++++++++++++------- .../Utility/Extensions/StringExtensions.cs | 8 +++ 8 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Services/SecureSocketsLayerSettingsProvider.cs create mode 100644 src/Orchard/Security/ISslSettingsProvider.cs create mode 100644 src/Orchard/Security/Providers/DefaultSslSettingsProvider.cs 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