Skip to content

Commit

Permalink
Add Security Module (Lombiq Technologies: OCORE-91) (#11538)
Browse files Browse the repository at this point in the history
  • Loading branch information
hishamco authored May 20, 2022
1 parent f1f639e commit bbe1b14
Show file tree
Hide file tree
Showing 45 changed files with 1,572 additions and 1 deletion.
7 changes: 7 additions & 0 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrchardCore.Themes", "Orcha
src\OrchardCore.Themes\Directory.Build.targets = src\OrchardCore.Themes\Directory.Build.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Security", "src\OrchardCore.Modules\OrchardCore.Security\OrchardCore.Security.csproj", "{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Abstractions.Tests", "test\OrchardCore.Abstractions.Tests\OrchardCore.Abstractions.Tests.csproj", "{FE8011DE-D917-4F74-9955-238B2BBA9165}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrchardCore.Tests.Themes", "OrchardCore.Tests.Themes", "{61436BAE-FB36-4ADA-8E5D-EE64C4E04522}"
Expand Down Expand Up @@ -1172,6 +1174,10 @@ Global
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Release|Any CPU.Build.0 = Release|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Release|Any CPU.Build.0 = Release|Any CPU
{FE8011DE-D917-4F74-9955-238B2BBA9165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE8011DE-D917-4F74-9955-238B2BBA9165}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE8011DE-D917-4F74-9955-238B2BBA9165}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -1416,6 +1422,7 @@ Global
{2BC850C3-9846-47E1-9068-AC0A8E5537AC} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{85EF279B-8F35-476D-9BBD-F503F20712B5} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{21F459C1-494E-41C9-B221-6C102774A47F} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
{FE8011DE-D917-4F74-9955-238B2BBA9165} = {B8D16C60-99B4-43D5-A3AD-4CD89AF39B25}
{61436BAE-FB36-4ADA-8E5D-EE64C4E04522} = {B8D16C60-99B4-43D5-A3AD-4CD89AF39B25}
{A493E5AD-9046-47F3-87A0-0D3AC7EF8699} = {B8D16C60-99B4-43D5-A3AD-4CD89AF39B25}
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ nav:
- Roles: docs/reference/modules/Roles/README.md
- Sanitizer: docs/reference/core/Sanitizer/README.md
- Scripting: docs/reference/modules/Scripting/README.md
- Security: docs/reference/modules/Security/README.md
- Setup: docs/reference/modules/Setup/README.md
- Shells: docs/reference/core/Shells/README.md
- Tenants: docs/reference/modules/Tenants/README.md
Expand Down
6 changes: 6 additions & 0 deletions src/OrchardCore.Cms.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
// "Configuration": "192.168.99.100:6379,allowAdmin=true", // Redis Configuration string.
// "InstancePrefix": "" // Optional prefix allowing a Redis instance to be shared by different applications.
//},
// See https://docs.orchardcore.net/en/latest/docs/reference/modules/Security/#security-settings-configuration to configure security settings.
//"OrchardCore_Security": {
// "ContentSecurityPolicy": {},
// "PermissionsPolicy": { "fullscreen": "self" },
// "ReferrerPolicy": "no-referrer"
//},
// See https://docs.orchardcore.net/en/latest/docs/reference/core/Shells/#enable-azure-shells-configuration to configure shell and tenant configuration in Azure Blob Storage.
// Add a reference to the OrchardCore.Shells.Azure NuGet package.
// Add '.AddAzureShellsConfiguration()' to your Host Startup AddOrchardCms() section.
Expand Down
34 changes: 34 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Security/AdminMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Navigation;
using OrchardCore.Security.Drivers;

namespace OrchardCore.Security
{
public class AdminMenu : INavigationProvider
{
private readonly IStringLocalizer S;

public AdminMenu(IStringLocalizer<AdminMenu> localizer)
{
S = localizer;
}

public Task BuildNavigationAsync(string name, NavigationBuilder builder)
{
if (String.Equals(name, "admin", StringComparison.OrdinalIgnoreCase))
{
builder.Add(S["Security"], NavigationConstants.AdminMenuSecurityPosition, security => security
.AddClass("security").Id("security")
.Add(S["Settings"], settings => settings
.Add(S["Security Headers"], S["Security Headers"].PrefixPosition(), headers => headers
.Permission(SecurityPermissions.ManageSecurityHeadersSettings)
.Action("Index", "Admin", new { area = "OrchardCore.Settings", groupId = SecuritySettingsDisplayDriver.SettingsGroupId })
.LocalNav())));
}

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Environment.Shell;
using OrchardCore.Security.Options;
using OrchardCore.Security.Settings;
using OrchardCore.Security.ViewModels;
using OrchardCore.Settings;

namespace OrchardCore.Security.Drivers
{
public class SecuritySettingsDisplayDriver : SectionDisplayDriver<ISite, SecuritySettings>
{
internal const string SettingsGroupId = "SecurityHeaders";

private readonly IShellHost _shellHost;
private readonly ShellSettings _shellSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly SecuritySettings _securitySettings;

public SecuritySettingsDisplayDriver(
IShellHost shellHost,
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IOptionsSnapshot<SecuritySettings> securitySettings)
{
_shellHost = shellHost;
_shellSettings = shellSettings;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_securitySettings = securitySettings.Value;
}

public override async Task<IDisplayResult> EditAsync(SecuritySettings settings, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, SecurityPermissions.ManageSecurityHeadersSettings))
{
return null;
}

return Initialize<SecuritySettingsViewModel>("SecurityHeadersSettings_Edit", model =>
{
// Set the settings from configuration when AdminSettings are overriden via ConfigureSecuritySettings()
var currentSettings = settings;
if (_securitySettings.FromConfiguration)
{
currentSettings = _securitySettings;
}
model.FromConfiguration = currentSettings.FromConfiguration;
model.ContentSecurityPolicy = currentSettings.ContentSecurityPolicy;
model.PermissionsPolicy = currentSettings.PermissionsPolicy;
model.ReferrerPolicy = currentSettings.ReferrerPolicy;
model.EnableSandbox = currentSettings.ContentSecurityPolicy != null &&
currentSettings.ContentSecurityPolicy.ContainsKey(ContentSecurityPolicyValue.Sandbox);
model.UpgradeInsecureRequests = currentSettings.ContentSecurityPolicy != null &&
currentSettings.ContentSecurityPolicy.ContainsKey(ContentSecurityPolicyValue.UpgradeInsecureRequests);
}).Location("Content:2").OnGroup(SettingsGroupId);
}

public override async Task<IDisplayResult> UpdateAsync(SecuritySettings section, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, SecurityPermissions.ManageSecurityHeadersSettings))
{
return null;
}

if (context.GroupId == SettingsGroupId)
{
var model = new SecuritySettingsViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix);

PrepareContentSecurityPolicyValues(model);

section.ContentTypeOptions = SecurityHeaderDefaults.ContentTypeOptions;
section.ContentSecurityPolicy = model.ContentSecurityPolicy;
section.PermissionsPolicy = model.PermissionsPolicy;
section.ReferrerPolicy = model.ReferrerPolicy;

if (context.Updater.ModelState.IsValid)
{
await _shellHost.ReleaseShellContextAsync(_shellSettings);
}
}

return await EditAsync(section, context);
}

private static void PrepareContentSecurityPolicyValues(SecuritySettingsViewModel model)
{
if (!model.EnableSandbox)
{
model.ContentSecurityPolicy.Remove(ContentSecurityPolicyValue.Sandbox);
}
else if (!model.ContentSecurityPolicy.TryGetValue(ContentSecurityPolicyValue.Sandbox, out _))
{
model.ContentSecurityPolicy[ContentSecurityPolicyValue.Sandbox] = null;
}

if (!model.UpgradeInsecureRequests)
{
model.ContentSecurityPolicy.Remove(ContentSecurityPolicyValue.UpgradeInsecureRequests);
}
else
{
model.ContentSecurityPolicy[ContentSecurityPolicyValue.UpgradeInsecureRequests] = null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Security.Settings;

namespace Microsoft.Extensions.DependencyInjection
{
public static class OrchardCoreBuilderExtensions
{
public static OrchardCoreBuilder ConfigureSecuritySettings(this OrchardCoreBuilder builder)
{
builder.ConfigureServices((tenantServices, serviceProvider) =>
{
var configurationSection = serviceProvider.GetRequiredService<IShellConfiguration>().GetSection("OrchardCore_Security");
tenantServices.PostConfigure<SecuritySettings>(settings =>
{
settings.ContentSecurityPolicy.Clear();
settings.PermissionsPolicy.Clear();
configurationSection.Bind(settings);
settings.FromConfiguration = true;
});
});

return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using OrchardCore.Security.Services;
using OrchardCore.Security.Options;

namespace Microsoft.AspNetCore.Builder
{
public static class SecurityHeadersApplicationBuilderExtensions
{
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
{
ArgumentNullException.ThrowIfNull(app, nameof(app));

return app.UseSecurityHeaders(new SecurityHeadersOptions());
}

public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app, SecurityHeadersOptions options)
{
ArgumentNullException.ThrowIfNull(app, nameof(app));
ArgumentNullException.ThrowIfNull(options, nameof(options));

app.UseMiddleware<SecurityHeadersMiddleware>(options);

return app;
}

public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app, Action<SecurityHeadersOptions> optionsAction)
{
ArgumentNullException.ThrowIfNull(app, nameof(app));
ArgumentNullException.ThrowIfNull(optionsAction, nameof(optionsAction));

var options = new SecurityHeadersOptions();

optionsAction.Invoke(options);

return app.UseSecurityHeaders(options);
}
}
}
10 changes: 10 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Security/Manifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using OrchardCore.Modules.Manifest;

[assembly: Module(
Name = "Security",
Author = ManifestConstants.OrchardCoreTeam,
Website = ManifestConstants.OrchardCoreWebsite,
Version = ManifestConstants.OrchardCoreVersion,
Description = "The Security module adds HTTP headers to follow security best practices.",
Category = "Security"
)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OrchardCore.Security.Options
{
public class ContentSecurityPolicyOriginValue
{
public const string Any = "*";

public const string None = "'none'";

public const string Self = "'self'";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace OrchardCore.Security.Options
{
public class ContentSecurityPolicyValue
{
public const string BaseUri = "base-uri";

public const string ChildSource = "child-src";

public const string ConnectSource = "connect-src";

public const string DefaultSource = "default-src";

public const string FontSource = "font-src";

public const string FormAction = "form-action";

public const string FrameAncestors = "frame-ancestors";

public const string FrameSource = "frame-src";

public const string ImageSource = "img-src";

public const string ManifestSource = "manifest-src";

public const string MediaSource = "media-src";

public const string ObjectSource = "object-src";

public const string ReportUri = "report-uri";

public const string Sandbox = "sandbox";

public const string ScriptSource = "script-src";

public const string StyleSource = "style-src";

public const string UpgradeInsecureRequests = "upgrade-insecure-requests";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OrchardCore.Security.Options
{
public class ContentTypeOptionsValue
{
public const string NoSniff = "nosniff";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OrchardCore.Security.Options
{
public class PermissionsPolicyOriginValue
{
public const string Any = "*";

public const string None = "()";

public const string Self = "self";
}
}
Loading

0 comments on commit bbe1b14

Please sign in to comment.