Skip to content

Commit 403b74a

Browse files
committed
feature: integrate ASP.NET Core Data Protection with Umbraco CMS
1 parent 62f3b4f commit 403b74a

File tree

9 files changed

+6697
-0
lines changed

9 files changed

+6697
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Umbraco.Cms.Core.Composing;
2+
using Umbraco.Cms.Core.DependencyInjection;
3+
4+
namespace Umbraco.Community.DataProtection.Composing;
5+
6+
public class Composer : IComposer
7+
{
8+
public void Compose(IUmbracoBuilder builder)
9+
{
10+
builder.AddUmbracoDataProtection();
11+
}
12+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.AspNetCore.DataProtection;
2+
using Microsoft.AspNetCore.DataProtection.KeyManagement;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Options;
5+
using Umbraco.Cms.Core.DependencyInjection;
6+
using Umbraco.Community.DataProtection.Persistence;
7+
using Umbraco.Extensions;
8+
9+
namespace Umbraco.Community.DataProtection.Composing;
10+
11+
public static class UmbracoBuilderExtensions
12+
{
13+
public static IDataProtectionBuilder AddUmbracoDataProtection(this IUmbracoBuilder builder, string? applicationDiscriminator = null, Action<KeyManagementOptions>? configureOptions = null)
14+
{
15+
builder.ManifestFilters().Append<ManifestFilter>();
16+
builder.PackageMigrationPlans()!.Add<MigrationPlan>();
17+
builder.Services.AddSingleton<UmbracoXmlRepository>();
18+
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
19+
{
20+
var repository = services.GetRequiredService<UmbracoXmlRepository>();
21+
return new ConfigureOptions<KeyManagementOptions>(options =>
22+
{
23+
options.XmlRepository = repository;
24+
options.AutoGenerateKeys = true;
25+
configureOptions?.Invoke(options);
26+
});
27+
});
28+
29+
return builder.Services.AddDataProtection(x =>
30+
{
31+
if (!applicationDiscriminator.IsNullOrWhiteSpace())
32+
{
33+
x.ApplicationDiscriminator = applicationDiscriminator;
34+
}
35+
});
36+
}
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Umbraco.Community.DataProtection;
2+
3+
public static class Constants
4+
{
5+
public const string PackageId = "Umbraco.Community.DataProtection";
6+
public const string PackageName = "Simple Data Protection";
7+
8+
public static class Tables
9+
{
10+
public const string DataProtectionKeys = "DataProtectionKeys";
11+
}
12+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Microsoft.Extensions.Options;
2+
using Umbraco.Cms.Core.Configuration.Models;
3+
using Umbraco.Cms.Core.IO;
4+
using Umbraco.Cms.Core.PropertyEditors;
5+
using Umbraco.Cms.Core.Services;
6+
using Umbraco.Cms.Core.Strings;
7+
using Umbraco.Cms.Infrastructure.Migrations;
8+
using Umbraco.Cms.Infrastructure.Packaging;
9+
using Umbraco.Community.DataProtection.Persistence.Models;
10+
11+
namespace Umbraco.Community.DataProtection.Persistence;
12+
13+
public class DataProtectionMigration : PackageMigrationBase
14+
{
15+
public DataProtectionMigration(
16+
IPackagingService packagingService,
17+
IMediaService mediaService,
18+
MediaFileManager mediaFileManager,
19+
MediaUrlGeneratorCollection mediaUrlGenerators,
20+
IShortStringHelper shortStringHelper,
21+
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider,
22+
IMigrationContext context,
23+
IOptions<PackageMigrationSettings> packageMigrationsSettings) : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider,
24+
context, packageMigrationsSettings)
25+
{
26+
}
27+
28+
protected override void Migrate()
29+
{
30+
if (!TableExists(Constants.Tables.DataProtectionKeys))
31+
{
32+
Create.Table<DataProtectionKey>().Do();
33+
}
34+
}
35+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Umbraco.Cms.Core.Packaging;
2+
3+
namespace Umbraco.Community.DataProtection.Persistence;
4+
5+
public class MigrationPlan : PackageMigrationPlan
6+
{
7+
public MigrationPlan() : base(Constants.PackageId)
8+
{
9+
}
10+
11+
protected override void DefinePlan()
12+
{
13+
To<DataProtectionMigration>(nameof(DataProtectionMigration));
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using NPoco;
2+
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
3+
4+
namespace Umbraco.Community.DataProtection.Persistence.Models;
5+
6+
[TableName(Constants.Tables.DataProtectionKeys)]
7+
public class DataProtectionKey
8+
{
9+
/// <summary>
10+
/// The entity identifier of the <see cref="DataProtectionKey" />.
11+
/// </summary>
12+
[PrimaryKeyColumn(AutoIncrement = true)]
13+
public int Id { get; set; }
14+
15+
/// <summary>
16+
/// The friendly name of the <see cref="DataProtectionKey" />.
17+
/// </summary>
18+
public string? FriendlyName { get; set; }
19+
20+
/// <summary>
21+
/// The XML representation of the <see cref="DataProtectionKey" />.
22+
/// </summary>
23+
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
24+
public string? Xml { get; set; }
25+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Xml.Linq;
2+
using Microsoft.AspNetCore.DataProtection.Repositories;
3+
using Microsoft.Extensions.Logging;
4+
using Umbraco.Cms.Infrastructure.Scoping;
5+
using Umbraco.Community.DataProtection.Persistence.Models;
6+
using Umbraco.Extensions;
7+
8+
namespace Umbraco.Community.DataProtection.Persistence;
9+
10+
public class UmbracoXmlRepository : IXmlRepository
11+
{
12+
private readonly IScopeProvider _scopeProvider;
13+
private readonly ILogger<UmbracoXmlRepository> _logger;
14+
15+
public UmbracoXmlRepository(IScopeProvider scopeProvider, ILogger<UmbracoXmlRepository> logger)
16+
{
17+
_scopeProvider = scopeProvider;
18+
_logger = logger;
19+
}
20+
21+
public IReadOnlyCollection<XElement> GetAllElements()
22+
{
23+
try
24+
{
25+
using var scope = _scopeProvider.CreateScope();
26+
var sql = scope.SqlContext.Sql().SelectAll().From<DataProtectionKey>();
27+
var results = scope.Database.Fetch<DataProtectionKey>(sql);
28+
var output = results.Select(x => XElement.Parse(x.Xml!)).WhereNotNull().ToList().AsReadOnly();
29+
scope.Complete();
30+
return output;
31+
}
32+
catch (Exception ex)
33+
{
34+
_logger.LogError(ex, "Error getting all elements");
35+
return Array.Empty<XElement>();
36+
}
37+
}
38+
39+
public void StoreElement(XElement element, string friendlyName)
40+
{
41+
try
42+
{
43+
using var scope = _scopeProvider.CreateScope(autoComplete: false);
44+
var key = new DataProtectionKey
45+
{
46+
FriendlyName = friendlyName,
47+
Xml = element.ToString(SaveOptions.DisableFormatting)
48+
};
49+
scope.Database.Save(key);
50+
scope.Complete();
51+
}
52+
catch (Exception ex)
53+
{
54+
_logger.LogError(ex, "Error storing element");
55+
}
56+
}
57+
}

src/Umbraco.Community.DataProtection/Umbraco.Community.DataProtection.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
<NuGetAuditLevel>high</NuGetAuditLevel>
2525
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
2626
</PropertyGroup>
27+
28+
<ItemGroup>
29+
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
30+
<PackageReference Include="jcdcdev.Umbraco.Core" Version="0.3.1"/>
31+
</ItemGroup>
32+
2733
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
2834
<PackageReference Include="Umbraco.Cms.Core" Version="[10.4.0,11.0.0)"/>
2935
<PackageReference Include="Umbraco.Cms.Infrastructure" Version="[10.4.0,11.0.0)"/>

0 commit comments

Comments
 (0)