1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

Add support for Key Connector OTP and account migration (#1663)

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Oscar Hinton
2021-11-09 16:37:32 +01:00
committed by GitHub
parent f6bc35b2d0
commit fd37cb5a12
62 changed files with 3799 additions and 306 deletions

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class DeleteAccountRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -1,10 +1,10 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
using Bit.Core.Models.Api;
namespace Bit.Core.Models.Api
{
public class EmailRequestModel
public class EmailRequestModel : SecretVerificationRequestModel
{
[Required]
[StrictEmailAddress]
@ -12,9 +12,6 @@ namespace Bit.Core.Models.Api
public string NewEmail { get; set; }
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
[Required]
[StringLength(300)]
public string NewMasterPasswordHash { get; set; }
[Required]
public string Token { get; set; }

View File

@ -1,16 +1,14 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
using Bit.Core.Models.Api;
namespace Bit.Core.Models.Api
{
public class EmailTokenRequestModel
public class EmailTokenRequestModel : SecretVerificationRequestModel
{
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string NewEmail { get; set; }
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -1,13 +1,9 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class PasswordRequestModel
public class PasswordRequestModel : SecretVerificationRequestModel
{
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
[Required]
[StringLength(300)]
public string NewMasterPasswordHash { get; set; }

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class SecretVerificationRequestModel : IValidatableObject
{
[StringLength(300)]
public string MasterPasswordHash { get; set; }
public string OTP { get; set; }
public string Secret => !string.IsNullOrEmpty(MasterPasswordHash) ? MasterPasswordHash : OTP;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(Secret))
{
yield return new ValidationResult("MasterPasswordHash or OTP must be supplied.");
}
}
}
}

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class SecurityStampRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -4,7 +4,7 @@ using Bit.Core.Models.Table;
namespace Bit.Core.Models.Api.Request.Accounts
{
public class SetCryptoAgentKeyRequestModel
public class SetKeyConnectorKeyRequestModel
{
[Required]
public string Key { get; set; }

View File

@ -2,9 +2,9 @@
namespace Bit.Core.Models.Api
{
public class ApiKeyRequestModel
public class VerifyOTPRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
public string OTP { get; set; }
}
}

View File

@ -1,12 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class VerifyPasswordRequestModel
{
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class CipherPurgeRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class OrganizationDeleteRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
}
}

View File

@ -8,7 +8,6 @@ using Bit.Core.Sso;
using U2F.Core.Utils;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.RegularExpressions;
using Bit.Core.Models.Table;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
@ -40,47 +39,11 @@ namespace Bit.Core.Models.Api
{
public SsoConfigurationDataRequest() {}
public SsoConfigurationDataRequest(SsoConfigurationData configurationData)
{
ConfigType = configurationData.ConfigType;
UseCryptoAgent = configurationData.UseCryptoAgent;
CryptoAgentUrl = configurationData.CryptoAgentUrl;
Authority = configurationData.Authority;
ClientId = configurationData.ClientId;
ClientSecret = configurationData.ClientSecret;
MetadataAddress = configurationData.MetadataAddress;
RedirectBehavior = configurationData.RedirectBehavior;
GetClaimsFromUserInfoEndpoint = configurationData.GetClaimsFromUserInfoEndpoint;
IdpEntityId = configurationData.IdpEntityId;
IdpBindingType = configurationData.IdpBindingType;
IdpSingleSignOnServiceUrl = configurationData.IdpSingleSignOnServiceUrl;
IdpSingleLogoutServiceUrl = configurationData.IdpSingleLogoutServiceUrl;
IdpArtifactResolutionServiceUrl = configurationData.IdpArtifactResolutionServiceUrl;
IdpX509PublicCert = configurationData.IdpX509PublicCert;
IdpOutboundSigningAlgorithm = configurationData.IdpOutboundSigningAlgorithm;
IdpAllowUnsolicitedAuthnResponse = configurationData.IdpAllowUnsolicitedAuthnResponse;
IdpDisableOutboundLogoutRequests = configurationData.IdpDisableOutboundLogoutRequests;
IdpWantAuthnRequestsSigned = configurationData.IdpWantAuthnRequestsSigned;
SpNameIdFormat = configurationData.SpNameIdFormat;
SpOutboundSigningAlgorithm = configurationData.SpOutboundSigningAlgorithm ?? SamlSigningAlgorithms.Sha256;
SpSigningBehavior = configurationData.SpSigningBehavior;
SpWantAssertionsSigned = configurationData.SpWantAssertionsSigned;
SpValidateCertificates = configurationData.SpValidateCertificates;
SpMinIncomingSigningAlgorithm = configurationData.SpMinIncomingSigningAlgorithm ?? SamlSigningAlgorithms.Sha256;
AdditionalScopes = configurationData.AdditionalScopes;
AdditionalUserIdClaimTypes = configurationData.AdditionalUserIdClaimTypes;
AdditionalEmailClaimTypes = configurationData.AdditionalEmailClaimTypes;
AdditionalNameClaimTypes = configurationData.AdditionalNameClaimTypes;
AcrValues = configurationData.AcrValues;
ExpectedReturnAcrValue = configurationData.ExpectedReturnAcrValue;
}
[Required]
public SsoType ConfigType { get; set; }
// Crypto Agent
public bool UseCryptoAgent { get; set; }
public string CryptoAgentUrl { get; set; }
public bool UseKeyConnector { get; set; }
public string KeyConnectorUrl { get; set; }
// OIDC
public string Authority { get; set; }
@ -215,8 +178,8 @@ namespace Bit.Core.Models.Api
return new SsoConfigurationData
{
ConfigType = ConfigType,
UseCryptoAgent = UseCryptoAgent,
CryptoAgentUrl = CryptoAgentUrl,
UseKeyConnector = UseKeyConnector,
KeyConnectorUrl = KeyConnectorUrl,
Authority = Authority,
ClientId = ClientId,
ClientSecret = ClientSecret,

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace Bit.Core.Models.Api
{
public class UpdateTwoFactorAuthenticatorRequestModel : TwoFactorRequestModel
public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationRequestModel
{
[Required]
[StringLength(50)]
@ -38,7 +38,7 @@ namespace Bit.Core.Models.Api
}
}
public class UpdateTwoFactorDuoRequestModel : TwoFactorRequestModel, IValidatableObject
public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
[StringLength(50)]
@ -111,7 +111,7 @@ namespace Bit.Core.Models.Api
}
}
public class UpdateTwoFactorYubicoOtpRequestModel : TwoFactorRequestModel, IValidatableObject
public class UpdateTwoFactorYubicoOtpRequestModel : SecretVerificationRequestModel, IValidatableObject
{
public string Key1 { get; set; }
public string Key2 { get; set; }
@ -195,7 +195,7 @@ namespace Bit.Core.Models.Api
}
}
public class TwoFactorEmailRequestModel : TwoFactorRequestModel
public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
{
[Required]
[EmailAddress]
@ -231,13 +231,18 @@ namespace Bit.Core.Models.Api
public string Name { get; set; }
}
public class TwoFactorWebAuthnDeleteRequestModel : TwoFactorRequestModel, IValidatableObject
public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
public int? Id { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var validationResult in Validate(validationContext))
{
yield return validationResult;
}
if (!Id.HasValue || Id < 0 || Id > 5)
{
yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) });
@ -252,18 +257,12 @@ namespace Bit.Core.Models.Api
public string Token { get; set; }
}
public class TwoFactorProviderRequestModel : TwoFactorRequestModel
public class TwoFactorProviderRequestModel : SecretVerificationRequestModel
{
[Required]
public TwoFactorProviderType? Type { get; set; }
}
public class TwoFactorRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
}
public class TwoFactorRecoveryRequestModel : TwoFactorEmailRequestModel
{
[Required]

View File

@ -79,6 +79,8 @@ namespace Bit.Core.Models.Api
Email = organizationUser.Email;
TwoFactorEnabled = twoFactorEnabled;
SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId);
// Prevent reset password when using key connector.
ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector;
}
public string Name { get; set; }

View File

@ -38,6 +38,12 @@ namespace Bit.Core.Models.Api
UserId = organization.UserId?.ToString();
ProviderId = organization.ProviderId?.ToString();
ProviderName = organization.ProviderName;
if (organization.SsoConfig != null)
{
var ssoConfigData = SsoConfigurationData.Deserialize(organization.SsoConfig);
UsesKeyConnector = ssoConfigData.UseKeyConnector && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl);
KeyConnectorUrl = ssoConfigData.KeyConnectorUrl;
}
}
public string Id { get; set; }
@ -68,5 +74,7 @@ namespace Bit.Core.Models.Api
public bool HasPublicAndPrivateKeys { get; set; }
public string ProviderId { get; set; }
public string ProviderName { get; set; }
public bool UsesKeyConnector { get; set; }
public string KeyConnectorUrl { get; set; }
}
}

View File

@ -31,6 +31,7 @@ namespace Bit.Core.Models.Api
PrivateKey = user.PrivateKey;
SecurityStamp = user.SecurityStamp;
ForcePasswordReset = user.ForcePasswordReset;
UsesKeyConnector = user.UsesKeyConnector;
Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o));
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
ProviderOrganizations =
@ -49,6 +50,7 @@ namespace Bit.Core.Models.Api
public string PrivateKey { get; set; }
public string SecurityStamp { get; set; }
public bool ForcePasswordReset { get; set; }
public bool UsesKeyConnector { get; set; }
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
public IEnumerable<ProfileProviderResponseModel> Providers { get; set; }
public IEnumerable<ProfileProviderOrganizationResponseModel> ProviderOrganizations { get; set; }

View File

@ -33,5 +33,6 @@ namespace Bit.Core.Models.Data
public string PrivateKey { get; set; }
public Guid? ProviderId { get; set; }
public string ProviderName { get; set; }
public string SsoConfig { get; set; }
}
}

View File

@ -23,6 +23,7 @@ namespace Bit.Core.Models.Data
public string SsoExternalId { get; set; }
public string Permissions { get; set; }
public string ResetPasswordKey { get; set; }
public bool UsesKeyConnector { get; set; }
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
{

View File

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Sso;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
namespace Bit.Core.Models.Data
@ -13,11 +15,20 @@ namespace Bit.Core.Models.Data
private const string _oidcSignedOutPath = "/oidc-signedout";
private const 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; }
// Crypto Agent
public bool UseCryptoAgent { get; set; }
public string CryptoAgentUrl { get; set; }
public bool UseKeyConnector { get; set; }
public string KeyConnectorUrl { get; set; }
// OIDC
public string Authority { get; set; }

View File

@ -1,5 +1,4 @@
using System;
using System.Text.Json;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Table
@ -13,11 +12,6 @@ namespace Bit.Core.Models.Table
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
private JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
public void SetNewId()
{
// int will be auto-populated
@ -26,12 +20,12 @@ namespace Bit.Core.Models.Table
public SsoConfigurationData GetData()
{
return JsonSerializer.Deserialize<SsoConfigurationData>(Data, _jsonSerializerOptions);
return SsoConfigurationData.Deserialize(Data);
}
public void SetData(SsoConfigurationData data)
{
Data = JsonSerializer.Serialize(data, _jsonSerializerOptions);
Data = data.Serialize();
}
}
}

View File

@ -58,7 +58,7 @@ namespace Bit.Core.Models.Table
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
public bool ForcePasswordReset { get; set; }
public bool UsesCryptoAgent { get; set; }
public bool UsesKeyConnector { get; set; }
public void SetNewId()
{