SSL module brought into the core distribution

This commit is contained in:
Bertrand Le Roy
2013-10-07 21:30:20 -07:00
parent 094e6d370d
commit f7998bc95c
15 changed files with 778 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.SecureSocketsLayer.Models;
namespace Orchard.SecureSocketsLayer.Commands {
public class SecureSocketsLayersCommand : DefaultOrchardCommandHandler {
private readonly IOrchardServices _services;
public SecureSocketsLayersCommand(IOrchardServices services) {
_services = services;
}
[OrchardSwitch]
public bool SecureEverything { get; set; }
[OrchardSwitch]
public bool CustomEnabled { get; set; }
[OrchardSwitch]
public string Urls { get; set; }
[OrchardSwitch]
public string SecureHostName { get; set; }
[OrchardSwitch]
public string InsecureHostName { get; set; }
[CommandName("site setting set ssl")]
[CommandHelp("site setting set ssl /SecureEverything:true /CustomEnabled:true /Urls:<value> /SecureHostName:domain.com /InsecureHostName:secure.domain.com\r\n" +
"\tSet the 'SSL' site settings. Urls example: /Urls:\"'mysite.com/a','mysite.com/b'\"")]
[OrchardSwitches("SecureEverything,CustomEnabled,Urls,SecureHostName,InsecureHostName")]
public void SetSSLInfo() {
var settings = _services.WorkContext.CurrentSite.As<SslSettingsPart>();
if (settings == null) {
return;
}
if (!string.IsNullOrWhiteSpace(Urls)) {
var comma = false;
var urlList = new List<string>();
try {
Urls = Urls.Trim();
while (Urls.Length != 0) {
var first = Urls[0];
if (first == ',' && comma) {
Urls = Urls.Substring(1);
comma = false;
}
else if (first == '\'' && !comma) {
int end = Urls.IndexOf('\'', 1);
if (end == -1) {
throw new ArgumentException("Invalid Urls");
}
urlList.Add(Urls.Substring(1, end - 1));
Urls = Urls.Substring(end + 1);
comma = true;
}
else {
throw new ArgumentException("Invalid Urls");
}
}
if (!comma)
throw new ArgumentException("Invalid Urls");
Urls = string.Join("\r\n", urlList);
}
catch(ArgumentException) {
Context.Output.WriteLine(T("'Urls' site setting invalid"));
return;
}
}
if (string.IsNullOrWhiteSpace(Urls)) {
Urls = null;
}
settings.SecureEverything = SecureEverything;
settings.CustomEnabled = CustomEnabled;
settings.Urls = Urls;
settings.SecureHostName = SecureHostName;
settings.InsecureHostName = InsecureHostName;
Context.Output.WriteLine(T("'Secure Everything' site setting set to '{0}'", SecureEverything));
Context.Output.WriteLine(T("'Custom Enabled' site setting set to '{0}'", CustomEnabled));
Context.Output.WriteLine(T("'Urls' site setting set to '{0}'", Urls));
Context.Output.WriteLine(T("'Secure Host Name' site setting set to '{0}'", SecureHostName));
Context.Output.WriteLine(T("'Insecure Host Name' site setting set to '{0}'", InsecureHostName));
}
}
}

View File

@@ -0,0 +1,51 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Localization;
using Orchard.SecureSocketsLayer.Models;
namespace Orchard.SecureSocketsLayer.Drivers {
public class SslSettingsPartDriver : ContentPartDriver<SslSettingsPart> {
private const string TemplateName = "Parts/SecureSocketsLayer.Settings";
public SslSettingsPartDriver() {
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
protected override string Prefix {
get { return "SslSettings"; }
}
protected override DriverResult Editor(SslSettingsPart part, dynamic shapeHelper) {
return ContentShape("Parts_SslSettings_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: part, Prefix: Prefix))
.OnGroup("Ssl");
}
protected override DriverResult Editor(SslSettingsPart part, IUpdateModel updater, dynamic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
protected override void Importing(SslSettingsPart part, ImportContentContext context) {
var elementName = part.PartDefinition.Name;
part.SecureEverything = bool.Parse(context.Attribute(elementName, "SecureEverything") ?? "true");
part.CustomEnabled = bool.Parse(context.Attribute(elementName, "CustomEnabled") ?? "false");
part.Urls = context.Attribute(elementName, "Urls") ?? "";
part.InsecureHostName = context.Attribute(elementName, "InsecureHostName") ?? "";
part.SecureHostName = context.Attribute(elementName, "SecureHostName") ?? "";
}
protected override void Exporting(SslSettingsPart part, ExportContentContext context) {
var el = context.Element(part.PartDefinition.Name);
el.SetAttributeValue("SecureEverything", part.SecureEverything);
el.SetAttributeValue("CustomEnabled", part.CustomEnabled);
el.SetAttributeValue("Urls", part.Urls);
el.SetAttributeValue("InsecureHostName", part.InsecureHostName);
el.SetAttributeValue("SecureHostName", part.SecureHostName);
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Specialized;
using System.Web.Mvc;
using Orchard.Mvc.Filters;
using Orchard.SecureSocketsLayer.Services;
namespace Orchard.SecureSocketsLayer.Filters {
public class SecureSocketsLayersFilter : FilterProvider, IActionFilter {
private readonly ISecureSocketsLayerService _sslService;
public SecureSocketsLayersFilter(ISecureSocketsLayerService sslService) {
_sslService = sslService;
}
public void OnActionExecuted(ActionExecutedContext filterContext) {}
public void OnActionExecuting(ActionExecutingContext filterContext) {
var user = filterContext.HttpContext.User;
var secure =
(user != null && user.Identity.IsAuthenticated) ||
_sslService.ShouldBeSecure(filterContext);
var request = filterContext.HttpContext.Request;
// redirect to a secured connection ?
if (secure && !request.IsSecureConnection) {
var secureActionUrl = AppendQueryString(
request.QueryString,
_sslService.SecureActionUrl(
filterContext.ActionDescriptor.ActionName,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.RequestContext.RouteData.Values));
filterContext.Result = new RedirectResult(secureActionUrl);
return;
}
// non auth page on a secure canal
// nb: needed as the ReturnUrl for LogOn doesn't force the scheme to http, and reuses the current one
if (!secure && request.IsSecureConnection) {
var insecureActionUrl = AppendQueryString(
request.QueryString,
_sslService.InsecureActionUrl(
filterContext.ActionDescriptor.ActionName,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.RequestContext.RouteData.Values));
filterContext.Result = new RedirectResult(insecureActionUrl);
}
}
private static string AppendQueryString(NameValueCollection queryString, string url) {
if (queryString.Count > 0) {
url += '?' + queryString.ToString();
}
return url;
}
}
}

View File

@@ -0,0 +1,27 @@
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using Orchard.Localization;
using Orchard.SecureSocketsLayer.Models;
namespace Orchard.SecureSocketsLayer.Handlers {
public class SslSettingsPartHandler : ContentHandler {
public SslSettingsPartHandler(IRepository<SslSettingsPartRecord> repository) {
T = NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<SslSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
}
public Localizer T { get; set; }
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
if (context.ContentItem.ContentType != "Site")
return;
base.GetItemMetadata(context);
context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("SSL")) {
Id = "Ssl",
Position = "2"
});
}
}
}

View File

@@ -0,0 +1,20 @@
using Orchard.Data.Migration;
namespace Orchard.SecureSocketsLayer {
public class Migrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("SslSettingsPartRecord",
table => table
.ContentPartRecord()
.Column<bool>("CustomEnabled")
.Column<bool>("SecureEverything")
.Column<string>("Urls", c => c.Unlimited())
.Column<string>("SecureHostName")
.Column<string>("InsecureHostName")
);
return 1;
}
}
}

View File

@@ -0,0 +1,31 @@
using Orchard.ContentManagement;
namespace Orchard.SecureSocketsLayer.Models {
public class SslSettingsPart : ContentPart<SslSettingsPartRecord> {
public string Urls
{
get { return Record.Urls; }
set { Record.Urls = value; }
}
public bool SecureEverything {
get { return Record.SecureEverything; }
set { Record.SecureEverything = value; }
}
public bool CustomEnabled {
get { return Record.CustomEnabled; }
set { Record.CustomEnabled = value; }
}
public string SecureHostName {
get { return Record.SecureHostName; }
set { Record.SecureHostName = value; }
}
public string InsecureHostName {
get { return Record.InsecureHostName; }
set { Record.InsecureHostName = value; }
}
}
}

View File

@@ -0,0 +1,11 @@
using Orchard.ContentManagement.Records;
namespace Orchard.SecureSocketsLayer.Models {
public class SslSettingsPartRecord : ContentPartRecord {
public virtual string Urls { get; set; }
public virtual bool SecureEverything { get; set; }
public virtual bool CustomEnabled { get; set; }
public virtual string SecureHostName { get; set; }
public virtual string InsecureHostName { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
Name: Secure Sockets Layer
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardproject.net
Version: 1.7.1
OrchardVersion: 1.7.1
Description: This module will ensure SSL is used when accessing specific parts of the website like the dashboard, authentication pages or custom pages.
FeatureName: Secure Sockets Layer
Category: Security
FeatureDescription: Use SSL for specific parts of the website
Dependencies: Orchard.Users

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{36B82383-D69E-4897-A24A-648BABDF80EC}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.SecureSocketsLayer</RootNamespace>
<AssemblyName>Orchard.SecureSocketsLayer</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>4.0</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\SecureSocketsLayersCommand.cs" />
<Compile Include="Drivers\SslSettingsPartDriver.cs" />
<Compile Include="Filters\SecureSocketsLayersFilter.cs" />
<Compile Include="Handlers\SslSettingsPartHandler.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\SslSettingsPart.cs" />
<Compile Include="Models\SslSettingsPartRecord.cs" />
<Compile Include="Services\ISecureSocketsLayerService.cs" />
<Compile Include="Services\SecuredSocketsLayerService.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\SecureSocketsLayer.Settings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Placement.info" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
<PropertyGroup>
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
</PropertyGroup>
<!-- If this is an area child project, uncomment the following line:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
-->
<!-- If this is an area parent project, uncomment the following lines:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
-->
</Target>
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>45979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -0,0 +1,3 @@
<Placement>
<Place Parts_SslSettings_Edit="Content:2"/>
</Placement>

View File

@@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Orchard.SecureSocketsLayer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Orchard")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("285775cf-fa49-4030-8a30-7872120d6574")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,16 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace Orchard.SecureSocketsLayer.Services {
public interface ISecureSocketsLayerService : IDependency {
bool ShouldBeSecure(string actionName, string controllerName, RouteValueDictionary routeValues);
bool ShouldBeSecure(RequestContext requestContext);
bool ShouldBeSecure(ActionExecutingContext actionContext);
string InsecureActionUrl(string actionName, string controllerName);
string InsecureActionUrl(string actionName, string controllerName, object routeValues);
string InsecureActionUrl(string actionName, string controllerName, RouteValueDictionary routeValues);
string SecureActionUrl(string actionName, string controllerName);
string SecureActionUrl(string actionName, string controllerName, object routeValues);
string SecureActionUrl(string actionName, string controllerName, RouteValueDictionary routeValues);
}
}

View File

@@ -0,0 +1,239 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Caching;
using Orchard.ContentManagement;
using Orchard.SecureSocketsLayer.Models;
using Orchard.UI.Admin;
namespace Orchard.SecureSocketsLayer.Services {
public class SecureSocketsLayerService : ISecureSocketsLayerService {
private readonly IWorkContextAccessor _workContextAccessor;
private readonly ICacheManager _cacheManager;
public SecureSocketsLayerService(IWorkContextAccessor workContextAccessor, ICacheManager cacheManager) {
_workContextAccessor = workContextAccessor;
_cacheManager = cacheManager;
}
public bool ShouldBeSecure(string actionName, string controllerName, RouteValueDictionary routeValues) {
var requestContext = GetRequestContext(actionName, controllerName, routeValues);
return ShouldBeSecure(requestContext, null);
}
public bool ShouldBeSecure(RequestContext requestContext) {
return ShouldBeSecure(requestContext, null);
}
public bool ShouldBeSecure(ActionExecutingContext actionContext) {
var requestContext = GetRequestContext(
actionContext.ActionDescriptor.ActionName,
actionContext.ActionDescriptor.ControllerDescriptor.ControllerName,
actionContext.RequestContext.RouteData.Values);
return ShouldBeSecure(requestContext, actionContext);
}
private bool ShouldBeSecure(RequestContext requestContext, ActionExecutingContext actionContext) {
var controllerName = (string) requestContext.RouteData.Values["controller"];
if (controllerName == null) return false;
var actionName = (string) requestContext.RouteData.Values["action"];
if (actionName == null) return false;
if (actionName.EndsWith("Ssl") || controllerName.EndsWith("Ssl")) {
return true;
}
var controller = (actionContext != null
? actionContext.Controller
: ControllerBuilder.Current.GetControllerFactory()
.CreateController(requestContext, controllerName)) as ControllerBase;
if (controller != null) {
var controllerType = controller.GetType();
if (controllerType.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Any()) {
return true;
}
ActionDescriptor actionDescriptor;
if (actionContext != null) {
actionDescriptor = actionContext.ActionDescriptor;
}
else {
var controllerContext = new ControllerContext(requestContext, controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
}
if (actionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Any()) {
return true;
}
}
var settings = GetSettings();
if (settings == null) return false;
if (settings.SecureEverything) return true;
if (controllerName == "Account" &&
(actionName == "LogOn"
|| actionName == "ChangePassword"
|| actionName == "AccessDenied"
|| actionName == "Register"
|| actionName.StartsWith("ChallengeEmail", StringComparison.OrdinalIgnoreCase))) {
return true;
}
if (controllerName == "Admin" || AdminFilter.IsApplied(requestContext)) {
return true;
}
if (!settings.CustomEnabled) return false;
var urlHelper = new UrlHelper(requestContext);
var url = urlHelper.Action(actionName, controllerName, requestContext.RouteData);
return IsRequestProtected(
url, requestContext.HttpContext.Request.ApplicationPath, settings);
}
public string SecureActionUrl(string actionName, string controllerName) {
return SecureActionUrl(actionName, controllerName, new object());
}
public string SecureActionUrl(string actionName, string controllerName, object routeValues) {
return SecureActionUrl(actionName, controllerName, new RouteValueDictionary(routeValues));
}
public string SecureActionUrl(string actionName, string controllerName, RouteValueDictionary routeValues) {
var requestContext = GetRequestContext(actionName, controllerName, routeValues);
var url = new UrlHelper(requestContext);
var actionUrl = url.Action(actionName, controllerName, routeValues);
if (actionUrl == null) return null;
var currentUri = _workContextAccessor.GetContext().HttpContext.Request.Url;
return currentUri != null && currentUri.Scheme.Equals(Uri.UriSchemeHttps) ?
actionUrl /* action url is relative so will keep current protocol */ :
MakeSecure(actionUrl);
}
public string InsecureActionUrl(string actionName, string controllerName) {
return InsecureActionUrl(actionName, controllerName, new object());
}
public string InsecureActionUrl(string actionName, string controllerName, object routeValues) {
return InsecureActionUrl(actionName, controllerName, new RouteValueDictionary(routeValues));
}
public string InsecureActionUrl(string actionName, string controllerName, RouteValueDictionary routeValues) {
var requestContext = GetRequestContext(actionName, controllerName, routeValues);
var url = new UrlHelper(requestContext);
var actionUrl = url.Action(actionName, controllerName, routeValues);
if (actionUrl == null) return null;
var currentUri = _workContextAccessor.GetContext().HttpContext.Request.Url;
return currentUri != null && currentUri.Scheme.Equals(Uri.UriSchemeHttp) ?
actionUrl /* action url is relative so will keep current protocol */ :
MakeInsecure(actionUrl);
}
private RequestContext GetRequestContext(
string actionName,
string controllerName,
RouteValueDictionary routeValues) {
var httpContext = _workContextAccessor.GetContext().HttpContext;
var routeData = new RouteData();
foreach (var routeValue in routeValues) {
routeData.Values[routeValue.Key] = routeValue.Value;
}
routeData.Values["controller"] = controllerName;
routeData.Values["action"] = actionName;
var requestContext = new RequestContext(httpContext, routeData);
return requestContext;
}
private static bool IsRequestProtected(string path, string appPath, SslSettingsPartRecord settings) {
var match = false;
var sr = new StringReader(settings.Urls ?? "");
string pattern;
while (!match && null != (pattern = sr.ReadLine())) {
pattern = pattern.Trim();
match = IsMatched(pattern, path, appPath);
}
return match;
}
private static bool IsMatched(string pattern, string path, string appPath) {
if (pattern.StartsWith("~/")) {
pattern = pattern.Substring(2);
if (appPath == "/")
appPath = "";
pattern = string.Format("{0}/{1}", appPath, pattern);
}
if (!pattern.Contains("?"))
pattern = pattern.TrimEnd('/');
var requestPath = path;
if (!requestPath.Contains("?"))
requestPath = requestPath.TrimEnd('/');
return pattern.EndsWith("*")
? requestPath.StartsWith(pattern.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)
: string.Equals(requestPath, pattern, StringComparison.OrdinalIgnoreCase);
}
private SslSettingsPartRecord GetSettings() {
return _cacheManager.Get("SslSettings", ctx => {
var settings = _workContextAccessor.GetContext().CurrentSite.As<SslSettingsPart>();
return new SslSettingsPartRecord {
CustomEnabled = settings.CustomEnabled,
Urls = settings.Urls,
SecureEverything = settings.SecureEverything,
InsecureHostName = settings.InsecureHostName,
SecureHostName = settings.SecureHostName
};
});
}
private string MakeInsecure(string path) {
var settings = GetSettings();
if (settings == null) return path;
var builder = new UriBuilder {
Scheme = Uri.UriSchemeHttp,
Port = 80
};
var insecureHostName = settings.InsecureHostName;
SetHost(insecureHostName, builder);
builder.Path = path;
return builder.Uri.ToString();
}
private string MakeSecure(string path) {
var settings = GetSettings();
if (settings == null) return path;
var builder = new UriBuilder {
Scheme = Uri.UriSchemeHttps,
Port = 443
};
var secureHostName = settings.SecureHostName;
SetHost(secureHostName, builder);
builder.Path = path;
return builder.Uri.ToString();
}
private static void SetHost(string hostName, UriBuilder builder) {
if (string.IsNullOrWhiteSpace(hostName)) return;
var splitSecuredHostName = hostName.Split(new[] {':'}, StringSplitOptions.RemoveEmptyEntries);
if (splitSecuredHostName.Length == 2) {
int port;
if (int.TryParse(splitSecuredHostName[1], NumberStyles.Integer, CultureInfo.InvariantCulture,
out port)) {
builder.Port = port;
hostName = splitSecuredHostName[0];
}
}
builder.Host = hostName;
}
}
}

View File

@@ -0,0 +1,36 @@
@model Orchard.SecureSocketsLayer.Models.SslSettingsPart
<fieldset>
<legend>@T("SSL Settings")</legend>
<div>
@Html.EditorFor(m => m.SecureEverything)
<label for="@Html.FieldIdFor(m => m.SecureEverything)" class="forcheckbox">@T("Force SSL on all pages")</label>
@Html.ValidationMessage("SecureEverything", "*")
</div>
<div>
<div>
@Html.EditorFor(m => m.CustomEnabled)
<label for="@Html.FieldIdFor(m => m.CustomEnabled)" class="forcheckbox">@T("Enable SSL on specific pages")</label>
@Html.ValidationMessage("CustomEnabled", "*")
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.CustomEnabled)">
<div>
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Urls")</label>
@Html.TextAreaFor(m => m.Urls, new { @class = "textMedium", rows = "5" } )
@Html.ValidationMessage("Urls", "*")
<span class="hint">@T("Provide a list of urls, one per line. Urls can contains wildcard matches using '*', or root identifier like '~/'")</span>
<span class="hint">@T("Examples: http://mysite.com/mypage, ~/Profile/Edit, ~/Profile/*")</span>
</div>
</div>
</div>
<div>
<label for="@Html.FieldIdFor(m => m.SecureHostName)">@T("Secure Host Name")</label>
@Html.TextBoxFor(m => m.SecureHostName, new { @class = "textMedium" })
<span class="hint">@T("Provide the host name secure traffic should be redirected to (e.g. secure.mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. secure.127-0-0-1.org.uk:4333). Leave empty to remain on the same host name (not recommended as this can be a security issue).")</span>
</div>
<div>
<label for="@Html.FieldIdFor(m => m.InsecureHostName)">@T("Insecure Host Name")</label>
@Html.TextBoxFor(m => m.InsecureHostName, new { @class = "textMedium" })
<span class="hint">@T("Provide the host name non-secured traffic should be redirected to (e.g. mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. dev.127-0-0-1.org.uk:4333). Leave empty to remain on the same host name (not recommended as this can be a security issue).")</span>
</div>
</fieldset>

View File

@@ -158,6 +158,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules.Deprecated", "Modul
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure", "Orchard.Web\Modules\Orchard.Azure\Orchard.Azure.csproj", "{CBC7993C-57D8-4A6C-992C-19E849DFE71D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure", "Orchard.Web\Modules\Orchard.Azure\Orchard.Azure.csproj", "{CBC7993C-57D8-4A6C-992C-19E849DFE71D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.SecureSocketsLayer", "Orchard.Web\Modules\Orchard.SecureSocketsLayer\Orchard.SecureSocketsLayer.csproj", "{36B82383-D69E-4897-A24A-648BABDF80EC}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeCoverage|Any CPU = CodeCoverage|Any CPU CodeCoverage|Any CPU = CodeCoverage|Any CPU
@@ -876,6 +878,16 @@ Global
{CBC7993C-57D8-4A6C-992C-19E849DFE71D}.FxCop|Any CPU.Build.0 = Release|Any CPU {CBC7993C-57D8-4A6C-992C-19E849DFE71D}.FxCop|Any CPU.Build.0 = Release|Any CPU
{CBC7993C-57D8-4A6C-992C-19E849DFE71D}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBC7993C-57D8-4A6C-992C-19E849DFE71D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBC7993C-57D8-4A6C-992C-19E849DFE71D}.Release|Any CPU.Build.0 = Release|Any CPU {CBC7993C-57D8-4A6C-992C-19E849DFE71D}.Release|Any CPU.Build.0 = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Coverage|Any CPU.ActiveCfg = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Coverage|Any CPU.Build.0 = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.FxCop|Any CPU.ActiveCfg = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.FxCop|Any CPU.Build.0 = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36B82383-D69E-4897-A24A-648BABDF80EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -934,6 +946,7 @@ Global
{6E444FF1-A47C-4CF6-BB3F-507C8EBD776D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {6E444FF1-A47C-4CF6-BB3F-507C8EBD776D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{8A9FDB57-342D-49C2-BAFC-D885AAE5CC7C} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {8A9FDB57-342D-49C2-BAFC-D885AAE5CC7C} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{CBC7993C-57D8-4A6C-992C-19E849DFE71D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {CBC7993C-57D8-4A6C-992C-19E849DFE71D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{36B82383-D69E-4897-A24A-648BABDF80EC} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
{F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
{6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}