using Autofac; using PKISharp.WACS.Configuration; using PKISharp.WACS.DomainObjects; using PKISharp.WACS.Extensions; using PKISharp.WACS.Plugins.Base.Factories.Null; using PKISharp.WACS.Plugins.Base.Options; using PKISharp.WACS.Plugins.Interfaces; using PKISharp.WACS.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace PKISharp.WACS { internal class RenewalCreator { private readonly IInputService _input; private readonly ILogService _log; private readonly IRenewalStore _renewalStore; private readonly MainArguments _args; private readonly PasswordGenerator _passwordGenerator; private readonly ISettingsService _settings; private readonly IContainer _container; private readonly IAutofacBuilder _scopeBuilder; private readonly ExceptionHandler _exceptionHandler; private readonly RenewalExecutor _renewalExecution; private readonly NotificationService _notification; public RenewalCreator( PasswordGenerator passwordGenerator, MainArguments args, IRenewalStore renewalStore, IContainer container, IInputService input, ILogService log, ISettingsService settings, IAutofacBuilder autofacBuilder, NotificationService notification, ExceptionHandler exceptionHandler, RenewalExecutor renewalExecutor) { _passwordGenerator = passwordGenerator; _renewalStore = renewalStore; _args = args; _input = input; _log = log; _settings = settings; _container = container; _scopeBuilder = autofacBuilder; _exceptionHandler = exceptionHandler; _renewalExecution = renewalExecutor; _notification = notification; } /// /// If renewal is already Scheduled, replace it with the new options /// /// /// private async Task CreateRenewal(Renewal temp, RunLevel runLevel) { // First check by id var existing = _renewalStore.FindByArguments(temp.Id, null).FirstOrDefault(); // If Id has been specified, we don't consider the Friendlyname anymore // So specifying --id becomes a way to create duplicate certificates // with the same --friendlyname in unattended mode. if (existing == null && string.IsNullOrEmpty(_args.Id)) { existing = _renewalStore.FindByArguments(null, temp.LastFriendlyName?.EscapePattern()).FirstOrDefault(); } // This will be a completely new renewal, no further processing needed if (existing == null) { return temp; } // Match found with existing certificate, determine if we want to overwrite // it or create it side by side with the current one. if (runLevel.HasFlag(RunLevel.Interactive)) { _input.CreateSpace(); _input.Show("Existing renewal", existing.ToString(_input)); if (!await _input.PromptYesNo($"Overwrite?", true)) { return temp; } } // Move settings from temporary renewal over to // the pre-existing one that we are overwriting _log.Warning("Overwriting previously created renewal"); existing.Updated = true; existing.TargetPluginOptions = temp.TargetPluginOptions; existing.CsrPluginOptions = temp.CsrPluginOptions; existing.StorePluginOptions = temp.StorePluginOptions; existing.ValidationPluginOptions = temp.ValidationPluginOptions; existing.InstallationPluginOptions = temp.InstallationPluginOptions; return existing; } /// /// Setup a new scheduled renewal /// /// internal async Task SetupRenewal(RunLevel runLevel) { if (_args.Test) { runLevel |= RunLevel.Test; } if (_args.Force) { runLevel |= RunLevel.IgnoreCache; } _log.Information(LogType.All, "Running in mode: {runLevel}", runLevel); var tempRenewal = Renewal.Create(_args.Id, _settings.ScheduledTask.RenewalDays, _passwordGenerator); using var configScope = _scopeBuilder.Configuration(_container, tempRenewal, runLevel); // Choose target plugin var targetPluginOptionsFactory = configScope.Resolve(); if (targetPluginOptionsFactory is INull) { _exceptionHandler.HandleException(message: $"No target plugin could be selected"); return; } var (targetPluginDisabled, targetPluginDisabledReason) = targetPluginOptionsFactory.Disabled; if (targetPluginDisabled) { _exceptionHandler.HandleException(message: $"Target plugin {targetPluginOptionsFactory.Name} is not available. {targetPluginDisabledReason}"); return; } var targetPluginOptions = runLevel.HasFlag(RunLevel.Unattended) ? await targetPluginOptionsFactory.Default() : await targetPluginOptionsFactory.Aquire(_input, runLevel); if (targetPluginOptions == null) { _exceptionHandler.HandleException(message: $"Target plugin {targetPluginOptionsFactory.Name} aborted or failed"); return; } tempRenewal.TargetPluginOptions = targetPluginOptions; // Generate Target and validation plugin choice using var targetScope = _scopeBuilder.Target(_container, tempRenewal, runLevel); var initialTarget = targetScope.Resolve(); if (initialTarget is INull) { _exceptionHandler.HandleException(message: $"Target plugin {targetPluginOptionsFactory.Name} was unable to generate a target"); return; } if (!initialTarget.IsValid(_log)) { _exceptionHandler.HandleException(message: $"Target plugin {targetPluginOptionsFactory.Name} generated an invalid target"); return; } _log.Information("Target generated using plugin {name}: {target}", targetPluginOptions.Name, initialTarget); // Choose FriendlyName if (!string.IsNullOrEmpty(_args.FriendlyName)) { tempRenewal.FriendlyName = _args.FriendlyName; } else if (runLevel.HasFlag(RunLevel.Advanced | RunLevel.Interactive)) { var alt = await _input.RequestString($"Suggested friendly name '{initialTarget.FriendlyName}', press to accept or type an alternative"); if (!string.IsNullOrEmpty(alt)) { tempRenewal.FriendlyName = alt; } } tempRenewal.LastFriendlyName = tempRenewal.FriendlyName ?? initialTarget.FriendlyName; // Choose validation plugin var validationPluginOptionsFactory = targetScope.Resolve(); if (validationPluginOptionsFactory is INull) { _exceptionHandler.HandleException(message: $"No validation plugin could be selected"); return; } // Configure validation try { var validationOptions = runLevel.HasFlag(RunLevel.Unattended) ? await validationPluginOptionsFactory.Default(initialTarget) : await validationPluginOptionsFactory.Aquire(initialTarget, _input, runLevel); if (validationOptions == null) { _exceptionHandler.HandleException(message: $"Validation plugin {validationPluginOptionsFactory.Name} was unable to generate options"); return; } tempRenewal.ValidationPluginOptions = validationOptions; } catch (Exception ex) { _exceptionHandler.HandleException(ex, $"Validation plugin {validationPluginOptionsFactory.Name} aborted or failed"); return; } // Choose order plugin var orderPluginOptionsFactory = targetScope.Resolve(); if (orderPluginOptionsFactory is INull) { _exceptionHandler.HandleException(message: $"No order plugin could be selected"); return; } // Configure order try { var orderOptions = runLevel.HasFlag(RunLevel.Unattended) ? await orderPluginOptionsFactory.Default() : await orderPluginOptionsFactory.Aquire(_input, runLevel); if (orderOptions == null) { _exceptionHandler.HandleException(message: $"Order plugin {orderPluginOptionsFactory.Name} was unable to generate options"); return; } tempRenewal.OrderPluginOptions = orderOptions; } catch (Exception ex) { _exceptionHandler.HandleException(ex, $"Order plugin {orderPluginOptionsFactory.Name} aborted or failed"); return; } // Choose CSR plugin if (initialTarget.CsrBytes == null) { var csrPluginOptionsFactory = configScope.Resolve(); if (csrPluginOptionsFactory is INull) { _exceptionHandler.HandleException(message: $"No CSR plugin could be selected"); return; } // Configure CSR try { var csrOptions = runLevel.HasFlag(RunLevel.Unattended) ? await csrPluginOptionsFactory.Default() : await csrPluginOptionsFactory.Aquire(_input, runLevel); if (csrOptions == null) { _exceptionHandler.HandleException(message: $"CSR plugin {csrPluginOptionsFactory.Name} was unable to generate options"); return; } tempRenewal.CsrPluginOptions = csrOptions; } catch (Exception ex) { _exceptionHandler.HandleException(ex, $"CSR plugin {csrPluginOptionsFactory.Name} aborted or failed"); return; } } // Choose and configure store plugins var resolver = configScope.Resolve(); var storePluginOptionsFactories = new List(); try { while (true) { var storePluginOptionsFactory = await resolver.GetStorePlugin(configScope, storePluginOptionsFactories); if (storePluginOptionsFactory == null) { _exceptionHandler.HandleException(message: $"Store could not be selected"); return; } StorePluginOptions? storeOptions; try { storeOptions = runLevel.HasFlag(RunLevel.Unattended) ? await storePluginOptionsFactory.Default() : await storePluginOptionsFactory.Aquire(_input, runLevel); } catch (Exception ex) { _exceptionHandler.HandleException(ex, $"Store plugin {storePluginOptionsFactory.Name} aborted or failed"); return; } if (storeOptions == null) { _exceptionHandler.HandleException(message: $"Store plugin {storePluginOptionsFactory.Name} was unable to generate options"); return; } var isNull = storePluginOptionsFactory is NullStoreOptionsFactory; if (!isNull || storePluginOptionsFactories.Count == 0) { tempRenewal.StorePluginOptions.Add(storeOptions); storePluginOptionsFactories.Add(storePluginOptionsFactory); } if (isNull) { break; } } } catch (Exception ex) { _exceptionHandler.HandleException(ex, "Invalid selection of store plugins"); return; } // Choose and configure installation plugins var installationPluginFactories = new List(); try { while (true) { var installationPluginOptionsFactory = await resolver.GetInstallationPlugin(configScope, tempRenewal.StorePluginOptions.Select(x => x.Instance), installationPluginFactories); if (installationPluginOptionsFactory == null) { _exceptionHandler.HandleException(message: $"Installation plugin could not be selected"); return; } InstallationPluginOptions installOptions; try { installOptions = runLevel.HasFlag(RunLevel.Unattended) ? await installationPluginOptionsFactory.Default(initialTarget) : await installationPluginOptionsFactory.Aquire(initialTarget, _input, runLevel); } catch (Exception ex) { _exceptionHandler.HandleException(ex, $"Installation plugin {installationPluginOptionsFactory.Name} aborted or failed"); return; } if (installOptions == null) { _exceptionHandler.HandleException(message: $"Installation plugin {installationPluginOptionsFactory.Name} was unable to generate options"); return; } var isNull = installationPluginOptionsFactory is NullInstallationOptionsFactory; if (!isNull || installationPluginFactories.Count == 0) { tempRenewal.InstallationPluginOptions.Add(installOptions); installationPluginFactories.Add(installationPluginOptionsFactory); } if (isNull) { break; } } } catch (Exception ex) { _exceptionHandler.HandleException(ex, "Invalid selection of installation plugins"); return; } // Try to run for the first time var renewal = await CreateRenewal(tempRenewal, runLevel); retry: var result = await _renewalExecution.HandleRenewal(renewal, runLevel); if (result.Abort) { _exceptionHandler.HandleException(message: $"Create certificate cancelled"); } else if (!result.Success) { if (runLevel.HasFlag(RunLevel.Interactive) && await _input.PromptYesNo("Create certificate failed, retry?", false)) { goto retry; } _exceptionHandler.HandleException(message: $"Create certificate failed: {string.Join("\n\t- ", result.ErrorMessages)}"); } else { try { _renewalStore.Save(renewal, result); await _notification.NotifyCreated(renewal, _log.Lines); } catch (Exception ex) { _exceptionHandler.HandleException(ex); } } } } }