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

168 fix entities being listed as changed due to import #175

Merged
merged 8 commits into from
Aug 24, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,45 @@

namespace Eurofurence.App.Backoffice.Authentication
{
public class BackendAccountClaimsFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public IServiceProvider _serviceProvider { get; set; }

public BackendAccountClaimsFactory(IServiceProvider serviceProvider, IAccessTokenProviderAccessor accessor) : base(accessor)
{
_serviceProvider = serviceProvider;
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
var userAccount = await base.CreateUserAsync(account, options);

if (!(userAccount.Identity?.IsAuthenticated ?? false))
{
return userAccount;
}

if (userAccount.Identity is ClaimsIdentity identity)
{
var usersService = _serviceProvider.GetRequiredService<IUsersService>();
var userRecord = await usersService.GetUserSelf();
foreach (var role in userRecord.Roles)
{
identity.AddClaim(new Claim(identity.RoleClaimType, role));
}
}

return userAccount;

}
}

public class BackendAccountClaimsFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public IServiceProvider _serviceProvider { get; set; }

public BackendAccountClaimsFactory(IServiceProvider serviceProvider, IAccessTokenProviderAccessor accessor) :
base(accessor)
{
_serviceProvider = serviceProvider;
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var userAccount = await base.CreateUserAsync(account, options);

if (!(userAccount.Identity?.IsAuthenticated ?? false))
{
return userAccount;
}

if (userAccount.Identity is ClaimsIdentity identity)
{
var usersService = _serviceProvider.GetRequiredService<IUserService>();
try
{
var userRecord = await usersService.GetUserSelf();
foreach (var role in userRecord.Roles)
{
identity.AddClaim(new Claim(identity.RoleClaimType, role));
}
}
catch (AccessTokenNotAvailableException)
{
// No token, no user record
// AccessTokenNotAvailableException is ignored here because it will be handled by the TokenAuthorizationMessageHandler
}
}

return userAccount;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace Eurofurence.App.Backoffice.Authentication
namespace Eurofurence.App.Backoffice.Authentication;

public class TokenAuthorizationMessageHandler : DelegatingHandler
{
public class TokenAuthorizationMessageHandler : AuthorizationMessageHandler
private readonly IAccessTokenProvider _tokenProvider;
private readonly NavigationManager _navigation;

public TokenAuthorizationMessageHandler(IAccessTokenProvider tokenProvider, NavigationManager navigation)
{
_tokenProvider = tokenProvider;
_navigation = navigation;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
public TokenAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration configuration) : base(provider, navigation)
var result = await _tokenProvider.RequestAccessToken();

if (result.TryGetToken(out var token))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Value);
return await base.SendAsync(request, cancellationToken);
}
else
{
ConfigureHandler(new[] { configuration.GetValue<string>("ApiUrl") ?? string.Empty });
// Redirect to login page if the token is not available
if (!_navigation.Uri.Contains("authentication/login"))
{
_navigation.NavigateTo("authentication/login");
}
throw new AccessTokenNotAvailableException(_navigation, result, null);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin("authentication/login");
Navigation.NavigateTo("authentication/login");
}
}
2 changes: 1 addition & 1 deletion src/Eurofurence.App.Backoffice/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
builder.Services.AddScoped<IArtistAlleyService, ArtistAlleyService>();
builder.Services.AddScoped<IFursuitService, FursuitService>();
builder.Services.AddScoped<IDealerService, DealerService>();
builder.Services.AddScoped<IUsersService, UsersService>();
builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddScoped<TokenAuthorizationMessageHandler>();
builder.Services.AddHttpClient("api", options =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Eurofurence.App.Backoffice.Services
{
public interface IUsersService
public interface IUserService
{
public Task<UserRecord> GetUserSelf();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Eurofurence.App.Backoffice.Services
{
public class UsersService(HttpClient http) : IUsersService
public class UserService(HttpClient http) : IUserService
{
public async Task<UserRecord> GetUserSelf()
{
Expand Down
1 change: 0 additions & 1 deletion src/Eurofurence.App.Backoffice/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="Eurofurence.App.Backoffice.styles.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="css/tiny-mde.min.css" rel="stylesheet" />
</head>
Expand Down
67 changes: 50 additions & 17 deletions src/Eurofurence.App.Common/DataDiffUtils/PatchDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Eurofurence.App.Common.Abstractions;

namespace Eurofurence.App.Common.DataDiffUtils
{
public class PatchDefinition<TSource, TTarget> where TTarget : IEntityBase, new()
{
private readonly List<PatchOperator<TSource, TTarget>> _operators = new List<PatchOperator<TSource, TTarget>>();
private readonly List<PatchOperator<TSource, TTarget>> _operators = [];
private readonly Func<TSource, IEnumerable<TTarget>, TTarget> _targetItemLocator;

/// <param name="targetItemLocator">
/// A selector that, inside an enumerable of TTarget, finds the
/// A selector that, inside an IEnumerable of TTarget, finds the
/// single TTarget that corresponds to the provided TSource
/// </param>
public PatchDefinition(Func<TSource, IEnumerable<TTarget>, TTarget> targetItemLocator)
{
_targetItemLocator = targetItemLocator;
}

private bool ArraysEqual(Array a1, Array a2)
private static bool ArraysEqual(Array a1, Array a2)
{
if (a1.Length == a2.Length)
{
Expand All @@ -39,6 +41,35 @@ private bool ArraysEqual(Array a1, Array a2)
return false;
}

private static bool CollectionsEqual(ICollection collection1, ICollection collection2)
{
if (collection1.Count == collection2.Count)
{
var a1e = collection1.GetEnumerator();
var a2e = collection2.GetEnumerator();

for (int i = 0; i < collection1.Count; i++)
{
if (!a1e.MoveNext() || !a2e.MoveNext())
{
return false;
}

if (!a1e.Current.Equals(a2e.Current))
return false;
}
return true;
}
return false;
}

private static bool DictionariesEqual(IDictionary dictionary1, IDictionary dictionary2)
{
var string1 = JsonSerializer.Serialize(dictionary1);
var string2 = JsonSerializer.Serialize(dictionary2);
return string1 == string2;
}

public PatchDefinition<TSource, TTarget> Map<TField>(
Func<TSource, TField> sourceValueSelector,
Expression<Func<TTarget, TField>> targetSelector)
Expand All @@ -54,21 +85,29 @@ public PatchDefinition<TSource, TTarget> Map<TField>(
if (sourceValue == null && targetValue == null)
return true;

if (sourceValue == null && targetValue != null || sourceValue != null && targetValue == null)
if (sourceValue == null || targetValue == null)
return false;

if (sourceValue.GetType().IsArray)
return ArraysEqual((sourceValue as Array), (targetValue as Array));

return sourceValue?.Equals(targetValue) ?? false;
if (sourceValue is IDictionary &&
sourceValue.GetType().IsGenericType)
return DictionariesEqual((IDictionary)sourceValue, (IDictionary)targetValue);

if (sourceValue is ICollection &&
sourceValue.GetType().IsGenericType)
return CollectionsEqual((ICollection)sourceValue, (ICollection)targetValue);

return (bool)sourceValue?.Equals(targetValue);
},
ApplySourceValueToTarget =
(source, target) =>
{
var value = sourceValueSelector(source);
var memberSelectorExpression = targetSelector.Body as MemberExpression;
if (targetSelector.Body is not MemberExpression memberSelectorExpression) return;
var property = memberSelectorExpression.Member as PropertyInfo;
property.SetValue(target, value, null);
if (property != null) property.SetValue(target, value, null);
}
};

Expand All @@ -84,10 +123,11 @@ public List<PatchOperation<TTarget>> Patch(IEnumerable<TSource> sources, IEnumer

var unprocessedTargets = targets.ToList();
var newTargets = new List<TTarget>();
if (newTargets == null) throw new ArgumentNullException(nameof(newTargets));

foreach (var sourceItem in sources)
{
var target = default(TTarget);
TTarget target;

var result = new PatchOperation<TTarget> {Action = ActionEnum.NotModified};

Expand All @@ -107,10 +147,8 @@ public List<PatchOperation<TTarget>> Patch(IEnumerable<TSource> sources, IEnumer

result.Entity = target;

foreach (var o in _operators)
foreach (var o in _operators.Where(o => !o.IsEqual(sourceItem, target)))
{
if (o.IsEqual(sourceItem, target)) continue;

if (result.Action == ActionEnum.NotModified)
result.Action = ActionEnum.Update;

Expand All @@ -120,12 +158,7 @@ public List<PatchOperation<TTarget>> Patch(IEnumerable<TSource> sources, IEnumer
patchResults.Add(result);
}

foreach (var targetItem in unprocessedTargets)
patchResults.Add(new PatchOperation<TTarget>
{
Action = ActionEnum.Delete,
Entity = targetItem
});
patchResults.AddRange(unprocessedTargets.Select(targetItem => new PatchOperation<TTarget> { Action = ActionEnum.Delete, Entity = targetItem }));

return patchResults;
}
Expand Down