1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[SM-220] Move identity specific files to identity (#2279)

This commit is contained in:
Oscar Hinton
2022-09-27 18:30:37 +02:00
committed by GitHub
parent ea0087ee6f
commit c11a179332
25 changed files with 91 additions and 124 deletions

View File

@ -1,5 +1,5 @@
using Bit.Admin.Models;
using Bit.Core.Identity;
using Bit.Admin.IdentityServer;
using Bit.Admin.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

View File

@ -0,0 +1,87 @@
using Bit.Core.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace Bit.Admin.IdentityServer;
public class PasswordlessSignInManager<TUser> : SignInManager<TUser> where TUser : class
{
public const string PasswordlessSignInPurpose = "PasswordlessSignIn";
private readonly IMailService _mailService;
public PasswordlessSignInManager(UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation,
IMailService mailService)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_mailService = mailService;
}
public async Task<SignInResult> PasswordlessSignInAsync(string email, string returnUrl)
{
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
return SignInResult.Failed;
}
var token = await UserManager.GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
PasswordlessSignInPurpose);
await _mailService.SendPasswordlessSignInAsync(returnUrl, token, email);
return SignInResult.Success;
}
public async Task<SignInResult> PasswordlessSignInAsync(TUser user, string token, bool isPersistent)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordlessSignInAsync(user, token);
return attempt.Succeeded ?
await SignInOrTwoFactorAsync(user, isPersistent, bypassTwoFactor: true) : attempt;
}
public async Task<SignInResult> PasswordlessSignInAsync(string email, string token, bool isPersistent)
{
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordlessSignInAsync(user, token, isPersistent);
}
public virtual async Task<SignInResult> CheckPasswordlessSignInAsync(TUser user, string token)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
if (await UserManager.VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
PasswordlessSignInPurpose, token))
{
return SignInResult.Success;
}
Logger.LogWarning(2, "User {userId} failed to provide the correct token.",
await UserManager.GetUserIdAsync(user));
return SignInResult.Failed;
}
}

View File

@ -0,0 +1,65 @@
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Identity;
namespace Bit.Admin.IdentityServer;
public class ReadOnlyEnvIdentityUserStore : ReadOnlyIdentityUserStore
{
private readonly IConfiguration _configuration;
public ReadOnlyEnvIdentityUserStore(IConfiguration configuration)
{
_configuration = configuration;
}
public override Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default)
{
var usersCsv = _configuration["adminSettings:admins"];
if (!CoreHelpers.SettingHasValue(usersCsv))
{
return Task.FromResult<IdentityUser>(null);
}
var users = usersCsv.ToLowerInvariant().Split(',');
var usersDict = new Dictionary<string, string>();
foreach (var u in users)
{
var parts = u.Split(':');
if (parts.Length == 2)
{
var email = parts[0].Trim();
var stamp = parts[1].Trim();
usersDict.Add(email, stamp);
}
else
{
var email = parts[0].Trim();
usersDict.Add(email, email);
}
}
var userStamp = usersDict.ContainsKey(normalizedEmail) ? usersDict[normalizedEmail] : null;
if (userStamp == null)
{
return Task.FromResult<IdentityUser>(null);
}
return Task.FromResult(new IdentityUser
{
Id = normalizedEmail,
Email = normalizedEmail,
NormalizedEmail = normalizedEmail,
EmailConfirmed = true,
UserName = normalizedEmail,
NormalizedUserName = normalizedEmail,
SecurityStamp = userStamp
});
}
public override Task<IdentityUser> FindByIdAsync(string userId,
CancellationToken cancellationToken = default)
{
return FindByEmailAsync(userId, cancellationToken);
}
}

View File

@ -0,0 +1,118 @@
using Microsoft.AspNetCore.Identity;
namespace Bit.Admin.IdentityServer;
public abstract class ReadOnlyIdentityUserStore :
IUserEmailStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
public void Dispose() { }
public Task<IdentityResult> CreateAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public abstract Task<IdentityUser> FindByEmailAsync(string normalizedEmail,
CancellationToken cancellationToken = default);
public abstract Task<IdentityUser> FindByIdAsync(string userId,
CancellationToken cancellationToken = default);
public async Task<IdentityUser> FindByNameAsync(string normalizedUserName,
CancellationToken cancellationToken = default)
{
return await FindByEmailAsync(normalizedUserName, cancellationToken);
}
public Task<string> GetEmailAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.Email);
}
public Task<bool> GetEmailConfirmedAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.EmailConfirmed);
}
public Task<string> GetNormalizedEmailAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.Email);
}
public Task<string> GetNormalizedUserNameAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.Email);
}
public Task<string> GetUserIdAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.Id);
}
public Task<string> GetUserNameAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(user.Email);
}
public Task SetEmailAsync(IdentityUser user, string email,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task SetEmailConfirmedAsync(IdentityUser user, bool confirmed,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task SetNormalizedEmailAsync(IdentityUser user, string normalizedEmail,
CancellationToken cancellationToken = default)
{
user.NormalizedEmail = normalizedEmail;
return Task.FromResult(0);
}
public Task SetNormalizedUserNameAsync(IdentityUser user, string normalizedName,
CancellationToken cancellationToken = default)
{
user.NormalizedUserName = normalizedName;
return Task.FromResult(0);
}
public Task SetUserNameAsync(IdentityUser user, string userName,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(IdentityUser user,
CancellationToken cancellationToken = default)
{
return Task.FromResult(IdentityResult.Success);
}
public Task SetSecurityStampAsync(IdentityUser user, string stamp, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetSecurityStampAsync(IdentityUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.SecurityStamp);
}
}

View File

@ -0,0 +1,44 @@
using Bit.Core.Entities;
using Bit.Core.Identity;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Bit.Admin.IdentityServer;
public static class ServiceCollectionExtensions
{
public static Tuple<IdentityBuilder, IdentityBuilder> AddPasswordlessIdentityServices<TUserStore>(
this IServiceCollection services, GlobalSettings globalSettings) where TUserStore : class
{
services.TryAddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
services.Configure<DataProtectionTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromMinutes(15);
});
var passwordlessIdentityBuilder = services.AddIdentity<IdentityUser, Role>()
.AddUserStore<TUserStore>()
.AddRoleStore<RoleStore>()
.AddDefaultTokenProviders();
var regularIdentityBuilder = services.AddIdentityCore<User>()
.AddUserStore<UserStore>();
services.TryAddScoped<PasswordlessSignInManager<IdentityUser>, PasswordlessSignInManager<IdentityUser>>();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/";
options.AccessDeniedPath = "/login?accessDenied=true";
options.Cookie.Name = $"Bitwarden_{globalSettings.ProjectName}";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromDays(2);
options.ReturnUrlParameter = "returnUrl";
options.SlidingExpiration = true;
});
return new Tuple<IdentityBuilder, IdentityBuilder>(passwordlessIdentityBuilder, regularIdentityBuilder);
}
}

View File

@ -1,6 +1,6 @@
using System.Globalization;
using Bit.Admin.IdentityServer;
using Bit.Core.Context;
using Bit.Core.Identity;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.SharedWeb.Utilities;