< Summary

Information
Class: ProjectTemplate.Web.Extensions.DataProtectionServiceExtensions
Assembly: ProjectTemplate.Web
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/Extensions/DataProtectionServiceExtensions.cs
Line coverage
95%
Covered lines: 58
Uncovered lines: 3
Coverable lines: 61
Total lines: 122
Line coverage: 95%
Branch coverage
80%
Covered branches: 16
Total branches: 20
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddApplicationDataProtection(...)83.33%1212100%
LoadKeyEncryptionCertificate(...)75%8883.33%

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/Extensions/DataProtectionServiceExtensions.cs

#LineLine coverage
 1using System.Security.Cryptography.X509Certificates;
 2using Microsoft.AspNetCore.DataProtection;
 3using ProjectTemplate.Web.Options;
 4
 5namespace ProjectTemplate.Web.Extensions;
 6
 7/// <summary>
 8/// Provides extension methods for configuring the persistent ASP.NET Core Data Protection key ring.
 9/// </summary>
 10public static class DataProtectionServiceExtensions
 11{
 12    private const string _keyEncryptionPasswordWithoutPathMessage =
 13        "ProjectTemplate:DataProtection:KeyEncryptionCertificatePassword requires KeyEncryptionCertificatePath.";
 14
 15    /// <summary>
 16    /// Registers Data Protection with a stable application discriminator and persistent filesystem key ring.
 17    /// </summary>
 18    /// <param name="services">The service collection to configure.</param>
 19    /// <param name="configuration">The application configuration source.</param>
 20    /// <param name="environment">The current hosting environment.</param>
 21    /// <returns>The same service collection instance for chaining.</returns>
 22    public static IServiceCollection AddApplicationDataProtection(
 23        this IServiceCollection services,
 24        IConfiguration configuration,
 25        IHostEnvironment environment)
 26    {
 11327        ArgumentNullException.ThrowIfNull(services);
 11328        ArgumentNullException.ThrowIfNull(configuration);
 11329        ArgumentNullException.ThrowIfNull(environment);
 30
 11331        IConfigurationSection section = configuration.GetSection(ApplicationDataProtectionOptions.SectionName);
 32
 11333        services
 11334            .AddOptions<ApplicationDataProtectionOptions>()
 11335            .Bind(section)
 11336            .Validate(
 11337                options => !string.IsNullOrWhiteSpace(options.ApplicationName),
 11338                "ProjectTemplate:DataProtection:ApplicationName is required.")
 11339            .Validate(
 11340                options => !string.IsNullOrWhiteSpace(options.KeyRingPath),
 11341                "ProjectTemplate:DataProtection:KeyRingPath is required.")
 11342            .Validate(
 11343                options => string.IsNullOrEmpty(options.KeyEncryptionCertificatePassword) ||
 11344                    !string.IsNullOrWhiteSpace(options.KeyEncryptionCertificatePath),
 11345                _keyEncryptionPasswordWithoutPathMessage)
 11346            .ValidateOnStart();
 47
 11348        ApplicationDataProtectionOptions options = section.Get<ApplicationDataProtectionOptions>() ?? new();
 49
 11350        string applicationName = !string.IsNullOrWhiteSpace(options.ApplicationName)
 11351            ? options.ApplicationName.Trim()
 11352            : throw new InvalidOperationException("ProjectTemplate:DataProtection:ApplicationName is required.");
 11253        string configuredKeyRingPath = !string.IsNullOrWhiteSpace(options.KeyRingPath)
 11254            ? options.KeyRingPath.Trim()
 11255            : throw new InvalidOperationException("ProjectTemplate:DataProtection:KeyRingPath is required.");
 11156        string keyRingPath = Path.IsPathFullyQualified(configuredKeyRingPath)
 11157            ? configuredKeyRingPath
 11158            : Path.GetFullPath(configuredKeyRingPath, environment.ContentRootPath);
 59
 11160        IDataProtectionBuilder dataProtectionBuilder = services
 11161            .AddDataProtection()
 11162            .SetApplicationName(applicationName)
 11163            .PersistKeysToFileSystem(new DirectoryInfo(keyRingPath));
 64
 11165        if (!string.IsNullOrWhiteSpace(options.KeyEncryptionCertificatePath))
 66        {
 367            X509Certificate2 keyEncryptionCertificate = LoadKeyEncryptionCertificate(
 368                options.KeyEncryptionCertificatePath.Trim(),
 369                options.KeyEncryptionCertificatePassword,
 370                environment.ContentRootPath);
 71
 72            // ProtectKeysWithCertificate encrypts new keys. UnprotectKeysWithAnyCertificate supplies the same
 73            // certificate for decryption, which is required on Linux and macOS where the framework cannot resolve it
 74            // from a certificate store by thumbprint.
 275            _ = dataProtectionBuilder
 276                .ProtectKeysWithCertificate(keyEncryptionCertificate)
 277                .UnprotectKeysWithAnyCertificate(keyEncryptionCertificate);
 78        }
 10879        else if (!string.IsNullOrEmpty(options.KeyEncryptionCertificatePassword))
 80        {
 181            throw new InvalidOperationException(_keyEncryptionPasswordWithoutPathMessage);
 82        }
 83
 10984        return services;
 85    }
 86
 87    private static X509Certificate2 LoadKeyEncryptionCertificate(
 88        string configuredCertificatePath,
 89        string? password,
 90        string contentRootPath)
 91    {
 392        string certificatePath = Path.IsPathFullyQualified(configuredCertificatePath)
 393            ? configuredCertificatePath
 394            : Path.GetFullPath(configuredCertificatePath, contentRootPath);
 95
 396        if (!File.Exists(certificatePath))
 97        {
 198            throw new InvalidOperationException(
 199                $"ProjectTemplate:DataProtection:KeyEncryptionCertificatePath '{certificatePath}' was not found.");
 100        }
 101
 102        // EphemeralKeySet avoids writing the private key to the user profile or machine key store. macOS does not
 103        // support ephemeral key sets, so the default storage is used there.
 2104        X509KeyStorageFlags keyStorageFlags = OperatingSystem.IsMacOS()
 2105            ? X509KeyStorageFlags.DefaultKeySet
 2106            : X509KeyStorageFlags.EphemeralKeySet;
 107
 2108        X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
 2109            certificatePath,
 2110            password,
 2111            keyStorageFlags);
 112
 2113        if (!certificate.HasPrivateKey)
 114        {
 0115            certificate.Dispose();
 0116            throw new InvalidOperationException(
 0117                "ProjectTemplate:DataProtection:KeyEncryptionCertificatePath must reference a certificate that includes 
 118        }
 119
 2120        return certificate;
 121    }
 122}