Skip to content

Commit

Permalink
Feature/architecture (#4)
Browse files Browse the repository at this point in the history
* Refactor EF.DataProtection.Services
* Update README.md
  • Loading branch information
qdimka authored Mar 22, 2020
1 parent 2bdf00e commit 3dc65fb
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 70 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
+ Flexible
+ Declarative
+ Linq support (with limitations)
+ Supports netstandard 2.0
+ Supports netstandard 2.1

# Status
This library is still under construction and needs to be peer reviewed as well as have features added.

# Usage

Add reference to EntityFramework.DataProtection.Core to your project.
Add reference to EF.DataProtection.Services to your project.

```
dotnet add package EF.DataProtection.Extensions
dotnet add package EF.DataProtection.Services
```

Include the following code to Startup.cs
Expand All @@ -32,14 +32,19 @@ Include the following code to Startup.cs
.UseInternalServiceProvider(p);
})
.AddEfEncryption()
.UseAes256(opt =>
.AddAes256(opt =>
{
opt.Password = "Really_Strong_Password_For_Data";
opt.Salt = "Salt";
})
.UseSha512(opt => { opt.Password = "Really_Strong_Password_For_Data"; });
.AddSha512(opt => { opt.Password = "Really_Strong_Password_For_Data"; });
```

Add reference to EF.DataProtection.Abstractions to your domain model project.

```
dotnet add package EF.DataProtection.Abstractions
```
Mark properties in your entity with attributes `Aes256` or `Sha512`

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>EF.DataProtection.Abstractions</AssemblyName>
<PackageId>EF.DataProtection.Abstractions</PackageId>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
<PackageProjectUrl>https://github.com/qdimka/EntityFramework.DataProtection</PackageProjectUrl>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/EF.DataProtection.Core/EF.DataProtection.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>EF.DataProtection.Core</AssemblyName>
<PackageId>EF.DataProtection.Core</PackageId>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
<PackageProjectUrl>https://github.com/qdimka/EntityFramework.DataProtection</PackageProjectUrl>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>EF.DataProtection.Extensions</AssemblyName>
<PackageId>EF.DataProtection.Extensions</PackageId>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
<PackageProjectUrl>https://github.com/qdimka/EntityFramework.DataProtection</PackageProjectUrl>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
Expand All @@ -17,7 +17,6 @@
<ItemGroup>
<ProjectReference Include="..\EF.DataProtection.Abstractions\EF.DataProtection.Abstractions.csproj" />
<ProjectReference Include="..\EF.DataProtection.Core\EF.DataProtection.Core.csproj" />
<ProjectReference Include="..\EF.DataProtection.Services\EF.DataProtection.Services.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;

namespace EF.DataProtection.Extensions.Extensions
namespace EF.DataProtection.Extensions
{
public class EncryptionServicesBuilder
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using EF.DataProtection.Core.Services;
using Microsoft.Extensions.Options;

namespace EF.DataProtection.Extensions.Extensions
namespace EF.DataProtection.Extensions
{
public class EncryptionsServicesOptionsPostConfigure : IPostConfigureOptions<EncryptionServicesOptions>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Linq;
using EF.DataProtection.Abstractions.Abstractions;
using EF.DataProtection.Core.Customizers;
using EF.DataProtection.Core.Services;
using EF.DataProtection.Services.Aes256;
using EF.DataProtection.Services.Sha512;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

namespace EF.DataProtection.Extensions.Extensions
namespace EF.DataProtection.Extensions
{
public static class ServiceCollectionExtensions
{
Expand All @@ -30,22 +29,28 @@ public static EncryptionServicesBuilder AddEfEncryption(this IServiceCollection

public static class EncryptionServicesBuilderExtensions
{
public static EncryptionServicesBuilder UseAes256(this EncryptionServicesBuilder builder,
Action<Aes256DataEncryptorOptions> configure = null)
{
builder.ServiceCollection.AddSingleton<IDataEncryptor, Aes256DataEncryptor>();

if (configure != null)
builder.ServiceCollection.Configure(configure);
public static EncryptionServicesBuilder AddDataEncryptor<TImplementation, TOptions>(
this EncryptionServicesBuilder builder, Action<TOptions> configure = null)
where TImplementation : IDataEncryptor
where TOptions : class =>
builder.AddDataProtector(typeof(IDataEncryptor), typeof(TImplementation), configure);

return builder;
}

public static EncryptionServicesBuilder UseSha512(this EncryptionServicesBuilder builder,
Action<Sha512DataHasherOptions> configure = null)
public static EncryptionServicesBuilder AddDataHasher<TImplementation, TOptions>(
this EncryptionServicesBuilder builder, Action<TOptions> configure = null)
where TImplementation : IDataHasher
where TOptions : class =>
builder.AddDataProtector(typeof(IDataHasher), typeof(TImplementation), configure);

public static EncryptionServicesBuilder AddDataProtector<TOptions>(
this EncryptionServicesBuilder builder, Type dataProtector,
Type implementation, Action<TOptions> configure = null)
where TOptions : class
{
builder.ServiceCollection.AddSingleton<IDataHasher, Sha512DataHasher>();

builder
.ServiceCollection
.AddSingleton(dataProtector, implementation);

if (configure != null)
builder.ServiceCollection.Configure(configure);

Expand Down
15 changes: 15 additions & 0 deletions src/EF.DataProtection.Sample.Tests/Common/HttpContentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace EF.DataProtection.Sample.Tests.Common
{
public static class HttpContentExtensions
{
public static async Task<T> ReadAsAsync<T>(this HttpContent content)
{
var stream = await content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(stream);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Text;
using System.Threading.Tasks;
using EF.DataProtection.Sample.Controllers;
using EF.DataProtection.Sample.Tests.Common;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Xunit;

Expand All @@ -11,7 +13,7 @@ namespace EF.DataProtection.Sample.Tests.Controller
public class UsersControllerTests: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly HttpClient _httpClient;

public UsersControllerTests(WebApplicationFactory<Startup> factory)
{
_httpClient = factory.CreateClient();
Expand Down Expand Up @@ -40,27 +42,52 @@ public async Task Should_CreateUser_WithoutError()
.GetAsync($"api/users/{userId}");

usersResponse.EnsureSuccessStatusCode();

var userFromDatabase = await usersResponse.Content.ReadAsAsync<UserDto>();

Assert.NotNull(userFromDatabase);
Assert.Equal(user.Email, userFromDatabase.Email);
Assert.Equal(user.SensitiveData, userFromDatabase.SensitiveData);
Assert.Equal(user.PhoneNumber, userFromDatabase.PhoneNumber);
}

[Fact]
public async Task Should_Encrypt_SensitiveFields()
{
var user = new UserCreateModel
{
Email = "email@email.com",
PhoneNumber = "400000000",
SensitiveData = "Some sensitive data"
};

var stream = await usersResponse.Content.ReadAsStringAsync();
var response = await _httpClient
.PostAsync("api/users",
new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json"));

response.EnsureSuccessStatusCode();

var userFromDatabase = JsonConvert.DeserializeObject<UserDto>(stream);
var userId = await response.Content
.ReadAsStringAsync();

var usersResponse = await _httpClient
.GetAsync($"api/users/{userId}/protected");

usersResponse.EnsureSuccessStatusCode();

var userFromDatabase = await usersResponse.Content.ReadAsAsync<UserDto>();

Assert.NotNull(userFromDatabase);
Assert.Equal(user.Email, userFromDatabase.PersonalData.Email);
Assert.Equal(user.SensitiveData, userFromDatabase.PersonalData.SensitiveData);
Assert.Equal(user.PhoneNumber, userFromDatabase.PersonalData.PhoneNumber);
Assert.NotEqual(user.Email, userFromDatabase.Email);
Assert.NotEqual(user.SensitiveData, userFromDatabase.SensitiveData);
Assert.NotEqual(user.PhoneNumber, userFromDatabase.PhoneNumber);
}
}

public class UserDto
{
public long Id { get; set; }

public PersonalDataDto PersonalData { get; set; }
}

public class PersonalDataDto
{
public string PhoneNumber { get; set; }

public string Email { get; set; }
Expand Down
15 changes: 7 additions & 8 deletions src/EF.DataProtection.Sample/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,20 @@ public IActionResult Get(long userId)
.Users
.FirstOrDefault(x => x.Id == userId));

[HttpGet("{userId}/protected")]
public IActionResult GetProtectedData(long userId)
=> Ok(_dbContext
.UserQuery
.FirstOrDefault(x => x.Id == userId));

[HttpGet]
public IActionResult GetAll()
=> Ok(_dbContext.Users.ToList());

[HttpPost]
public IActionResult Post(UserCreateModel createModel)
{
var data = new PersonalData
{
Email = createModel.Email,
PhoneNumber = createModel.PhoneNumber,
SensitiveData = createModel.SensitiveData
};

var user = new User(data);
var user = new User(createModel.PhoneNumber, createModel.Email, createModel.SensitiveData);

_dbContext.Add(user);
_dbContext.SaveChanges();
Expand Down
8 changes: 6 additions & 2 deletions src/EF.DataProtection.Sample/Dal/SampleDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public class SampleDbContext : DbContext
{
public DbSet<User> Users { get; set; }

public DbSet<UserQuery> UserQuery { get; set; }

public SampleDbContext(DbContextOptions<SampleDbContext> options)
:base(options)
{
Expand All @@ -16,8 +18,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<User>()
.OwnsOne<PersonalData>(x => x.PersonalData);
modelBuilder.Entity<UserQuery>()
.ToQuery(() => UserQuery.FromSqlRaw(@"
select Id, PhoneNumber, Email, SensitiveData, PhoneNumberHash from Users"))
.HasNoKey();
}
}
}
2 changes: 0 additions & 2 deletions src/EF.DataProtection.Sample/EF.DataProtection.Sample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EF.DataProtection.Core\EF.DataProtection.Core.csproj" />
<ProjectReference Include="..\EF.DataProtection.Extensions\EF.DataProtection.Extensions.csproj" />
<ProjectReference Include="..\EF.DataProtection.Services\EF.DataProtection.Services.csproj" />
</ItemGroup>

Expand Down
23 changes: 18 additions & 5 deletions src/EF.DataProtection.Sample/Entities/User.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using EF.DataProtection.Services.Aes256;
using EF.DataProtection.Services.Sha512;

namespace EF.DataProtection.Sample.Entities
{
Expand All @@ -8,13 +10,24 @@ public class User

protected User() { }

public User(PersonalData data)
public User(string phoneNumber, string email, string sensitiveData)
{
// ReSharper disable once VirtualMemberCallInConstructor
PersonalData = data
?? throw new ArgumentNullException(nameof(data));
PhoneNumber = phoneNumber;
PhoneNumberHash = phoneNumber;
Email = email;
SensitiveData = sensitiveData;
}

public virtual PersonalData PersonalData { get; protected set; }
[Aes256]
public string PhoneNumber { get; set; }

[Aes256]
public string Email { get; set; }

[Aes256]
public string SensitiveData { get; set; }

[Sha512]
public string PhoneNumberHash { get; protected set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
using EF.DataProtection.Services.Aes256;
using EF.DataProtection.Services.Sha512;

namespace EF.DataProtection.Sample.Entities
{
public class PersonalData
public class UserQuery
{
[Aes256]
public long Id { get; set; }

public string PhoneNumber { get; set; }

[Aes256]

public string Email { get; set; }

[Aes256]

public string SensitiveData { get; set; }

[Sha512]

public string PhoneNumberHash { get; protected set; }
}
}
Loading

0 comments on commit 3dc65fb

Please sign in to comment.