Headless CMS and Content Managment Hub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

414 lines
13 KiB

// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
using Squidex.Log;
using Squidex.Shared;
using Squidex.Shared.Identity;
using Squidex.Shared.Users;
namespace Squidex.Domain.Users
{
public sealed class DefaultUserService : IUserService
{
private readonly UserManager<IdentityUser> userManager;
private readonly IUserFactory userFactory;
private readonly IEnumerable<IUserEvents> userEvents;
private readonly ISemanticLog log;
public DefaultUserService(UserManager<IdentityUser> userManager, IUserFactory userFactory,
IEnumerable<IUserEvents> userEvents, ISemanticLog log)
{
Guard.NotNull(userManager, nameof(userManager));
Guard.NotNull(userFactory, nameof(userFactory));
Guard.NotNull(userEvents, nameof(userEvents));
Guard.NotNull(log, nameof(log));
this.userManager = userManager;
this.userFactory = userFactory;
this.userEvents = userEvents;
this.log = log;
}
public async Task<bool> IsEmptyAsync()
{
var result = await QueryAsync(null, 1, 0);
return result.Total == 0;
}
public string GetUserId(ClaimsPrincipal user)
{
Guard.NotNull(user, nameof(user));
return userManager.GetUserId(user);
}
public async Task<IResultList<IUser>> QueryAsync(IEnumerable<string> ids)
{
Guard.NotNull(ids, nameof(ids));
ids = ids.Where(userFactory.IsId);
if (!ids.Any())
{
return ResultList.CreateFrom<IUser>(0);
}
var users = userManager.Users.Where(x => ids.Contains(x.Id)).ToList();
var resolved = await ResolveAsync(users);
return ResultList.Create(users.Count, resolved);
}
public async Task<IResultList<IUser>> QueryAsync(string? query, int take, int skip)
{
Guard.GreaterThan(take, 0, nameof(take));
Guard.GreaterEquals(skip, 0, nameof(skip));
IQueryable<IdentityUser> QueryUsers(string? email = null)
{
var result = userManager.Users;
if (!string.IsNullOrWhiteSpace(email))
{
var normalizedEmail = userManager.NormalizeEmail(email);
result = result.Where(x => x.NormalizedEmail.Contains(normalizedEmail));
}
return result;
}
var userItems = QueryUsers(query).Take(take).Skip(skip).ToList();
var userTotal = QueryUsers(query).LongCount();
var resolved = await ResolveAsync(userItems);
return ResultList.Create(userTotal, resolved);
}
public Task<IList<UserLoginInfo>> GetLoginsAsync(IUser user)
{
Guard.NotNull(user, nameof(user));
return userManager.GetLoginsAsync((IdentityUser)user.Identity);
}
public Task<bool> HasPasswordAsync(IUser user)
{
Guard.NotNull(user, nameof(user));
return userManager.HasPasswordAsync((IdentityUser)user.Identity);
}
public async Task<IUser?> FindByLoginAsync(string provider, string key)
{
Guard.NotNullOrEmpty(provider, nameof(provider));
var user = await userManager.FindByLoginAsync(provider, key);
return await ResolveOptionalAsync(user);
}
public async Task<IUser?> FindByEmailAsync(string email)
{
Guard.NotNullOrEmpty(email, nameof(email));
var user = await userManager.FindByEmailAsync(email);
return await ResolveOptionalAsync(user);
}
public async Task<IUser?> GetAsync(ClaimsPrincipal principal)
{
Guard.NotNull(principal, nameof(principal));
var user = await userManager.GetUserAsync(principal);
return await ResolveOptionalAsync(user);
}
public async Task<IUser?> FindByIdAsync(string id)
{
if (!userFactory.IsId(id))
{
return null;
}
var user = await userManager.FindByIdAsync(id);
return await ResolveOptionalAsync(user);
}
public async Task<IUser> CreateAsync(string email, UserValues? values = null, bool lockAutomatically = false)
{
Guard.NotNullOrEmpty(email, nameof(email));
var isFirst = !userManager.Users.Any();
var user = userFactory.Create(email);
try
{
await userManager.CreateAsync(user).Throw(log);
values ??= new UserValues();
if (string.IsNullOrWhiteSpace(values.DisplayName))
{
values.DisplayName = email;
}
if (isFirst)
{
var permissions = values.Permissions?.ToIds().ToList() ?? new List<string>();
permissions.Add(Permissions.Admin);
values.Permissions = new PermissionSet(permissions);
}
await userManager.SyncClaims(user, values).Throw(log);
if (!string.IsNullOrWhiteSpace(values.Password))
{
await userManager.AddPasswordAsync(user, values.Password).Throw(log);
}
if (!isFirst && lockAutomatically)
{
await userManager.SetLockoutEndDateAsync(user, LockoutDate()).Throw(log);
}
}
catch (Exception)
{
try
{
if (userFactory.IsId(user.Id))
{
await userManager.DeleteAsync(user);
}
}
catch (Exception ex2)
{
log.LogError(ex2, w => w
.WriteProperty("action", "CleanupUser")
.WriteProperty("status", "Failed"));
}
throw;
}
var resolved = await ResolveAsync(user);
foreach (var @events in userEvents)
{
await @events.OnUserRegisteredAsync(resolved);
}
if (HasConsentGiven(values, null!))
{
foreach (var @events in userEvents)
{
await @events.OnConsentGivenAsync(resolved);
}
}
return resolved;
}
public Task<IUser> SetPasswordAsync(string id, string password, string? oldPassword)
{
Guard.NotNullOrEmpty(id, nameof(id));
return ForUserAsync(id, async user =>
{
if (await userManager.HasPasswordAsync(user))
{
await userManager.ChangePasswordAsync(user, oldPassword!, password).Throw(log);
}
else
{
await userManager.AddPasswordAsync(user, password).Throw(log);
}
});
}
public async Task<IUser> UpdateAsync(string id, UserValues values, bool silent = false)
{
Guard.NotNullOrEmpty(id, nameof(id));
Guard.NotNull(values, nameof(values));
var user = await GetUserAsync(id);
var oldUser = await ResolveAsync(user);
if (!string.IsNullOrWhiteSpace(values.Email) && values.Email != user.Email)
{
await userManager.SetEmailAsync(user, values.Email).Throw(log);
await userManager.SetUserNameAsync(user, values.Email).Throw(log);
}
await userManager.SyncClaims(user, values).Throw(log);
if (!string.IsNullOrWhiteSpace(values.Password))
{
if (await userManager.HasPasswordAsync(user))
{
await userManager.RemovePasswordAsync(user).Throw(log);
}
await userManager.AddPasswordAsync(user, values.Password).Throw(log);
}
var resolved = await ResolveAsync(user);
if (!silent)
{
foreach (var @events in userEvents)
{
await @events.OnUserUpdatedAsync(resolved, oldUser);
}
if (HasConsentGiven(values, oldUser))
{
foreach (var @events in userEvents)
{
await @events.OnConsentGivenAsync(resolved);
}
}
}
return resolved;
}
public Task<IUser> LockAsync(string id)
{
Guard.NotNullOrEmpty(id, nameof(id));
return ForUserAsync(id, user => userManager.SetLockoutEndDateAsync(user, LockoutDate()).Throw(log));
}
public Task<IUser> UnlockAsync(string id)
{
Guard.NotNullOrEmpty(id, nameof(id));
return ForUserAsync(id, user => userManager.SetLockoutEndDateAsync(user, null).Throw(log));
}
public Task<IUser> AddLoginAsync(string id, ExternalLoginInfo externalLogin)
{
Guard.NotNullOrEmpty(id, nameof(id));
return ForUserAsync(id, user => userManager.AddLoginAsync(user, externalLogin).Throw(log));
}
public Task<IUser> RemoveLoginAsync(string id, string loginProvider, string providerKey)
{
Guard.NotNullOrEmpty(id, nameof(id));
return ForUserAsync(id, user => userManager.RemoveLoginAsync(user, loginProvider, providerKey).Throw(log));
}
public async Task DeleteAsync(string id)
{
Guard.NotNullOrEmpty(id, nameof(id));
var user = await GetUserAsync(id);
var resolved = await ResolveAsync(user);
await userManager.DeleteAsync(user).Throw(log);
foreach (var @events in userEvents)
{
await @events.OnUserDeletedAsync(resolved);
}
}
private async Task<IUser> ForUserAsync(string id, Func<IdentityUser, Task> action)
{
var user = await GetUserAsync(id);
await action(user);
return await ResolveAsync(user);
}
private async Task<IdentityUser> GetUserAsync(string id)
{
if (!userFactory.IsId(id))
{
throw new DomainObjectNotFoundException(id);
}
var user = await userManager.FindByIdAsync(id);
if (user == null)
{
throw new DomainObjectNotFoundException(id);
}
return user;
}
private Task<IUser[]> ResolveAsync(IEnumerable<IdentityUser> users)
{
return Task.WhenAll(users.Select(async user =>
{
return await ResolveAsync(user);
}));
}
private async Task<IUser> ResolveAsync(IdentityUser user)
{
var claims = await userManager.GetClaimsAsync(user);
if (!claims.Any(x => string.Equals(x.Type, SquidexClaimTypes.DisplayName, StringComparison.OrdinalIgnoreCase)))
{
claims.Add(new Claim(SquidexClaimTypes.DisplayName, user.Email));
}
return new UserWithClaims(user, claims.ToList());
}
private async Task<IUser?> ResolveOptionalAsync(IdentityUser? user)
{
if (user == null)
{
return null;
}
return await ResolveAsync(user);
}
private static bool HasConsentGiven(UserValues values, IUser? oldUser)
{
if (values.Consent == true && oldUser?.Claims.HasConsent() != true)
{
return true;
}
return values.ConsentForEmails == true && oldUser?.Claims.HasConsentForEmails() != true;
}
private static DateTimeOffset LockoutDate()
{
return DateTimeOffset.UtcNow.AddYears(100);
}
}
}