diff --git a/src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj b/src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj
index dff5093fa..fbae8f950 100644
--- a/src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj
+++ b/src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj b/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj
index 62570d28e..8f024eb56 100644
--- a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj
+++ b/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoRole.cs b/src/Squidex.Domain.Users.MongoDb/MongoRole.cs
new file mode 100644
index 000000000..682ef8b9c
--- /dev/null
+++ b/src/Squidex.Domain.Users.MongoDb/MongoRole.cs
@@ -0,0 +1,28 @@
+// ==========================================================================
+// MongoRole.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Squidex.Domain.Users.MongoDb
+{
+ public sealed class MongoRole : IRole
+ {
+ [BsonRepresentation(BsonType.ObjectId)]
+ [BsonElement]
+ public string Id { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string Name { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string NormalizedName { get; set; }
+ }
+}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs b/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs
index 2ff23c1fc..c7340d010 100644
--- a/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs
+++ b/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs
@@ -9,82 +9,101 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Identity.MongoDB;
using MongoDB.Driver;
+using Squidex.Infrastructure.MongoDb;
+using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Users.MongoDb
{
- public sealed class MongoRoleStore : IRoleStore, IRoleFactory
+ public sealed class MongoRoleStore : MongoRepositoryBase, IRoleStore, IRoleFactory
{
- private readonly RoleStore innerStore;
-
public MongoRoleStore(IMongoDatabase database)
+ : base(database)
{
- var rolesCollection = database.GetCollection("Identity_Roles");
+ }
- IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection);
+ protected override string CollectionName()
+ {
+ return "Identity_Roles";
+ }
- innerStore = new RoleStore(rolesCollection);
+ protected override Task SetupCollectionAsync(IMongoCollection collection)
+ {
+ return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedName), new CreateIndexOptions { Unique = true });
+ }
+
+ protected override MongoCollectionSettings CollectionSettings()
+ {
+ return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public void Dispose()
{
- innerStore.Dispose();
}
public IRole Create(string name)
{
- return new WrappedIdentityRole { Name = name };
+ return new MongoRole { Name = name };
}
public async Task FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
- return await innerStore.FindByIdAsync(roleId, cancellationToken);
+ return await Collection.Find(x => x.Id == roleId).FirstOrDefaultAsync(cancellationToken);
}
public async Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
{
- return await innerStore.FindByNameAsync(normalizedRoleName, cancellationToken);
+ return await Collection.Find(x => x.NormalizedName == normalizedRoleName).FirstOrDefaultAsync(cancellationToken);
}
- public Task CreateAsync(IRole role, CancellationToken cancellationToken)
+ public async Task CreateAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.CreateAsync((WrappedIdentityRole)role, cancellationToken);
+ await Collection.InsertOneAsync((MongoRole)role, null, cancellationToken);
+
+ return IdentityResult.Success;
}
- public Task UpdateAsync(IRole role, CancellationToken cancellationToken)
+ public async Task UpdateAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.UpdateAsync((WrappedIdentityRole)role, cancellationToken);
+ await Collection.ReplaceOneAsync(x => x.Id == ((MongoRole)role).Id, (MongoRole)role, null, cancellationToken);
+
+ return IdentityResult.Success;
}
- public Task DeleteAsync(IRole role, CancellationToken cancellationToken)
+ public async Task DeleteAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.DeleteAsync((WrappedIdentityRole)role, cancellationToken);
+ await Collection.DeleteOneAsync(x => x.Id == ((MongoRole)role).Id, null, cancellationToken);
+
+ return IdentityResult.Success;
}
public Task GetRoleIdAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.GetRoleIdAsync((WrappedIdentityRole)role, cancellationToken);
+ return Task.FromResult(((MongoRole)role).Id);
}
public Task GetRoleNameAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.GetRoleNameAsync((WrappedIdentityRole)role, cancellationToken);
+ return Task.FromResult(((MongoRole)role).Name);
}
- public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken)
+ public Task GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken)
{
- return innerStore.SetRoleNameAsync((WrappedIdentityRole)role, roleName, cancellationToken);
+ return Task.FromResult(((MongoRole)role).NormalizedName);
}
- public Task GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken)
+ public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken)
{
- return innerStore.GetNormalizedRoleNameAsync((WrappedIdentityRole)role, cancellationToken);
+ ((MongoRole)role).Name = roleName;
+
+ return TaskHelper.Done;
}
public Task SetNormalizedRoleNameAsync(IRole role, string normalizedName, CancellationToken cancellationToken)
{
- return innerStore.SetNormalizedRoleNameAsync((WrappedIdentityRole)role, normalizedName, cancellationToken);
+ ((MongoRole)role).NormalizedName = normalizedName;
+
+ return TaskHelper.Done;
}
}
}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUser.cs b/src/Squidex.Domain.Users.MongoDb/MongoUser.cs
new file mode 100644
index 000000000..191a66016
--- /dev/null
+++ b/src/Squidex.Domain.Users.MongoDb/MongoUser.cs
@@ -0,0 +1,199 @@
+// ==========================================================================
+// MongoUser.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Identity;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using Squidex.Infrastructure;
+using Squidex.Shared.Users;
+
+namespace Squidex.Domain.Users.MongoDb
+{
+ public sealed class MongoUser : IUser
+ {
+ [BsonRepresentation(BsonType.ObjectId)]
+ [BsonElement]
+ public string Id { get; set; }
+
+ [BsonIgnoreIfNull]
+ [BsonElement]
+ public string SecurityStamp { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string UserName { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string NormalizedUserName { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string Email { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string NormalizedEmail { get; set; }
+
+ [BsonIgnoreIfNull]
+ [BsonElement]
+ public string PhoneNumber { get; set; }
+
+ [BsonIgnoreIfNull]
+ [BsonElement]
+ public string PasswordHash { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public bool EmailConfirmed { get; set; }
+
+ [BsonIgnoreIfDefault]
+ [BsonElement]
+ public bool PhoneNumberConfirmed { get; set; }
+
+ [BsonIgnoreIfDefault]
+ [BsonElement]
+ public bool TwoFactorEnabled { get; set; }
+
+ [BsonIgnoreIfDefault]
+ [BsonElement]
+ public bool LockoutEnabled { get; set; }
+
+ [BsonIgnoreIfNull]
+ [BsonElement]
+ public DateTime? LockoutEndDateUtc { get; set; }
+
+ [BsonIgnoreIfDefault]
+ [BsonElement]
+ public int AccessFailedCount { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public List Roles { get; set; } = new List();
+
+ [BsonRequired]
+ [BsonElement]
+ public List Claims { get; set; } = new List();
+
+ [BsonRequired]
+ [BsonElement]
+ public List Tokens { get; set; } = new List();
+
+ [BsonRequired]
+ [BsonElement]
+ public List Logins { get; set; } = new List();
+
+ public bool IsLocked
+ {
+ get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; }
+ }
+
+ IReadOnlyList IUser.Claims
+ {
+ get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); }
+ }
+
+ IReadOnlyList IUser.Logins
+ {
+ get { return Logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey, x.ProviderDisplayName)).ToList(); }
+ }
+
+ public MongoUser()
+ {
+ Id = ObjectId.GenerateNewId().ToString();
+ }
+
+ public void UpdateEmail(string email)
+ {
+ Email = UserName = email;
+ }
+
+ public void AddRole(string role)
+ {
+ Roles.Add(role);
+ }
+
+ public void RemoveRole(string role)
+ {
+ Roles.Remove(role);
+ }
+
+ public void AddLogin(UserLoginInfo login)
+ {
+ Logins.Add(login);
+ }
+
+ public void RemoveLogin(string loginProvider, string providerKey)
+ {
+ Logins.RemoveAll(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
+ }
+
+ public void AddClaim(Claim claim)
+ {
+ Claims.Add(claim);
+ }
+
+ public void AddClaims(IEnumerable claims)
+ {
+ claims.Foreach(AddClaim);
+ }
+
+ public void RemoveClaim(Claim claim)
+ {
+ Claims.RemoveAll(c => c.Type == claim.Type && c.Value == claim.Value);
+ }
+
+ public void RemoveClaims(IEnumerable claims)
+ {
+ claims.Foreach(RemoveClaim);
+ }
+
+ public void SetClaim(string type, string value)
+ {
+ SetClaim(new Claim(type, value));
+ }
+
+ public void SetClaim(Claim claim)
+ {
+ ReplaceClaim(claim, claim);
+ }
+
+ public string GetToken(string loginProider, string name)
+ {
+ return Tokens.FirstOrDefault(t => t.LoginProvider == loginProider && t.Name == name)?.Value;
+ }
+
+ public void AddToken(string loginProvider, string name, string value)
+ {
+ Tokens.Add(new MongoUserToken { LoginProvider = loginProvider, Name = name, Value = value });
+ }
+
+ public void RemoveToken(string loginProvider, string name)
+ {
+ Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name);
+ }
+
+ public void ReplaceClaim(Claim existingClaim, Claim newClaim)
+ {
+ RemoveClaim(existingClaim);
+
+ AddClaim(newClaim);
+ }
+
+ public void SetToken(string loginProider, string name, string value)
+ {
+ RemoveToken(loginProider, name);
+
+ AddToken(loginProider, name, value);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserClaim.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserClaim.cs
new file mode 100644
index 000000000..d9632e439
--- /dev/null
+++ b/src/Squidex.Domain.Users.MongoDb/MongoUserClaim.cs
@@ -0,0 +1,34 @@
+// ==========================================================================
+// MongoUserClaim.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System.Security.Claims;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Squidex.Domain.Users.MongoDb
+{
+ public sealed class MongoUserClaim
+ {
+ [BsonRequired]
+ [BsonElement]
+ public string Type { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string Value { get; set; }
+
+ public static implicit operator MongoUserClaim(Claim claim)
+ {
+ return new MongoUserClaim { Type = claim.Type, Value = claim.Value };
+ }
+
+ public static implicit operator Claim(MongoUserClaim userClaim)
+ {
+ return new Claim(userClaim.Type, userClaim.Value);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserLogin.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserLogin.cs
new file mode 100644
index 000000000..899c0daea
--- /dev/null
+++ b/src/Squidex.Domain.Users.MongoDb/MongoUserLogin.cs
@@ -0,0 +1,43 @@
+// ==========================================================================
+// MongoUserLogin.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using Microsoft.AspNetCore.Identity;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Squidex.Domain.Users.MongoDb
+{
+ public sealed class MongoUserLogin
+ {
+ [BsonRequired]
+ [BsonElement]
+ public string LoginProvider { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string ProviderDisplayName { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string ProviderKey { get; set; }
+
+ public static implicit operator MongoUserLogin(UserLoginInfo login)
+ {
+ return new MongoUserLogin
+ {
+ LoginProvider = login.LoginProvider,
+ ProviderKey = login.ProviderKey,
+ ProviderDisplayName = login.ProviderDisplayName
+ };
+ }
+
+ public static implicit operator UserLoginInfo(MongoUserLogin userLogin)
+ {
+ return new UserLoginInfo(userLogin.LoginProvider, userLogin.ProviderKey, userLogin.ProviderDisplayName);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
index 23ca128be..964e9a990 100644
--- a/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
+++ b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
@@ -13,13 +13,15 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Identity.MongoDB;
using MongoDB.Driver;
+using Squidex.Infrastructure.MongoDb;
+using Squidex.Infrastructure.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users.MongoDb
{
public sealed class MongoUserStore :
+ MongoRepositoryBase,
IUserPasswordStore,
IUserRoleStore,
IUserLoginStore,
@@ -34,296 +36,357 @@ namespace Squidex.Domain.Users.MongoDb
IUserResolver,
IQueryableUserStore
{
- private readonly UserStore innerStore;
-
public MongoUserStore(IMongoDatabase database)
+ : base(database)
+ {
+ }
+
+ protected override string CollectionName()
{
- var usersCollection = database.GetCollection("Identity_Users");
+ return "Identity_Users";
+ }
- IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection);
- IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection);
+ protected override Task SetupCollectionAsync(IMongoCollection collection)
+ {
+ return Task.WhenAll(
+ collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedUserName), new CreateIndexOptions { Unique = true }),
+ collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NormalizedEmail), new CreateIndexOptions { Unique = true }));
+ }
- innerStore = new UserStore(usersCollection);
+ protected override MongoCollectionSettings CollectionSettings()
+ {
+ return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public void Dispose()
{
- innerStore.Dispose();
}
public IQueryable Users
{
- get { return innerStore.Users; }
+ get { return Collection.AsQueryable(); }
}
public IUser Create(string email)
{
- return new WrappedIdentityUser { Email = email, UserName = email };
+ return new MongoUser { Email = email, UserName = email };
}
- public async Task FindByIdAsync(string userId)
+ public async Task FindByIdAsync(string id)
{
- return await innerStore.FindByIdAsync(userId, CancellationToken.None);
+ return await Collection.Find(x => x.Id == id).FirstOrDefaultAsync();
}
public async Task FindByIdAsync(string userId, CancellationToken cancellationToken)
{
- return await innerStore.FindByIdAsync(userId, cancellationToken);
+ return await Collection.Find(x => x.Id == userId).FirstOrDefaultAsync(cancellationToken);
}
public async Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
{
- return await innerStore.FindByEmailAsync(normalizedEmail, cancellationToken);
+ return await Collection.Find(x => x.NormalizedEmail == normalizedEmail).FirstOrDefaultAsync(cancellationToken);
}
public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
- return await innerStore.FindByNameAsync(normalizedUserName, cancellationToken);
+ return await Collection.Find(x => x.NormalizedEmail == normalizedUserName).FirstOrDefaultAsync(cancellationToken);
}
public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
{
- return await innerStore.FindByLoginAsync(loginProvider, providerKey, cancellationToken);
+ return await Collection.Find(x => x.Logins.Any(y => y.LoginProvider == loginProvider && y.ProviderKey == providerKey)).FirstOrDefaultAsync(cancellationToken);
}
public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken)
{
- return (await innerStore.GetUsersForClaimAsync(claim, cancellationToken)).OfType().ToList();
+ return (await Collection.Find(x => x.Claims.Any(y => y.Type == claim.Type && y.Value == claim.Value)).ToListAsync(cancellationToken)).OfType().ToList();
}
public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
{
- return (await innerStore.GetUsersInRoleAsync(roleName, cancellationToken)).OfType().ToList();
+ return (await Collection.Find(x => x.Roles.Contains(roleName)).ToListAsync(cancellationToken)).OfType().ToList();
}
- public Task CreateAsync(IUser user, CancellationToken cancellationToken)
+ public async Task CreateAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.CreateAsync((WrappedIdentityUser)user, cancellationToken);
+ await Collection.InsertOneAsync((MongoUser)user, null, cancellationToken);
+
+ return IdentityResult.Success;
}
- public Task UpdateAsync(IUser user, CancellationToken cancellationToken)
+ public async Task UpdateAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.UpdateAsync((WrappedIdentityUser)user, cancellationToken);
+ await Collection.ReplaceOneAsync(x => x.Id == user.Id, (MongoUser)user, null, cancellationToken);
+
+ return IdentityResult.Success;
}
- public Task DeleteAsync(IUser user, CancellationToken cancellationToken)
+ public async Task DeleteAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.DeleteAsync((WrappedIdentityUser)user, cancellationToken);
+ await Collection.DeleteOneAsync(x => x.Id == user.Id, null, cancellationToken);
+
+ return IdentityResult.Success;
}
public Task GetUserIdAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetUserIdAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).Id);
}
public Task GetUserNameAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetUserNameAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).UserName);
}
- public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken)
+ public Task GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetUserNameAsync((WrappedIdentityUser)user, userName, cancellationToken);
+ return Task.FromResult(((MongoUser)user).NormalizedUserName);
}
- public Task GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetPasswordHashAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetNormalizedUserNameAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).PasswordHash);
}
- public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken)
+ public Task> GetRolesAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetNormalizedUserNameAsync((WrappedIdentityUser)user, normalizedName, cancellationToken);
+ return Task.FromResult>(((MongoUser)user).Roles);
}
- public Task GetPasswordHashAsync(IUser user, CancellationToken cancellationToken)
+ public Task IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
- return innerStore.GetPasswordHashAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).Roles.Contains(roleName));
}
- public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken)
+ public Task> GetLoginsAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetPasswordHashAsync((WrappedIdentityUser)user, passwordHash, cancellationToken);
+ return Task.FromResult>(((MongoUser)user).Logins.Select(x => (UserLoginInfo)x).ToList());
}
- public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
+ public Task GetSecurityStampAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.AddToRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
+ return Task.FromResult(((MongoUser)user).SecurityStamp);
}
- public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
+ public Task GetEmailAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.RemoveFromRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
+ return Task.FromResult(((MongoUser)user).Email);
}
- public Task> GetRolesAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetRolesAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).EmailConfirmed);
}
- public Task IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
+ public Task GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.IsInRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
+ return Task.FromResult(((MongoUser)user).NormalizedEmail);
}
- public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken)
+ public Task> GetClaimsAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.AddLoginAsync((WrappedIdentityUser)user, login, cancellationToken);
+ return Task.FromResult>(((MongoUser)user).Claims.Select(x => (Claim)x).ToList());
}
- public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
+ public Task GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.RemoveLoginAsync((WrappedIdentityUser)user, loginProvider, providerKey, cancellationToken);
+ return Task.FromResult(((MongoUser)user).PhoneNumber);
}
- public Task> GetLoginsAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetLoginsAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).PhoneNumberConfirmed);
}
- public Task GetSecurityStampAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetSecurityStampAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).TwoFactorEnabled);
}
- public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken)
+ public Task GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetSecurityStampAsync((WrappedIdentityUser)user, stamp, cancellationToken);
+ return Task.FromResult(((MongoUser)user).LockoutEndDateUtc);
}
- public Task GetEmailAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.GetEmailAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).AccessFailedCount);
}
- public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken)
+ public Task GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetEmailAsync((WrappedIdentityUser)user, email, cancellationToken);
+ return Task.FromResult(((MongoUser)user).LockoutEnabled);
}
- public Task GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken)
+ public Task GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
- return innerStore.GetEmailConfirmedAsync((WrappedIdentityUser)user, cancellationToken);
+ return Task.FromResult(((MongoUser)user).GetToken(loginProvider, name));
}
- public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
+ public Task HasPasswordAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetEmailConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken);
+ return Task.FromResult(!string.IsNullOrWhiteSpace(((MongoUser)user).PasswordHash));
}
- public Task GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken)
{
- return innerStore.GetNormalizedEmailAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).UserName = userName;
+
+ return TaskHelper.Done;
}
- public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken)
+ public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken)
{
- return innerStore.SetNormalizedEmailAsync((WrappedIdentityUser)user, normalizedEmail, cancellationToken);
+ ((MongoUser)user).NormalizedUserName = normalizedName;
+
+ return TaskHelper.Done;
}
- public Task> GetClaimsAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken)
{
- return innerStore.GetClaimsAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).PasswordHash = passwordHash;
+
+ return TaskHelper.Done;
}
- public Task AddClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken)
+ public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
- return innerStore.AddClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken);
+ ((MongoUser)user).AddRole(roleName);
+
+ return TaskHelper.Done;
}
- public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
+ public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
- return innerStore.ReplaceClaimAsync((WrappedIdentityUser)user, claim, newClaim, cancellationToken);
+ ((MongoUser)user).RemoveRole(roleName);
+
+ return TaskHelper.Done;
}
- public Task RemoveClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken)
+ public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken)
{
- return innerStore.RemoveClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken);
+ ((MongoUser)user).AddLogin(login);
+
+ return TaskHelper.Done;
}
- public Task GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken)
+ public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
{
- return innerStore.GetPhoneNumberAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).RemoveLogin(loginProvider, providerKey);
+
+ return TaskHelper.Done;
}
- public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken)
+ public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken)
{
- return innerStore.SetPhoneNumberAsync((WrappedIdentityUser)user, phoneNumber, cancellationToken);
+ ((MongoUser)user).SecurityStamp = stamp;
+
+ return TaskHelper.Done;
}
- public Task GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken)
{
- return innerStore.GetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).Email = email;
+
+ return TaskHelper.Done;
}
- public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
+ public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
{
- return innerStore.SetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken);
+ ((MongoUser)user).EmailConfirmed = confirmed;
+
+ return TaskHelper.Done;
}
- public Task GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken)
{
- return innerStore.GetTwoFactorEnabledAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).NormalizedEmail = normalizedEmail;
+
+ return TaskHelper.Done;
}
- public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
+ public Task AddClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken)
{
- return innerStore.SetTwoFactorEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken);
+ ((MongoUser)user).AddClaims(claims);
+
+ return TaskHelper.Done;
}
- public Task GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken)
+ public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
{
- return innerStore.GetLockoutEndDateAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).ReplaceClaim(claim, newClaim);
+
+ return TaskHelper.Done;
}
- public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
+ public Task RemoveClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken)
{
- return innerStore.SetLockoutEndDateAsync((WrappedIdentityUser)user, lockoutEnd, cancellationToken);
+ ((MongoUser)user).RemoveClaims(claims);
+
+ return TaskHelper.Done;
}
- public Task GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken)
{
- return innerStore.GetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).PhoneNumber = phoneNumber;
+
+ return TaskHelper.Done;
}
- public Task IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken)
{
- return innerStore.IncrementAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).PhoneNumberConfirmed = confirmed;
+
+ return TaskHelper.Done;
}
- public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
{
- return innerStore.ResetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).TwoFactorEnabled = enabled;
+
+ return TaskHelper.Done;
}
- public Task GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken)
+ public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
{
- return innerStore.GetLockoutEnabledAsync((WrappedIdentityUser)user, cancellationToken);
+ ((MongoUser)user).LockoutEndDateUtc = lockoutEnd?.UtcDateTime;
+
+ return TaskHelper.Done;
}
- public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
+ public Task IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetLockoutEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken);
+ ((MongoUser)user).AccessFailedCount++;
+
+ return Task.FromResult(((MongoUser)user).AccessFailedCount);
}
- public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
+ public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken)
{
- return innerStore.SetTokenAsync((WrappedIdentityUser)user, loginProvider, name, value, cancellationToken);
+ ((MongoUser)user).AccessFailedCount = 0;
+
+ return TaskHelper.Done;
}
- public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
+ public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken)
{
- return innerStore.RemoveTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken);
+ ((MongoUser)user).LockoutEnabled = enabled;
+
+ return TaskHelper.Done;
}
- public Task GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
+ public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
{
- return innerStore.GetTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken);
+ ((MongoUser)user).SetToken(loginProvider, name, value);
+
+ return TaskHelper.Done;
}
- public Task HasPasswordAsync(IUser user, CancellationToken cancellationToken)
+ public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
- return Task.FromResult(!string.IsNullOrWhiteSpace(((WrappedIdentityUser)user).PasswordHash));
+ ((MongoUser)user).RemoveToken(loginProvider, name);
+
+ return TaskHelper.Done;
}
}
}
diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserToken.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserToken.cs
new file mode 100644
index 000000000..e162116a8
--- /dev/null
+++ b/src/Squidex.Domain.Users.MongoDb/MongoUserToken.cs
@@ -0,0 +1,27 @@
+// ==========================================================================
+// MongoUserToken.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Squidex.Domain.Users.MongoDb
+{
+ public sealed class MongoUserToken
+ {
+ [BsonRequired]
+ [BsonElement]
+ public string LoginProvider { get; set; }
+
+ [BsonIgnoreIfDefault]
+ [BsonElement]
+ public string Name { get; set; }
+
+ [BsonRequired]
+ [BsonElement]
+ public string Value { get; set; }
+ }
+}
diff --git a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
index c118300b3..31704faa2 100644
--- a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
+++ b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
@@ -15,7 +15,6 @@
-
diff --git a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs
deleted file mode 100644
index fee399a23..000000000
--- a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// ==========================================================================
-// WrappedIdentityRole.cs
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex Group
-// All rights reserved.
-// ==========================================================================
-
-using Microsoft.AspNetCore.Identity.MongoDB;
-
-namespace Squidex.Domain.Users.MongoDb
-{
- public sealed class WrappedIdentityRole : IdentityRole, IRole
- {
- }
-}
diff --git a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs
deleted file mode 100644
index abbce010a..000000000
--- a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// ==========================================================================
-// WrappedIdentityUser.cs
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex Group
-// All rights reserved.
-// ==========================================================================
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Claims;
-using Microsoft.AspNetCore.Identity.MongoDB;
-using Squidex.Shared.Users;
-
-namespace Squidex.Domain.Users.MongoDb
-{
- public sealed class WrappedIdentityUser : IdentityUser, IUser
- {
- public bool IsLocked
- {
- get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; }
- }
-
- IReadOnlyList IUser.Claims
- {
- get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); }
- }
-
- IReadOnlyList IUser.Logins
- {
- get { return Logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey, x.ProviderDisplayName)).ToList(); }
- }
-
- public void UpdateEmail(string email)
- {
- Email = UserName = email;
- }
-
- public void SetClaim(string type, string value)
- {
- Claims.RemoveAll(x => string.Equals(x.Type, type, StringComparison.OrdinalIgnoreCase));
- Claims.Add(new IdentityUserClaim { Type = type, Value = value });
- }
- }
-}
diff --git a/src/Squidex.Domain.Users/UserManagerExtensions.cs b/src/Squidex.Domain.Users/UserManagerExtensions.cs
index c207d6c7e..799b9a2ee 100644
--- a/src/Squidex.Domain.Users/UserManagerExtensions.cs
+++ b/src/Squidex.Domain.Users/UserManagerExtensions.cs
@@ -87,7 +87,8 @@ namespace Squidex.Domain.Users
if (!string.IsNullOrWhiteSpace(email))
{
- user.UpdateEmail(email);
+ await DoChecked(() => userManager.SetEmailAsync(user, email), "Cannot update email.");
+ await DoChecked(() => userManager.SetUserNameAsync(user, email), "Cannot update email.");
}
if (!string.IsNullOrWhiteSpace(displayName))
diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs
index 0b177c816..b26ef92bb 100644
--- a/src/Squidex.Infrastructure/CollectionExtensions.cs
+++ b/src/Squidex.Infrastructure/CollectionExtensions.cs
@@ -128,5 +128,13 @@ namespace Squidex.Infrastructure
return result;
}
+
+ public static void Foreach(this IEnumerable collection, Action action)
+ {
+ foreach (var item in collection)
+ {
+ action(item);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj
index f6a54444f..83d110efc 100644
--- a/src/Squidex/Squidex.csproj
+++ b/src/Squidex/Squidex.csproj
@@ -8,6 +8,7 @@
Squidex
true
netcoreapp2.0
+ Latest
true
@@ -68,7 +69,7 @@
-
+