1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

[PM-1188] Server owner auth migration (#2825)

* [PM-1188] add sso project to auth

* [PM-1188] move sso api models to auth

* [PM-1188] fix sso api model namespace & imports

* [PM-1188] move core files to auth

* [PM-1188] fix core sso namespace & models

* [PM-1188] move sso repository files to auth

* [PM-1188] fix sso repo files namespace & imports

* [PM-1188] move sso sql files to auth folder

* [PM-1188] move sso test files to auth folders

* [PM-1188] fix sso tests namespace & imports

* [PM-1188] move auth api files to auth folder

* [PM-1188] fix auth api files namespace & imports

* [PM-1188] move auth core files to auth folder

* [PM-1188] fix auth core files namespace & imports

* [PM-1188] move auth email templates to auth folder

* [PM-1188] move auth email folder back into shared directory

* [PM-1188] fix auth email names

* [PM-1188] move auth core models to auth folder

* [PM-1188] fix auth model namespace & imports

* [PM-1188] add entire Identity project to auth codeowners

* [PM-1188] fix auth orm files namespace & imports

* [PM-1188] move auth orm files to auth folder

* [PM-1188] move auth sql files to auth folder

* [PM-1188] move auth tests to auth folder

* [PM-1188] fix auth test files namespace & imports

* [PM-1188] move emergency access api files to auth folder

* [PM-1188] fix emergencyaccess api files namespace & imports

* [PM-1188] move emergency access core files to auth folder

* [PM-1188] fix emergency access core files namespace & imports

* [PM-1188] move emergency access orm files to auth folder

* [PM-1188] fix emergency access orm files namespace & imports

* [PM-1188] move emergency access sql files to auth folder

* [PM-1188] move emergencyaccess test files to auth folder

* [PM-1188] fix emergency access test files namespace & imports

* [PM-1188] move captcha files to auth folder

* [PM-1188] fix captcha files namespace & imports

* [PM-1188] move auth admin files into auth folder

* [PM-1188] fix admin auth files namespace & imports
- configure mvc to look in auth folders for views

* [PM-1188] remove extra imports and formatting

* [PM-1188] fix ef auth model imports

* [PM-1188] fix DatabaseContextModelSnapshot paths

* [PM-1188] fix grant import in ef

* [PM-1188] update sqlproj

* [PM-1188] move missed sqlproj files

* [PM-1188] move auth ef models out of auth folder

* [PM-1188] fix auth ef models namespace

* [PM-1188] remove auth ef models unused imports

* [PM-1188] fix imports for auth ef models

* [PM-1188] fix more ef model imports

* [PM-1188] fix file encodings
This commit is contained in:
Jake Fink
2023-04-14 13:25:56 -04:00
committed by GitHub
parent 2529c5b36f
commit 88dd745070
332 changed files with 704 additions and 522 deletions

View File

@ -1,26 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
namespace Bit.Core.Models.Api.Request.Accounts;
public class KeysRequestModel
{
public string PublicKey { get; set; }
[Required]
public string EncryptedPrivateKey { get; set; }
public User ToUser(User existingUser)
{
if (string.IsNullOrWhiteSpace(existingUser.PublicKey) && !string.IsNullOrWhiteSpace(PublicKey))
{
existingUser.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingUser.PrivateKey))
{
existingUser.PrivateKey = EncryptedPrivateKey;
}
return existingUser;
}
}

View File

@ -1,11 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api.Request.Accounts;
public class PreloginRequestModel
{
[Required]
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
}

View File

@ -1,73 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Api.Request.Accounts;
public class RegisterRequestModel : IValidatableObject, ICaptchaProtectedModel
{
[StringLength(50)]
public string Name { get; set; }
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string Email { get; set; }
[Required]
[StringLength(1000)]
public string MasterPasswordHash { get; set; }
[StringLength(50)]
public string MasterPasswordHint { get; set; }
public string CaptchaResponse { get; set; }
public string Key { get; set; }
public KeysRequestModel Keys { get; set; }
public string Token { get; set; }
public Guid? OrganizationUserId { get; set; }
public KdfType? Kdf { get; set; }
public int? KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public Dictionary<string, object> ReferenceData { get; set; }
public User ToUser()
{
var user = new User
{
Name = Name,
Email = Email,
MasterPasswordHint = MasterPasswordHint,
Kdf = Kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = KdfIterations.GetValueOrDefault(5000),
KdfMemory = KdfMemory,
KdfParallelism = KdfParallelism
};
if (ReferenceData != null)
{
user.ReferenceData = JsonSerializer.Serialize(ReferenceData);
}
if (Key != null)
{
user.Key = Key;
}
if (Keys != null)
{
Keys.ToUser(user);
}
return user;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Kdf.HasValue && KdfIterations.HasValue)
{
return KdfSettingsValidator.Validate(Kdf.Value, KdfIterations.Value, KdfMemory, KdfParallelism);
}
return Enumerable.Empty<ValidationResult>();
}
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Api;
public interface ICaptchaProtectedModel
{
string CaptchaResponse { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Api.Response.Accounts;
public interface ICaptchaProtectedResponseModel
{
public string CaptchaBypassToken { get; set; }
}

View File

@ -1,20 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Api.Response.Accounts;
public class PreloginResponseModel
{
public PreloginResponseModel(UserKdfInformation kdfInformation)
{
Kdf = kdfInformation.Kdf;
KdfIterations = kdfInformation.KdfIterations;
KdfMemory = kdfInformation.KdfMemory;
KdfParallelism = kdfInformation.KdfParallelism;
}
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
}

View File

@ -1,12 +0,0 @@
namespace Bit.Core.Models.Api.Response.Accounts;
public class RegisterResponseModel : ResponseModel, ICaptchaProtectedResponseModel
{
public RegisterResponseModel(string captchaBypassToken)
: base("register")
{
CaptchaBypassToken = captchaBypassToken;
}
public string CaptchaBypassToken { get; set; }
}

View File

@ -1,9 +0,0 @@
namespace Bit.Core.Models.Business;
public class CaptchaResponse
{
public bool Success { get; set; }
public bool MaybeBot { get; set; }
public bool IsBot { get; set; }
public double Score { get; set; }
}

View File

@ -1,13 +0,0 @@
namespace Bit.Core.Models.Business;
public class ExpiringToken
{
public readonly string Token;
public readonly DateTime ExpirationDate;
public ExpiringToken(string token, DateTime expirationDate)
{
Token = token;
ExpirationDate = expirationDate;
}
}

View File

@ -1,35 +0,0 @@
using System.Text.Json.Serialization;
using Bit.Core.Entities;
namespace Bit.Core.Models.Business.Tokenables;
public class EmergencyAccessInviteTokenable : Tokens.ExpiringTokenable
{
public const string ClearTextPrefix = "";
public const string DataProtectorPurpose = "EmergencyAccessServiceDataProtector";
public const string TokenIdentifier = "EmergencyAccessInvite";
public string Identifier { get; set; } = TokenIdentifier;
public Guid Id { get; set; }
public string Email { get; set; }
[JsonConstructor]
public EmergencyAccessInviteTokenable(DateTime expirationDate)
{
ExpirationDate = expirationDate;
}
public EmergencyAccessInviteTokenable(EmergencyAccess user, int hoursTillExpiration)
{
Id = user.Id;
Email = user.Email;
ExpirationDate = DateTime.UtcNow.AddHours(hoursTillExpiration);
}
public bool IsValid(Guid id, string email)
{
return Id == id &&
Email.Equals(email, StringComparison.InvariantCultureIgnoreCase);
}
protected override bool TokenIsValid() => Identifier == TokenIdentifier && Id != default && !string.IsNullOrWhiteSpace(Email);
}

View File

@ -1,43 +0,0 @@
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Tokens;
namespace Bit.Core.Models.Business.Tokenables;
public class HCaptchaTokenable : ExpiringTokenable
{
private const double _tokenLifetimeInHours = (double)5 / 60; // 5 minutes
public const string ClearTextPrefix = "BWCaptchaBypass_";
public const string DataProtectorPurpose = "CaptchaServiceDataProtector";
public const string TokenIdentifier = "CaptchaBypassToken";
public string Identifier { get; set; } = TokenIdentifier;
public Guid Id { get; set; }
public string Email { get; set; }
[JsonConstructor]
public HCaptchaTokenable()
{
ExpirationDate = DateTime.UtcNow.AddHours(_tokenLifetimeInHours);
}
public HCaptchaTokenable(User user) : this()
{
Id = user?.Id ?? default;
Email = user?.Email;
}
public bool TokenIsValid(User user)
{
if (Id == default || Email == default || user == null)
{
return false;
}
return Id == user.Id &&
Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase);
}
// Validates deserialized
protected override bool TokenIsValid() => Identifier == TokenIdentifier && Id != default && !string.IsNullOrWhiteSpace(Email);
}

View File

@ -1,43 +0,0 @@
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Tokens;
namespace Bit.Core.Models.Business.Tokenables;
public class SsoTokenable : ExpiringTokenable
{
public const string ClearTextPrefix = "BWUserPrefix_";
public const string DataProtectorPurpose = "SsoTokenDataProtector";
public const string TokenIdentifier = "ssoToken";
public Guid OrganizationId { get; set; }
public string DomainHint { get; set; }
public string Identifier { get; set; } = TokenIdentifier;
[JsonConstructor]
public SsoTokenable() { }
public SsoTokenable(Organization organization, double tokenLifetimeInSeconds) : this()
{
OrganizationId = organization?.Id ?? default;
DomainHint = organization?.Identifier;
ExpirationDate = DateTime.UtcNow.AddSeconds(tokenLifetimeInSeconds);
}
public bool TokenIsValid(Organization organization)
{
if (OrganizationId == default || DomainHint == default || organization == null || !Valid)
{
return false;
}
return organization.Identifier.Equals(DomainHint, StringComparison.InvariantCultureIgnoreCase)
&& organization.Id.Equals(OrganizationId);
}
// Validates deserialized
protected override bool TokenIsValid() =>
Identifier == TokenIdentifier
&& OrganizationId != default
&& !string.IsNullOrWhiteSpace(DomainHint);
}

View File

@ -1,13 +0,0 @@
using Bit.Core.Entities;
namespace Bit.Core.Models.Data;
public class EmergencyAccessDetails : EmergencyAccess
{
public string GranteeName { get; set; }
public string GranteeEmail { get; set; }
public string GranteeAvatarColor { get; set; }
public string GrantorName { get; set; }
public string GrantorEmail { get; set; }
public string GrantorAvatarColor { get; set; }
}

View File

@ -1,10 +0,0 @@
using Bit.Core.Entities;
namespace Bit.Core.Models.Data;
public class EmergencyAccessNotify : EmergencyAccess
{
public string GrantorEmail { get; set; }
public string GranteeName { get; set; }
public string GranteeEmail { get; set; }
}

View File

@ -1,10 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Vault.Models.Data;
namespace Bit.Core.Models.Data;
public class EmergencyAccessViewData
{
public EmergencyAccess EmergencyAccess { get; set; }
public IEnumerable<CipherDetails> Ciphers { get; set; }
}

View File

@ -1,4 +1,6 @@
using Bit.Core.Enums;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Business;
using Bit.Core.Models.OrganizationConnectionConfigs;

View File

@ -1,125 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
namespace Bit.Core.Models.Data;
public class SsoConfigurationData
{
private static string _oidcSigninPath = "/oidc-signin";
private static string _oidcSignedOutPath = "/oidc-signedout";
private static string _saml2ModulePath = "/saml2";
public static SsoConfigurationData Deserialize(string data)
{
return CoreHelpers.LoadClassFromJsonData<SsoConfigurationData>(data);
}
public string Serialize()
{
return CoreHelpers.ClassToJsonData(this);
}
public SsoType ConfigType { get; set; }
public bool KeyConnectorEnabled { get; set; }
public string KeyConnectorUrl { get; set; }
// OIDC
public string Authority { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string MetadataAddress { get; set; }
public OpenIdConnectRedirectBehavior RedirectBehavior { get; set; }
public bool GetClaimsFromUserInfoEndpoint { get; set; }
public string AdditionalScopes { get; set; }
public string AdditionalUserIdClaimTypes { get; set; }
public string AdditionalEmailClaimTypes { get; set; }
public string AdditionalNameClaimTypes { get; set; }
public string AcrValues { get; set; }
public string ExpectedReturnAcrValue { get; set; }
// SAML2 IDP
public string IdpEntityId { get; set; }
public string IdpSingleSignOnServiceUrl { get; set; }
public string IdpSingleLogoutServiceUrl { get; set; }
public string IdpX509PublicCert { get; set; }
public Saml2BindingType IdpBindingType { get; set; }
public bool IdpAllowUnsolicitedAuthnResponse { get; set; }
public string IdpArtifactResolutionServiceUrl { get => null; set { /*IGNORE*/ } }
public bool IdpDisableOutboundLogoutRequests { get; set; }
public string IdpOutboundSigningAlgorithm { get; set; }
public bool IdpWantAuthnRequestsSigned { get; set; }
// SAML2 SP
public Saml2NameIdFormat SpNameIdFormat { get; set; }
public string SpOutboundSigningAlgorithm { get; set; }
public Saml2SigningBehavior SpSigningBehavior { get; set; }
public bool SpWantAssertionsSigned { get; set; }
public bool SpValidateCertificates { get; set; }
public string SpMinIncomingSigningAlgorithm { get; set; }
public static string BuildCallbackPath(string ssoUri = null)
{
return BuildSsoUrl(_oidcSigninPath, ssoUri);
}
public static string BuildSignedOutCallbackPath(string ssoUri = null)
{
return BuildSsoUrl(_oidcSignedOutPath, ssoUri);
}
public static string BuildSaml2ModulePath(string ssoUri = null, string scheme = null)
{
return string.Concat(BuildSsoUrl(_saml2ModulePath, ssoUri),
string.IsNullOrWhiteSpace(scheme) ? string.Empty : $"/{scheme}");
}
public static string BuildSaml2AcsUrl(string ssoUri = null, string scheme = null)
{
return string.Concat(BuildSaml2ModulePath(ssoUri, scheme), "/Acs");
}
public static string BuildSaml2MetadataUrl(string ssoUri = null, string scheme = null)
{
return BuildSaml2ModulePath(ssoUri, scheme);
}
public IEnumerable<string> GetAdditionalScopes() => AdditionalScopes?
.Split(',')?
.Where(c => !string.IsNullOrWhiteSpace(c))?
.Select(c => c.Trim()) ??
Array.Empty<string>();
public IEnumerable<string> GetAdditionalUserIdClaimTypes() => AdditionalUserIdClaimTypes?
.Split(',')?
.Where(c => !string.IsNullOrWhiteSpace(c))?
.Select(c => c.Trim()) ??
Array.Empty<string>();
public IEnumerable<string> GetAdditionalEmailClaimTypes() => AdditionalEmailClaimTypes?
.Split(',')?
.Where(c => !string.IsNullOrWhiteSpace(c))?
.Select(c => c.Trim()) ??
Array.Empty<string>();
public IEnumerable<string> GetAdditionalNameClaimTypes() => AdditionalNameClaimTypes?
.Split(',')?
.Where(c => !string.IsNullOrWhiteSpace(c))?
.Select(c => c.Trim()) ??
Array.Empty<string>();
private static string BuildSsoUrl(string relativePath, string ssoUri)
{
if (string.IsNullOrWhiteSpace(ssoUri) ||
!Uri.IsWellFormedUriString(ssoUri, UriKind.Absolute))
{
return relativePath;
}
if (Uri.TryCreate(string.Concat(ssoUri.TrimEnd('/'), relativePath), UriKind.Absolute, out var newUri))
{
return newUri.ToString();
}
return relativePath;
}
}

View File

@ -1,11 +0,0 @@
using Bit.Core.Enums;
namespace Bit.Core.Models;
public interface ITwoFactorProvidersUser
{
string TwoFactorProviders { get; }
Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders();
Guid? GetUserId();
bool GetPremium();
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessAcceptedViewModel : BaseMailModel
{
public string GranteeEmail { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessApprovedViewModel : BaseMailModel
{
public string Name { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessConfirmedViewModel : BaseMailModel
{
public string Name { get; set; }
}

View File

@ -1,10 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessInvitedViewModel : BaseMailModel
{
public string Name { get; set; }
public string Id { get; set; }
public string Email { get; set; }
public string Token { get; set; }
public string Url => $"{WebVaultUrl}/accept-emergency?id={Id}&name={Name}&email={Email}&token={Token}";
}

View File

@ -1,7 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessRecoveryTimedOutViewModel : BaseMailModel
{
public string Name { get; set; }
public string Action { get; set; }
}

View File

@ -1,8 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessRecoveryViewModel : BaseMailModel
{
public string Name { get; set; }
public string Action { get; set; }
public int DaysLeft { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class EmergencyAccessRejectedViewModel : BaseMailModel
{
public string Name { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class FailedAuthAttemptsModel : NewDeviceLoggedInModel
{
public string AffectedEmail { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class MasterPasswordHintViewModel : BaseMailModel
{
public string Hint { get; set; }
}

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Models.Mail;
public class PasswordlessSignInModel
{
public string Url { get; set; }
}

View File

@ -1,9 +0,0 @@
namespace Bit.Core.Models.Mail;
public class RecoverTwoFactorModel : BaseMailModel
{
public string TheDate { get; set; }
public string TheTime { get; set; }
public string TimeZone { get; set; }
public string IpAddress { get; set; }
}

View File

@ -1,15 +0,0 @@
namespace Bit.Core.Models.Mail;
public class VerifyDeleteModel : BaseMailModel
{
public string Url => string.Format("{0}/verify-recover-delete?userId={1}&token={2}&email={3}",
WebVaultUrl,
UserId,
Token,
EmailEncoded);
public Guid UserId { get; set; }
public string Email { get; set; }
public string EmailEncoded { get; set; }
public string Token { get; set; }
}

View File

@ -1,12 +0,0 @@
namespace Bit.Core.Models.Mail;
public class VerifyEmailModel : BaseMailModel
{
public string Url => string.Format("{0}/verify-email?userId={1}&token={2}",
WebVaultUrl,
UserId,
Token);
public Guid UserId { get; set; }
public string Token { get; set; }
}

View File

@ -1,66 +0,0 @@
using System.Text.Json;
using Bit.Core.Enums;
using Fido2NetLib.Objects;
namespace Bit.Core.Models;
public class TwoFactorProvider
{
public bool Enabled { get; set; }
public Dictionary<string, object> MetaData { get; set; } = new Dictionary<string, object>();
public class WebAuthnData
{
public WebAuthnData() { }
public WebAuthnData(dynamic o)
{
Name = o.Name;
try
{
Descriptor = o.Descriptor;
}
catch
{
// Fallback for older newtonsoft serialized tokens.
if (o.Descriptor.Type == 0)
{
o.Descriptor.Type = "public-key";
}
Descriptor = JsonSerializer.Deserialize<PublicKeyCredentialDescriptor>(o.Descriptor.ToString(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
PublicKey = o.PublicKey;
UserHandle = o.UserHandle;
SignatureCounter = o.SignatureCounter;
CredType = o.CredType;
RegDate = o.RegDate;
AaGuid = o.AaGuid;
Migrated = o.Migrated;
}
public string Name { get; set; }
public PublicKeyCredentialDescriptor Descriptor { get; internal set; }
public byte[] PublicKey { get; internal set; }
public byte[] UserHandle { get; internal set; }
public uint SignatureCounter { get; set; }
public string CredType { get; internal set; }
public DateTime RegDate { get; internal set; }
public Guid AaGuid { get; internal set; }
public bool Migrated { get; internal set; }
}
public static bool RequiresPremium(TwoFactorProviderType type)
{
switch (type)
{
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.U2f: // Keep to ensure old U2f keys are considered premium
case TwoFactorProviderType.WebAuthn:
return true;
default:
return false;
}
}
}