Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe Secrets Section #6421

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Newtonsoft.Json.Linq;
using OrchardCore.Admin;
using OrchardCore.Deployment.Core.Mvc;
using OrchardCore.Deployment.Core.Services;
using OrchardCore.Deployment.Services;
using OrchardCore.Deployment.Steps;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.Mvc.Utilities;
using OrchardCore.Recipes.Models;
using YesSql;
Expand All @@ -22,20 +25,27 @@ public class ExportFileController : Controller
private readonly IDeploymentManager _deploymentManager;
private readonly IAuthorizationService _authorizationService;
private readonly ISession _session;
private readonly INotifier _notifier;
private readonly IHtmlLocalizer H;

public ExportFileController(
IAuthorizationService authorizationService,
ISession session,
IDeploymentManager deploymentManager)
IDeploymentManager deploymentManager,
INotifier notifier,
IHtmlLocalizer<ExportFileController> htmlLocalizer
)
{
_authorizationService = authorizationService;
_deploymentManager = deploymentManager;
_session = session;
_notifier = notifier;
H = htmlLocalizer;
}

[HttpPost]
[DeleteFileResultFilter]
public async Task<IActionResult> Execute(int id)
public async Task<IActionResult> Execute(int id, string returnUrl)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.Export))
{
Expand Down Expand Up @@ -74,6 +84,19 @@ public async Task<IActionResult> Execute(int id)

var deploymentPlanResult = new DeploymentPlanResult(fileBuilder, recipeDescriptor);
await _deploymentManager.ExecuteDeploymentPlanAsync(deploymentPlan, deploymentPlanResult);
bool hasPlainTextSecrets = false;
if (deploymentPlanResult.Secrets != null && deploymentPlanResult.Secrets.HasValues)
{
hasPlainTextSecrets = deploymentPlanResult.Secrets.SelectTokens($"$..{nameof(RecipeSecret.Handler)}")
.Select(t => t.Value<string>())
.Any(v => String.Equals(v, RecipeSecretHandler.PlainText.ToString(), StringComparison.OrdinalIgnoreCase));
}
if (hasPlainTextSecrets)
{
_notifier.Error(H["You cannot export a deployment plan containing plain text secrets to a file"]);
return LocalRedirect(returnUrl);
}

ZipFile.CreateFromDirectory(fileBuilder.Folder, archiveFileName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
{
var routeWithId = new RouteValueDictionary(target.Route);
routeWithId["id"] = Model.DeploymentPlan.Id;
routeWithId["returnUrl"] = @FullRequestPath;
<div class="col-sm-12 col-md-6 col-lg-4 d-flex align-items-stretch card-item">
<div class="card w-100 mb-3">
<div class="card-body">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OrchardCore.Deployment;
using OrchardCore.Email.Services;
using OrchardCore.Entities;
using OrchardCore.Settings;

namespace OrchardCore.Email.Deployment
{
public class SmtpSettingsDeploymentSource : IDeploymentSource
{
private readonly ISiteService _siteService;
private readonly IDataProtectionProvider _dataProtectionProvider;
private ILogger _logger;

public SmtpSettingsDeploymentSource(ISiteService siteService,
IDataProtectionProvider dataProtectionProvider,
ILogger<SmtpSettingsDeploymentSource> logger)
{
_siteService = siteService;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
}

public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlanResult result)
{
var smtpSettingsStep = step as SmtpSettingsDeploymentStep;

if (smtpSettingsStep == null)
{
return;
}

var smtpSettings = (await _siteService.GetSiteSettingsAsync()).As<SmtpSettings>();

var jObject = JObject.FromObject(smtpSettings);

jObject.Remove("Password");

if (smtpSettingsStep.Password != DeploymentSecretHandler.Ignored)
{
// Default to cover error handling if protector is unsuccessful.
var passwordRecipeSecret = new RecipeSecret();

// The deployment step secret is added to the deployment step itself.
var passwordDeploymentStepSecret = new DeploymentStepSecret("smtpPassword");

switch (smtpSettingsStep.Password)
{
case DeploymentSecretHandler.Encrypted:
passwordRecipeSecret = new RecipeSecret
{
Handler = RecipeSecretHandler.Encrypted,
Value = smtpSettings.Password
};
break;
case DeploymentSecretHandler.PlainText:
if (!String.IsNullOrWhiteSpace(smtpSettings.Password))
{
try
{
var protector = _dataProtectionProvider.CreateProtector(nameof(SmtpSettingsConfiguration));
passwordRecipeSecret = new RecipeSecret
{
Handler = RecipeSecretHandler.PlainText,
Value = protector.Unprotect(smtpSettings.Password)
};
}
catch
{
_logger.LogError("The Smtp password could not be decrypted. It may have been encrypted using a different key.");
}
}
break;
default:
break;

};

// Add the deployment secret to the `SmtpSettings` step section.
jObject.Add(new JProperty("PasswordSecret", JObject.FromObject(passwordDeploymentStepSecret)));

// Add the secrets value to the 'secrets' section in the recipe.
result.Secrets["smtpPassword"] = JObject.FromObject(passwordRecipeSecret);
}

// Adding Smtp settings
result.Steps.Add(new JObject(
new JProperty("name", "SmtpSettings"),
new JProperty("SmtpSettings", jObject)
));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using OrchardCore.Deployment;

namespace OrchardCore.Email.Deployment
{
/// <summary>
/// Adds smtp settings to a <see cref="DeploymentPlanResult"/>.
/// </summary>
public class SmtpSettingsDeploymentStep : DeploymentStep
{
public SmtpSettingsDeploymentStep()
{
Name = "SmtpSettings";
}

public DeploymentSecretHandler Password { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Email.ViewModels;

namespace OrchardCore.Email.Deployment
{
public class SmtpSettingsDeploymentStepDriver : DisplayDriver<DeploymentStep, SmtpSettingsDeploymentStep>
{
public override IDisplayResult Display(SmtpSettingsDeploymentStep step)
{
return
Combine(
View("SmtpSettingsDeploymentStep_Fields_Summary", step).Location("Summary", "Content"),
View("SmtpSettingsDeploymentStep_Fields_Thumbnail", step).Location("Thumbnail", "Content")
);
}
public override IDisplayResult Edit(SmtpSettingsDeploymentStep step)
{
return Initialize<SmtpSettingsDeploymentStepViewModel>("SmtpSettingsDeploymentStep_Fields_Edit", model =>
{
model.Password = step.Password;
}).Location("Content");
}

public override async Task<IDisplayResult> UpdateAsync(SmtpSettingsDeploymentStep step, IUpdateModel updater)
{
await updater.TryUpdateModelAsync(step, Prefix, x => x.Password);

return Edit(step);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

<ItemGroup>
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Admin.Abstractions\OrchardCore.Admin.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Email.Abstractions\OrchardCore.Email.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Deployment.Abstractions\OrchardCore.Deployment.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Navigation.Core\OrchardCore.Navigation.Core.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Email.Abstractions\OrchardCore.Email.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Module.Targets\OrchardCore.Module.Targets.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Navigation.Core\OrchardCore.Navigation.Core.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Recipes.Abstractions\OrchardCore.Recipes.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ResourceManagement\OrchardCore.ResourceManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Workflows.Abstractions\OrchardCore.Workflows.Abstractions.csproj" />
</ItemGroup>
Expand Down
115 changes: 115 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Email/Recipes/SmtpSettingsStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OrchardCore.Deployment;
using OrchardCore.Email.Services;
using OrchardCore.Entities;
using OrchardCore.Recipes.Models;
using OrchardCore.Recipes.Services;
using OrchardCore.Settings;

namespace OrchardCore.Email.Recipes
{
public class SmtpSettingsStep : IRecipeStepHandler
{
private readonly ISiteService _siteService;
private readonly IDataProtectionProvider _dataProtectionProvider;
private ILogger _logger;

public SmtpSettingsStep(ISiteService siteService,
IDataProtectionProvider dataProtectionProvider,
ILogger<SmtpSettingsStep> logger
)
{
_siteService = siteService;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
}

public async Task ExecuteAsync(RecipeExecutionContext context)
{
if (!string.Equals(context.Name, "SmtpSettings", StringComparison.OrdinalIgnoreCase))
{
return;
}

var jStepSettings = context.Step["SmtpSettings"] as JObject;
if (jStepSettings == null)
{
return;
}


var passwordSecret = jStepSettings["PasswordSecret"]?.ToObject<RecipeSecret>();

string password = String.Empty;
if (passwordSecret != null)
{
// Remove the secret from the step
jStepSettings.Remove("PasswordSecret");

switch (passwordSecret.Handler)
{
case RecipeSecretHandler.PlainText:
if (!String.IsNullOrEmpty(passwordSecret.Value))
{
// Encrypt the password as it was supplied in plain text by the user.
password = EncryptPassword(passwordSecret.Value);
}
else
{
_logger.LogError("Password secret not provided for 'SmtpSettings'");
}
break;
case RecipeSecretHandler.Encrypted:
if (!String.IsNullOrEmpty(passwordSecret.Value))
{
try
{
var protector = _dataProtectionProvider.CreateProtector(nameof(SmtpSettingsConfiguration));
// Decrypt the password to test if the encryption keys are valid.
protector.Unprotect(passwordSecret.Value);
// On success we can store the supplied value.
password = passwordSecret.Value;
}
catch
{
_logger.LogError("The Smtp password could not be decrypted. It may have been encrypted using a different key.");
}
}
break;
default:
break;
}

}

var site = await _siteService.LoadSiteSettingsAsync();
// Retrieve the curent site settings first and merge them with the step values so we don't replace an ignored value.
var jSiteSettings = site.Properties["SmtpSettings"] as JObject;

if (jSiteSettings != null)
{
jStepSettings.Merge(jSiteSettings);
}

var model = jStepSettings.ToObject<SmtpSettings>();

if (!String.IsNullOrEmpty(password))
{
model.Password = password;
}

site.Put(model);
await _siteService.UpdateSiteSettingsAsync(site);
}

private string EncryptPassword(string password)
{
var protector = _dataProtectionProvider.CreateProtector(nameof(SmtpSettingsConfiguration));
return protector.Protect(password);
}
}
}
17 changes: 17 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Email/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.Admin;
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Email.Controllers;
using OrchardCore.Email.Deployment;
using OrchardCore.Email.Drivers;
using OrchardCore.Email.Recipes;
using OrchardCore.Email.Services;
using OrchardCore.Modules;
using OrchardCore.Mvc.Core.Utilities;
using OrchardCore.Navigation;
using OrchardCore.Recipes;
using OrchardCore.Security.Permissions;
using OrchardCore.Settings;

Expand All @@ -33,6 +37,8 @@ public override void ConfigureServices(IServiceCollection services)

services.AddTransient<IConfigureOptions<SmtpSettings>, SmtpSettingsConfiguration>();
services.AddScoped<ISmtpService, SmtpService>();

services.AddRecipeExecutionStep<SmtpSettingsStep>();
}

public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
Expand All @@ -45,4 +51,15 @@ public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilde
);
}
}

[RequireFeatures("OrchardCore.Deployment")]
public class DeploymentStartup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDeploymentSource, SmtpSettingsDeploymentSource>();
services.AddSingleton<IDeploymentStepFactory>(new DeploymentStepFactory<SmtpSettingsDeploymentStep>());
services.AddScoped<IDisplayDriver<DeploymentStep>, SmtpSettingsDeploymentStepDriver>();
}
}
}
Loading