diff --git a/appveyor.yml b/appveyor.yml
index 3db28fd..e842829 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,8 +14,8 @@ image:
environment:
GH_TOKEN:
secure: u7qaOQsrkLqq44yS24C0eM2vRCzp1A8gZTWNmlA58TIDJGmrDXguHL9H/vww7Fg/
- donetsdk: 3.1.406
- donetsdk5: 5.0.102
+ donetsdk3: 3.1.406
+ donetsdk: 5.0.200
JAVA_HOME: C:\Program Files\Java\jdk14
init:
- cmd: git config --global core.autocrlf true
@@ -26,12 +26,11 @@ install:
- sh: sudo apt-get -y install apt-transport-https
- sh: sudo apt-get update
- sh: sudo chmod +x ./dotnet-install.sh
+ - sh: sudo ./dotnet-install.sh -Channel Current -Version $donetsdk3 -InstallDir ./dotnetsdk -NoPath
- sh: sudo ./dotnet-install.sh -Channel Current -Version $donetsdk -InstallDir ./dotnetsdk -NoPath
- - sh: sudo ./dotnet-install.sh -Channel Current -Version $donetsdk5 -InstallDir ./dotnetsdk -NoPath
- sh: export PATH=/home/appveyor/projects/identity-ravendb/dotnetsdk:$PATH
- sh: sudo apt -y install nuget
- ps: if ($isWindows) { .\dotnet-install.ps1 -Version $env:donetsdk }
- - ps: if ($isWindows) { .\dotnet-install.ps1 -Version $env:donetsdk5 }
- ps: dotnet tool install --global GitVersion.Tool
- ps: dotnet gitversion /l console /output buildserver
- ps: dotnet tool install --global dotnet-sonarscanner
diff --git a/samples/IdentitySample/IdentitySample.csproj b/samples/IdentitySample/IdentitySample.csproj
index 72f4be8..ca201b5 100644
--- a/samples/IdentitySample/IdentitySample.csproj
+++ b/samples/IdentitySample/IdentitySample.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Aguacongas.Identity.RavenDb/Aguacongas.Identity.RavenDb.csproj b/src/Aguacongas.Identity.RavenDb/Aguacongas.Identity.RavenDb.csproj
index 19e2653..715050c 100644
--- a/src/Aguacongas.Identity.RavenDb/Aguacongas.Identity.RavenDb.csproj
+++ b/src/Aguacongas.Identity.RavenDb/Aguacongas.Identity.RavenDb.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0;net462
@@ -25,7 +25,7 @@
-
-
+
+
diff --git a/src/Aguacongas.Identity.RavenDb/DocumentStoreExtension.cs b/src/Aguacongas.Identity.RavenDb/DocumentStoreExtension.cs
new file mode 100644
index 0000000..3a4011d
--- /dev/null
+++ b/src/Aguacongas.Identity.RavenDb/DocumentStoreExtension.cs
@@ -0,0 +1,93 @@
+using Aguacongas.Identity.RavenDb;
+using Microsoft.AspNetCore.Identity;
+using System;
+using System.Reflection;
+
+namespace Raven.Client.Documents
+{
+ public static class DocumentStoreExtension
+ {
+ public static IDocumentStore SetFindIdentityPropertyForIdentityModel(this IDocumentStore store)
+ {
+ var findId = store.Conventions.FindIdentityProperty;
+ store.Conventions.FindIdentityProperty = memberInfo => SetConventions(memberInfo, findId);
+ return store;
+ }
+
+ private static bool SetConventions(MemberInfo memberInfo, Func findId)
+ {
+ if (memberInfo.DeclaringType == typeof(UserData))
+ {
+ return false;
+ }
+ if (memberInfo.DeclaringType == typeof(RoleData))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityUser<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityUserClaim<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityUserRole<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityUserLogin<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityUserToken<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityRole<>)))
+ {
+ return false;
+ }
+ if (IsSubclassOf(memberInfo.DeclaringType, typeof(IdentityRoleClaim<>)))
+ {
+ return false;
+ }
+
+ return findId(memberInfo);
+ }
+
+ private static bool IsSubclassOf(Type type, Type baseType)
+ {
+ if (type == null || baseType == null || type == baseType)
+ {
+ return false;
+ }
+
+ if (!baseType.IsGenericType)
+ {
+ if (!type.IsGenericType)
+ {
+ return type.IsSubclassOf(baseType);
+ }
+ }
+ else
+ {
+ baseType = baseType.GetGenericTypeDefinition();
+ }
+
+ var objectType = typeof(object);
+ while (type != objectType && type != null)
+ {
+ var curentType = type.IsGenericType ? type.GetGenericTypeDefinition() : type;
+ if (curentType == baseType)
+ {
+ return true;
+ }
+
+ type = type.BaseType;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Aguacongas.Identity.RavenDb/IdentityBuilderExtensions.cs b/src/Aguacongas.Identity.RavenDb/IdentityBuilderExtensions.cs
index c2f418d..da397c1 100644
--- a/src/Aguacongas.Identity.RavenDb/IdentityBuilderExtensions.cs
+++ b/src/Aguacongas.Identity.RavenDb/IdentityBuilderExtensions.cs
@@ -3,8 +3,6 @@
using Aguacongas.Identity.RavenDb;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System;
diff --git a/src/Aguacongas.Identity.RavenDb/Models/RoleData.cs b/src/Aguacongas.Identity.RavenDb/Models/RoleData.cs
index 03dd56e..e1d1d54 100644
--- a/src/Aguacongas.Identity.RavenDb/Models/RoleData.cs
+++ b/src/Aguacongas.Identity.RavenDb/Models/RoleData.cs
@@ -1,22 +1,15 @@
// Project: Aguafrommars/Identity.RavenDb
// Copyright (c) 2021 Olivier Lefebvre
-using Microsoft.AspNetCore.Identity;
-using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
namespace Aguacongas.Identity.RavenDb
{
- [SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "All are needed")]
- public class RoleData
- where TKey: IEquatable
- where TRole : IdentityRole
- where TRoleClaims: IdentityRoleClaim
+ public class RoleData
{
public string Id { get; set; }
- public virtual TRole Role { get; set; }
+ public string RoleId { get; set; }
- public virtual List Claims { get; private set; } = new List();
+ public List ClaimIds { get; private set; } = new List();
}
}
diff --git a/src/Aguacongas.Identity.RavenDb/Models/UserData.cs b/src/Aguacongas.Identity.RavenDb/Models/UserData.cs
index fd773fd..a10383e 100644
--- a/src/Aguacongas.Identity.RavenDb/Models/UserData.cs
+++ b/src/Aguacongas.Identity.RavenDb/Models/UserData.cs
@@ -1,25 +1,17 @@
// Project: Aguafrommars/Identity.RavenDb
// Copyright (c) 2021 Olivier Lefebvre
-using Microsoft.AspNetCore.Identity;
-using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
namespace Aguacongas.Identity.RavenDb
{
- [SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "All are needed")]
- public class UserData
- where TKey: IEquatable
- where TUser: IdentityUser
- where TUserClaim : IdentityUserClaim
- where TUserLogin : IdentityUserLogin
+ public class UserData
{
public string Id { get; set; }
- public virtual TUser User { get; set; }
+ public string UserId { get; set; }
- public virtual List Claims { get; private set; } = new List();
+ public List ClaimIds { get; private set; } = new List();
- public virtual List Logins { get; private set; } = new List();
+ public List LoginIds { get; private set; } = new List();
}
}
diff --git a/src/Aguacongas.Identity.RavenDb/Models/UserLoginIndex.cs b/src/Aguacongas.Identity.RavenDb/Models/UserLoginIndex.cs
deleted file mode 100644
index c752d20..0000000
--- a/src/Aguacongas.Identity.RavenDb/Models/UserLoginIndex.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Aguacongas.Identity.RavenDb
-{
- public class UserLoginIndex
- {
- public string Id { get; set; }
-
- public string UserId { get; set; }
-
- public string LoginProvider { get; set; }
-
- public string ProviderKey { get; set; }
- }
-}
diff --git a/src/Aguacongas.Identity.RavenDb/RoleStore.cs b/src/Aguacongas.Identity.RavenDb/RoleStore.cs
index d3dbc5d..eedbd91 100644
--- a/src/Aguacongas.Identity.RavenDb/RoleStore.cs
+++ b/src/Aguacongas.Identity.RavenDb/RoleStore.cs
@@ -71,8 +71,7 @@ public class RoleStore :
///
/// A navigation property for the roles the store contains.
///
- public IQueryable Roles => _session.Query>()
- .Select(d => d.Role)
+ public IQueryable Roles => _session.Query()
.ToListAsync().ConfigureAwait(false).GetAwaiter().GetResult().AsQueryable();
///
@@ -104,12 +103,14 @@ public async virtual Task CreateAsync(TRole role, CancellationTo
AssertNotNull(role, nameof(role));
var roleId = ConvertIdToString(role.Id);
- var data = new RoleData
+ var data = new RoleData
{
- Id = $"role/{roleId}",
- Role = role
+ Id = $"roledata/{roleId}",
+ RoleId = $"role/{roleId}"
};
- await _session.StoreAsync(data, cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(role, data.RoleId, cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(data, data.Id, cancellationToken).ConfigureAwait(false);
+
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return IdentityResult.Success;
@@ -128,8 +129,8 @@ public async virtual Task UpdateAsync(TRole role, CancellationTo
AssertNotNull(role, nameof(role));
var roleId = ConvertIdToString(role.Id);
- var data = await _session.LoadAsync>($"role/{roleId}").ConfigureAwait(false);
- data.Role = role;
+ var existing = await _session.LoadAsync($"role/{roleId}").ConfigureAwait(false);
+ CloneEntity(existing, typeof(TRole), role);
try
{
@@ -156,7 +157,13 @@ public async virtual Task DeleteAsync(TRole role, CancellationTo
AssertNotNull(role, nameof(role));
var roleId = ConvertIdToString(role.Id);
- _session.Delete($"role/{roleId}");
+
+ var data = await _session.LoadAsync($"roledata/{roleId}", cancellationToken).ConfigureAwait(false);
+ _session.Delete(data.RoleId);
+ foreach(var claimId in data.ClaimIds)
+ {
+ _session.Delete(claimId);
+ }
_session.Delete($"rolename/{role.NormalizedName}");
try
@@ -224,14 +231,12 @@ public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationTo
/// The role ID to look for.
/// The used to propagate notifications that the operation should be canceled.
/// A that result of the look up.
- public virtual async Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default)
+ public virtual Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- var data = await _session.LoadAsync>($"role/{roleId}", cancellationToken).ConfigureAwait(false);
-
- return data?.Role;
+ return _session.LoadAsync($"role/{roleId}", cancellationToken);
}
///
@@ -255,9 +260,7 @@ public virtual async Task FindByNameAsync(string normalizedRoleName, Canc
return null;
}
- var data = await _session.LoadAsync>(index.RoleId, cancellationToken).ConfigureAwait(false);
-
- return data.Role;
+ return await _session.LoadAsync(index.RoleId, cancellationToken).ConfigureAwait(false);
}
///
@@ -335,7 +338,7 @@ public async virtual Task> GetClaimsAsync(TRole role, CancellationT
ThrowIfDisposed();
AssertNotNull(role, nameof(role));
- var claimList = await GetRoleClaimsAsync(role).ConfigureAwait(false);
+ var claimList = await GetRoleClaimsAsync(role, cancellationToken).ConfigureAwait(false);
return claimList
.Select(c => c.ToClaim())
.ToList();
@@ -354,8 +357,13 @@ public virtual async Task AddClaimAsync(TRole role, Claim claim, CancellationTok
AssertNotNull(role, nameof(role));
AssertNotNull(claim, nameof(claim));
- var roleClaims = await GetRoleClaimsAsync(role).ConfigureAwait(false);
- roleClaims.Add(CreateRoleClaim(role, claim));
+ var roleId = ConvertIdToString(role.Id);
+ var data = await _session.LoadAsync($"roledata/{roleId}", cancellationToken).ConfigureAwait(false);
+ var roleClaim = CreateRoleClaim(role, claim);
+ roleClaim.Id = data.ClaimIds.Count;
+ var claimId = $"roleclaim/{roleId}@{roleClaim.Id}";
+ data.ClaimIds.Add(claimId);
+ await _session.StoreAsync(roleClaim, claimId, cancellationToken).ConfigureAwait(false);
}
///
@@ -371,8 +379,15 @@ public async virtual Task RemoveClaimAsync(TRole role, Claim claim, Cancellation
AssertNotNull(role, nameof(role));
AssertNotNull(claim, nameof(claim));
- var roleClaims = await GetRoleClaimsAsync(role).ConfigureAwait(false);
- roleClaims.RemoveAll(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value);
+ var roleId = ConvertIdToString(role.Id);
+ var claimList = await GetRoleClaimsAsync(role, cancellationToken).ConfigureAwait(false);
+ var data = await _session.LoadAsync($"roledata/{roleId}", cancellationToken).ConfigureAwait(false);
+ foreach(var roleClain in claimList.Where(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value))
+ {
+ var claimId = $"roleclaim/{roleId}@{roleClain.Id}";
+ _session.Delete(claimId);
+ data.ClaimIds.Remove(claimId);
+ }
}
///
@@ -412,11 +427,16 @@ public virtual string ConvertIdToString(TKey id)
return id.ToString();
}
- protected virtual async Task> GetRoleClaimsAsync(TRole role)
+ protected virtual async Task> GetRoleClaimsAsync(TRole role, CancellationToken cancellationToken = default)
{
var roleId = ConvertIdToString(role.Id);
- var data = await _session.LoadAsync>($"role/{roleId}").ConfigureAwait(false);
- return data.Claims;
+ var data = await _session.LoadAsync($"roledata/{roleId}", builder => builder.IncludeDocuments(d => d.ClaimIds), cancellationToken).ConfigureAwait(false);
+ var list = new List(data.ClaimIds.Count);
+ foreach(var id in data.ClaimIds)
+ {
+ list.Add(await _session.LoadAsync(id).ConfigureAwait(false));
+ }
+ return list;
}
private static void AssertNotNull(object p, string pName)
@@ -426,5 +446,13 @@ private static void AssertNotNull(object p, string pName)
throw new ArgumentNullException(pName);
}
}
+
+ private static void CloneEntity(object entity, Type type, object loaded)
+ {
+ foreach (var property in type.GetProperties())
+ {
+ property.SetValue(entity, property.GetValue(loaded));
+ }
+ }
}
}
diff --git a/src/Aguacongas.Identity.RavenDb/Stores/UserStoreBase.cs b/src/Aguacongas.Identity.RavenDb/Stores/UserStoreBase.cs
index 9814f16..d9b57d6 100644
--- a/src/Aguacongas.Identity.RavenDb/Stores/UserStoreBase.cs
+++ b/src/Aguacongas.Identity.RavenDb/Stores/UserStoreBase.cs
@@ -802,24 +802,7 @@ public void Dispose()
/// The user if it exists.
protected abstract Task FindUserAsync(TKey userId, CancellationToken cancellationToken);
- ///
- /// Return a user login with the matching userId, provider, providerKey if it exists.
- ///
- /// The user's id.
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected abstract Task FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken);
-
- ///
- /// Return a user login with provider, providerKey if it exists.
- ///
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected abstract Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken);
+
///
/// Called to create a new instance of a .
diff --git a/src/Aguacongas.Identity.RavenDb/UserOnlyStore.cs b/src/Aguacongas.Identity.RavenDb/UserOnlyStore.cs
index 74ab8ab..bfebe2c 100644
--- a/src/Aguacongas.Identity.RavenDb/UserOnlyStore.cs
+++ b/src/Aguacongas.Identity.RavenDb/UserOnlyStore.cs
@@ -90,8 +90,7 @@ public class UserOnlyStore :
/// A navigation property for the users the store contains.
///
public override IQueryable Users
- => _session.Query>()
- .Select(d => d.User)
+ => _session.Query()
.ToListAsync().ConfigureAwait(false).GetAwaiter().GetResult().AsQueryable();
///
@@ -118,12 +117,13 @@ public async override Task CreateAsync(TUser user, CancellationT
var userId = ConvertIdToString(user.Id);
- var data = new UserData
+ var data = new UserData
{
- Id = $"user/{userId}",
- User = user
+ Id = $"userdata/{userId}",
+ UserId = $"user/{userId}"
};
- await _session.StoreAsync(data, cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(user, data.UserId, cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(data, data.Id, cancellationToken).ConfigureAwait(false);
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return IdentityResult.Success;
@@ -142,9 +142,8 @@ public async override Task UpdateAsync(TUser user, CancellationT
AssertNotNull(user, nameof(user));
var userId = ConvertIdToString(user.Id);
- var data = await _session.LoadAsync>($"user/{userId}", cancellationToken).ConfigureAwait(false);
-
- data.User = user;
+ var existing = await _session.LoadAsync($"user/{userId}", cancellationToken).ConfigureAwait(false);
+ CloneEntity(existing, typeof(TUser), user);
try
{
@@ -171,7 +170,16 @@ public async override Task DeleteAsync(TUser user, CancellationT
AssertNotNull(user, nameof(user));
var userId = ConvertIdToString(user.Id);
- _session.Delete($"user/{userId}");
+ var data = await _session.LoadAsync($"userdata/{userId}", cancellationToken).ConfigureAwait(false);
+ _session.Delete(data.UserId);
+ foreach(var id in data.ClaimIds)
+ {
+ _session.Delete(id);
+ }
+ foreach (var id in data.LoginIds)
+ {
+ _session.Delete(id);
+ }
_session.Delete($"username/{user.NormalizedUserName}");
try
@@ -265,13 +273,12 @@ public override async Task SetNormalizedEmailAsync(TUser user, string normalized
///
/// The that represents the asynchronous operation, containing the user matching the specified if it exists.
///
- public override async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default)
+ public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- var data = await _session.LoadAsync>($"user/{userId}", cancellationToken).ConfigureAwait(false);
- return data?.User;
+ return _session.LoadAsync($"user/{userId}", cancellationToken);
}
///
@@ -295,8 +302,7 @@ public override async Task FindByNameAsync(string normalizedUserName, Can
return null;
}
- var data = await _session.LoadAsync>(index.UserId, cancellationToken).ConfigureAwait(false);
- return data.User;
+ return await _session.LoadAsync(index.UserId, cancellationToken).ConfigureAwait(false);
}
///
@@ -311,7 +317,19 @@ public async override Task> GetClaimsAsync(TUser user, Cancellation
ThrowIfDisposed();
AssertNotNull(user, nameof(user));
- var claimList = await GetUserClaimsAsync(user, cancellationToken).ConfigureAwait(false);
+ var userId = ConvertIdToString(user.Id);
+ var data = await _session.LoadAsync($"userdata/{userId}", builder => builder.IncludeDocuments(d => d.ClaimIds), cancellationToken).ConfigureAwait(false);
+ if (data == null)
+ {
+ return null;
+ }
+
+ var claimList = new List(data.ClaimIds.Count);
+ foreach(var id in data.ClaimIds)
+ {
+ claimList.Add(await _session.LoadAsync(id, cancellationToken).ConfigureAwait(false));
+ }
+
return claimList
.Select(c => c.ToClaim())
.ToList();
@@ -331,8 +349,15 @@ public override async Task AddClaimsAsync(TUser user, IEnumerable claims,
AssertNotNull(user, nameof(user));
AssertNotNull(claims, nameof(claims));
- var claimList = await GetUserClaimsAsync(user, cancellationToken).ConfigureAwait(false);
- claimList.AddRange(claims.Select(c => CreateUserClaim(user, c)));
+ var userId = ConvertIdToString(user.Id);
+ var data = await _session.LoadAsync($"userdata/{userId}", cancellationToken).ConfigureAwait(false);
+ var index = data.ClaimIds.Count;
+ foreach(var claim in claims)
+ {
+ var claimId = $"userclaim/{userId}@{index++}";
+ data.ClaimIds.Add(claimId);
+ await _session.StoreAsync(CreateUserClaim(user, claim), claimId).ConfigureAwait(false);
+ }
}
///
@@ -351,13 +376,17 @@ public async override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newC
AssertNotNull(claim, nameof(claim));
AssertNotNull(newClaim, nameof(newClaim));
- var claimList = await GetUserClaimsAsync(user, cancellationToken).ConfigureAwait(false);
- foreach (var uc in claimList)
+ var userId = ConvertIdToString(user.Id);
+ var data = await _session.LoadAsync($"userdata/{userId}", builder => builder.IncludeDocuments(d => d.ClaimIds), cancellationToken).ConfigureAwait(false);
+
+ foreach (var claimId in data.ClaimIds)
{
- if (uc.ClaimType == claim.Type && uc.ClaimValue == claim.Value)
+ var userClaim = await _session.LoadAsync(claimId, cancellationToken).ConfigureAwait(false);
+ if (userClaim.ClaimType == claim.Type && userClaim.ClaimValue == claim.Value)
{
- uc.ClaimType = newClaim.Type;
- uc.ClaimValue = newClaim.Value;
+ _session.Delete(claimId);
+ await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(CreateUserClaim(user, newClaim), claimId, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -375,10 +404,23 @@ public async override Task RemoveClaimsAsync(TUser user, IEnumerable clai
AssertNotNull(user, nameof(user));
AssertNotNull(claims, nameof(claims));
- var claimList = await GetUserClaimsAsync(user, cancellationToken).ConfigureAwait(false);
- foreach (var claim in claims)
+ var userId = ConvertIdToString(user.Id);
+ var data = await _session.LoadAsync($"userdata/{userId}", builder => builder.IncludeDocuments(d => d.ClaimIds), cancellationToken).ConfigureAwait(false);
+
+ var toDeleteList = new List();
+ foreach (var claimId in data.ClaimIds)
{
- claimList.RemoveAll(uc => uc.ClaimType == claim.Type && uc.ClaimValue == claim.Value);
+ var userClaim = await _session.LoadAsync(claimId, cancellationToken).ConfigureAwait(false);
+ if (claims.Any(c => userClaim.ClaimType == c.Type && userClaim.ClaimValue == c.Value))
+ {
+ toDeleteList.Add(claimId);
+ }
+ }
+
+ foreach(var claimId in toDeleteList)
+ {
+ _session.Delete(claimId);
+ data.ClaimIds.Remove(claimId);
}
}
@@ -398,16 +440,11 @@ public override async Task AddLoginAsync(TUser user, UserLoginInfo login,
AssertNotNull(login, nameof(login));
var userId = ConvertIdToString(user.Id);
- await _session.StoreAsync(new UserLoginIndex
- {
- Id = $"userlogin/{login.LoginProvider}-{login.ProviderKey}",
- UserId = $"user/{userId}",
- LoginProvider = login.LoginProvider,
- ProviderKey = login.ProviderKey
- }).ConfigureAwait(false);
+ var data = await _session.LoadAsync($"userdata/{userId}", cancellationToken).ConfigureAwait(false);
+ var userLoginId = $"userlogin/{login.LoginProvider}@{login.ProviderKey}";
- var logins = await GetUserLoginsAsync(userId, cancellationToken).ConfigureAwait(false);
- logins.Add(CreateUserLogin(user, login));
+ await _session.StoreAsync(CreateUserLogin(user, login), userLoginId, cancellationToken).ConfigureAwait(false);
+ data.LoginIds.Add(userLoginId);
}
///
@@ -425,12 +462,12 @@ public override async Task RemoveLoginAsync(TUser user, string loginProvider, st
ThrowIfDisposed();
AssertNotNull(user, nameof(user));
- _session.Delete($"userlogin/{loginProvider}-{providerKey}");
-
var userId = ConvertIdToString(user.Id);
+ var data = await _session.LoadAsync($"userdata/{userId}", cancellationToken).ConfigureAwait(false);
- var logins = await GetUserLoginsAsync(userId, cancellationToken).ConfigureAwait(false);
- logins.RemoveAll(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
+ var userLoginId = $"userlogin/{loginProvider}@{providerKey}";
+ data.LoginIds.Remove(userLoginId);
+ _session.Delete(userLoginId);
}
///
@@ -449,8 +486,13 @@ public async override Task> GetLoginsAsync(TUser user, Canc
var userId = ConvertIdToString(user.Id);
- var logins = await GetUserLoginsAsync(userId, cancellationToken).ConfigureAwait(false);
- return logins
+ var data = await _session.LoadAsync($"userdata/{userId}", builder => builder.IncludeDocuments(d => d.LoginIds), cancellationToken).ConfigureAwait(false);
+ var list = new List(data.LoginIds.Count);
+ foreach(var id in data.LoginIds)
+ {
+ list.Add(await _session.LoadAsync(id, cancellationToken).ConfigureAwait(false));
+ }
+ return list
.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName))
.ToList();
}
@@ -470,14 +512,14 @@ public override async Task FindByLoginAsync(string loginProvider, string
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- var index = await _session.LoadAsync($"userlogin/{loginProvider}-{providerKey}", cancellationToken).ConfigureAwait(false);
- if (index == null)
+ var login = await _session.LoadAsync($"userlogin/{loginProvider}@{providerKey}", builder => builder.IncludeDocuments(l => $"user/{l.UserId}"), cancellationToken).ConfigureAwait(false);
+ if (login == null)
{
return null;
}
- var data = await _session.LoadAsync>(index.UserId, cancellationToken).ConfigureAwait(false);
- return data.User;
+ var userId = ConvertIdToString(login.UserId);
+ return await _session.LoadAsync($"user/{userId}", cancellationToken).ConfigureAwait(false);
}
///
@@ -499,8 +541,7 @@ public override async Task FindByEmailAsync(string normalizedEmail, Cance
return null;
}
- var data = await _session.LoadAsync>(index.UserId, cancellationToken).ConfigureAwait(false);
- return data?.User;
+ return await _session.LoadAsync(index.UserId, cancellationToken).ConfigureAwait(false);
}
///
@@ -517,11 +558,15 @@ public async override Task> GetUsersForClaimAsync(Claim claim, Canc
ThrowIfDisposed();
AssertNotNull(claim, nameof(claim));
- return await _session.Query>()
- .Where(d => d.Claims.Any(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value))
- .Select(d => d.User)
- .ToListAsync(cancellationToken)
+ var userClaimsList = await _session.Query()
+ .Include(c => $"user/{c.UserId}")
+ .Where(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value)
+ .ToListAsync()
.ConfigureAwait(false);
+
+ var userList = await _session.LoadAsync(userClaimsList.Select(c => $"user/{c.UserId}")
+ , cancellationToken).ConfigureAwait(false);
+ return userList.Where(u => u.Value != null).Select(u => u.Value).ToList();
}
///
@@ -542,7 +587,7 @@ public override async Task SetTokenAsync(TUser user, string loginProvider, strin
ThrowIfDisposed();
var userId = ConvertIdToString(user.Id);
- var token = await _session.LoadAsync($"usertoken/{userId}-{loginProvider}-{name}", cancellationToken).ConfigureAwait(false);
+ var token = await _session.LoadAsync($"usertoken/{userId}@{loginProvider}@{name}", cancellationToken).ConfigureAwait(false);
if (token == null)
{
token = new TUserToken
@@ -552,7 +597,7 @@ public override async Task SetTokenAsync(TUser user, string loginProvider, strin
UserId = user.Id,
Value = value
};
- await _session.StoreAsync(token, $"usertoken/{userId}-{loginProvider}-{name}", cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(token, $"usertoken/{userId}@{loginProvider}@{name}", cancellationToken).ConfigureAwait(false);
}
token.Value = value;
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
@@ -575,7 +620,7 @@ public override async Task RemoveTokenAsync(TUser user, string loginProvider, st
ThrowIfDisposed();
var userId = ConvertIdToString(user.Id);
- _session.Delete($"usertoken/{userId}-{loginProvider}-{name}");
+ _session.Delete($"usertoken/{userId}@{loginProvider}@{name}");
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
@@ -597,35 +642,12 @@ public override async Task GetTokenAsync(TUser user, string loginProvide
var userId = ConvertIdToString(user.Id);
- var token = await _session.LoadAsync($"usertoken/{userId}-{loginProvider}-{name}", cancellationToken).ConfigureAwait(false);
+ var token = await _session.LoadAsync($"usertoken/{userId}@{loginProvider}@{name}", cancellationToken).ConfigureAwait(false);
return token?.Value;
}
- ///
- /// Return a user login with the matching userId, provider, providerKey if it exists.
- ///
- /// The user's id.
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- internal Task FindUserLoginInternalAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
- {
- return FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken);
- }
-
- ///
- /// Return a user login with provider, providerKey if it exists.
- ///
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- internal Task FindUserLoginInternalAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
- {
- return FindUserLoginAsync(loginProvider, providerKey, cancellationToken);
- }
-
+
+
///
/// Return a user with the matching userId if it exists.
@@ -638,51 +660,12 @@ protected override Task FindUserAsync(TKey userId, CancellationToken canc
return FindByIdAsync(userId.ToString(), cancellationToken);
}
- ///
- /// Return a user login with the matching userId, provider, providerKey if it exists.
- ///
- /// The user's id.
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected override async Task FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
+ private static void CloneEntity(object entity, Type type, object loaded)
{
- var data = await GetUserLoginsAsync(userId, cancellationToken).ConfigureAwait(false);
- if (data != null)
+ foreach (var property in type.GetProperties())
{
- return data.FirstOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
+ property.SetValue(entity, property.GetValue(loaded));
}
- return null;
- }
-
- ///
- /// Return a user login with provider, providerKey if it exists.
- ///
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
- {
- return _session.Query>()
- .Where(d => d.Logins.Any(l=> l.LoginProvider == loginProvider && l.ProviderKey == providerKey))
- .Select(d => d.Logins.First(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey))
- .FirstOrDefaultAsync();
- }
-
- protected virtual async Task> GetUserClaimsAsync(TUser user, CancellationToken cancellationToken)
- {
- var userId = ConvertIdToString(user.Id);
- var data = await _session.LoadAsync>($"user/{userId}", cancellationToken).ConfigureAwait(false);
-
- return data.Claims;
- }
-
- protected virtual async Task> GetUserLoginsAsync(string userId, CancellationToken cancellationToken)
- {
- var data = await _session.LoadAsync>($"user/{userId}", cancellationToken).ConfigureAwait(false);
- return data.Logins;
}
}
}
diff --git a/src/Aguacongas.Identity.RavenDb/UserStore.cs b/src/Aguacongas.Identity.RavenDb/UserStore.cs
index 3e54690..2da8387 100644
--- a/src/Aguacongas.Identity.RavenDb/UserStore.cs
+++ b/src/Aguacongas.Identity.RavenDb/UserStore.cs
@@ -199,7 +199,7 @@ public async override Task AddToRoleAsync(TUser user, string roleName, Cancellat
UserId = user.Id
};
- await _session.StoreAsync(userRole, $"userrole/{roleName}-{userId}", cancellationToken).ConfigureAwait(false);
+ await _session.StoreAsync(userRole, $"userrole/{roleName}@{userId}", cancellationToken).ConfigureAwait(false);
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
@@ -226,7 +226,7 @@ public async override Task RemoveFromRoleAsync(TUser user, string roleName, Canc
var userId = ConvertIdToString(user.Id);
- var userRole = await _session.LoadAsync($"userrole/{roleName}-{userId}", cancellationToken).ConfigureAwait(false);
+ var userRole = await _session.LoadAsync($"userrole/{roleName}@{userId}", cancellationToken).ConfigureAwait(false);
_session.Delete(userRole);
await _session.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
@@ -246,10 +246,9 @@ public override async Task> GetRolesAsync(TUser user, Cancellation
var userId = ConvertIdToString(user.Id);
- var userRoleList = await _session.Advanced.LoadStartingWithAsync(idPrefix: "userrole/", matches: $"*-{userId}", token: cancellationToken).ConfigureAwait(false);
- var roles = await _session.LoadAsync>(userRoleList.Select(r => $"role/{ConvertIdToString(r.RoleId)}"), cancellationToken).ConfigureAwait(false);
-
- return roles.Select(r => r.Value.Role.Name).ToList();
+ var userRoleList = await _session.Advanced.LoadStartingWithAsync(idPrefix: "userrole/", matches: $"*@{userId}", token: cancellationToken).ConfigureAwait(false);
+ var roles = await _session.LoadAsync(userRoleList.Select(r => $"role/{ConvertIdToString(r.RoleId)}"), cancellationToken).ConfigureAwait(false);
+ return roles.Where(r => r.Value != null).Select(r => r.Value.Name).ToList();
}
///
@@ -268,7 +267,7 @@ public override async Task IsInRoleAsync(TUser user, string roleName, Canc
AssertNotNullOrEmpty(roleName, nameof(roleName));
var userId = ConvertIdToString(user.Id);
- var userRole = await _session.LoadAsync($"userrole/{roleName}-{userId}", cancellationToken).ConfigureAwait(false);
+ var userRole = await _session.LoadAsync($"userrole/{roleName}@{userId}", cancellationToken).ConfigureAwait(false);
return userRole != null;
}
@@ -395,10 +394,10 @@ public async override Task> GetUsersInRoleAsync(string roleName, Ca
ThrowIfDisposed();
AssertNotNullOrEmpty(roleName, nameof(roleName));
- var userRoleList = await _session.Advanced.LoadStartingWithAsync($"userrole/{roleName}-", token: cancellationToken).ConfigureAwait(false);
- var userList = await _session.LoadAsync>(userRoleList.Select(ur => $"user/{ConvertIdToString(ur.UserId)}"), cancellationToken).ConfigureAwait(false);
+ var userRoleList = await _session.Advanced.LoadStartingWithAsync($"userrole/{roleName}@", token: cancellationToken).ConfigureAwait(false);
+ var userList = await _session.LoadAsync(userRoleList.Select(ur => $"user/{ConvertIdToString(ur.UserId)}"), cancellationToken).ConfigureAwait(false);
- return userList.Select(d => d.Value.User).ToList();
+ return userList.Where(u => u.Value != null).Select(u => u.Value).ToList();
}
///
@@ -481,9 +480,7 @@ protected override async Task FindRoleAsync(string normalizedRoleName, Ca
return null;
}
- var data = await _session.LoadAsync>(index.RoleId, cancellationToken).ConfigureAwait(false);
-
- return data.Role;
+ return await _session.LoadAsync(index.RoleId, cancellationToken).ConfigureAwait(false);
}
///
@@ -495,26 +492,7 @@ protected override async Task FindRoleAsync(string normalizedRoleName, Ca
protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken)
=> FindByIdAsync(userId.ToString(), cancellationToken);
- ///
- /// Return a user login with the matching userId, provider, providerKey if it exists.
- ///
- /// The user's id.
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected override Task FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
- => _userOnlyStore.FindUserLoginInternalAsync(userId, loginProvider, providerKey, cancellationToken);
-
- ///
- /// Return a user login with provider, providerKey if it exists.
- ///
- /// The login provider name.
- /// The key provided by the to identify a user.
- /// The used to propagate notifications that the operation should be canceled.
- /// The user login if it exists.
- protected override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
- => _userOnlyStore.FindUserLoginInternalAsync(loginProvider, providerKey, cancellationToken);
+
protected override void Dispose(bool disposed)
{
@@ -527,8 +505,7 @@ protected virtual async Task FindRoleByIdAsync(string id, CancellationTok
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- var data = await _session.LoadAsync>($"role/{id}", cancellationToken).ConfigureAwait(false);
- return data.Role;
+ return await _session.LoadAsync($"role/{id}", cancellationToken).ConfigureAwait(false);
}
private static void AssertNotNullOrEmpty(string p, string pName)
diff --git a/test/Aguacongas.Identity.RavenDb.IntegrationTest/Aguacongas.Identity.RavenDb.IntegrationTest.csproj b/test/Aguacongas.Identity.RavenDb.IntegrationTest/Aguacongas.Identity.RavenDb.IntegrationTest.csproj
index 14cb8d6..97964f7 100644
--- a/test/Aguacongas.Identity.RavenDb.IntegrationTest/Aguacongas.Identity.RavenDb.IntegrationTest.csproj
+++ b/test/Aguacongas.Identity.RavenDb.IntegrationTest/Aguacongas.Identity.RavenDb.IntegrationTest.csproj
@@ -7,16 +7,16 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
all
diff --git a/test/Aguacongas.Identity.RavenDb.IntegrationTest/RavenDbTestFixture.cs b/test/Aguacongas.Identity.RavenDb.IntegrationTest/RavenDbTestFixture.cs
index df9b78d..801a485 100644
--- a/test/Aguacongas.Identity.RavenDb.IntegrationTest/RavenDbTestFixture.cs
+++ b/test/Aguacongas.Identity.RavenDb.IntegrationTest/RavenDbTestFixture.cs
@@ -1,7 +1,6 @@
// Project: Aguafrommars/Identity.RavenDb
// Copyright (c) 2021 Olivier Lefebvre
using Raven.Client.Documents;
-using Raven.Client.Documents.Session;
using Raven.TestDriver;
using System.Runtime.CompilerServices;
@@ -22,6 +21,12 @@ class RavenDbTestDriverWrapper : RavenTestDriver
{
public new IDocumentStore GetDocumentStore(GetDocumentStoreOptions options = null, [CallerMemberName] string database = null)
=> base.GetDocumentStore(options, database);
+
+ protected override void PreInitialize(IDocumentStore documentStore)
+ {
+ documentStore.SetFindIdentityPropertyForIdentityModel();
+ base.PreInitialize(documentStore);
+ }
}
}
}
diff --git a/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestRole.cs b/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestRole.cs
index 8a58972..0d2ba16 100644
--- a/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestRole.cs
+++ b/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestRole.cs
@@ -1,9 +1,6 @@
// Project: Aguafrommars/Identity.RavenDb
// Copyright (c) 2021 Olivier Lefebvre
using Microsoft.AspNetCore.Identity;
-using System;
-using System.Collections.Generic;
-using System.Text;
namespace Aguacongas.Identity.RavenDb.IntegrationTest
{
diff --git a/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestUser.cs b/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestUser.cs
index c0fe57f..8126895 100644
--- a/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestUser.cs
+++ b/test/Aguacongas.Identity.RavenDb.IntegrationTest/TestUser.cs
@@ -1,9 +1,6 @@
// Project: Aguafrommars/Identity.RavenDb
// Copyright (c) 2021 Olivier Lefebvre
using Microsoft.AspNetCore.Identity;
-using System;
-using System.Collections.Generic;
-using System.Text;
namespace Aguacongas.Identity.RavenDb.IntegrationTest
{