mirror of
https://github.com/bitwarden/server.git
synced 2025-04-04 20:50:21 -05:00

* [AC-1145] Add TDE feature flag * [AC-1145] Update .gitignore to ignore flags.json in the Api project * [AC-1145] Introduce MemberDecryptionType property on SsoConfigurationData * [AC-1145] Add MemberDecryptionType to the SsoConfigurationDataRequest model * [AC-1145] Automatically enable password reset policy on TDE selection * [AC-1145] Remove references to obsolete KeyConnectorEnabled field * [AC-1145] Formatting * [AC-1145] Update XML doc reference to MemberDecryptionType
233 lines
9.5 KiB
C#
233 lines
9.5 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text.RegularExpressions;
|
|
using Bit.Core.Auth.Entities;
|
|
using Bit.Core.Auth.Enums;
|
|
using Bit.Core.Auth.Models.Data;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Sso;
|
|
using Bit.Core.Utilities;
|
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
|
|
|
namespace Bit.Api.Auth.Models.Request.Organizations;
|
|
|
|
public class OrganizationSsoRequestModel
|
|
{
|
|
[Required]
|
|
public bool Enabled { get; set; }
|
|
[StringLength(50)]
|
|
public string Identifier { get; set; }
|
|
[Required]
|
|
public SsoConfigurationDataRequest Data { get; set; }
|
|
|
|
public SsoConfig ToSsoConfig(Guid organizationId)
|
|
{
|
|
return ToSsoConfig(new SsoConfig { OrganizationId = organizationId });
|
|
}
|
|
|
|
public SsoConfig ToSsoConfig(SsoConfig existingConfig)
|
|
{
|
|
existingConfig.Enabled = Enabled;
|
|
var configurationData = Data.ToConfigurationData();
|
|
existingConfig.SetData(configurationData);
|
|
return existingConfig;
|
|
}
|
|
}
|
|
|
|
public class SsoConfigurationDataRequest : IValidatableObject
|
|
{
|
|
public SsoConfigurationDataRequest() { }
|
|
|
|
[Required]
|
|
public SsoType ConfigType { get; set; }
|
|
public MemberDecryptionType MemberDecryptionType { get; set; }
|
|
|
|
[Obsolete("Use MemberDecryptionType instead")]
|
|
public bool KeyConnectorEnabled
|
|
{
|
|
// Setter is kept for backwards compatibility with older clients that still use this property.
|
|
set { MemberDecryptionType = value ? MemberDecryptionType.KeyConnector : MemberDecryptionType.MasterPassword; }
|
|
}
|
|
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 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; }
|
|
|
|
// SAML2 IDP
|
|
public string IdpEntityId { get; set; }
|
|
public Saml2BindingType IdpBindingType { get; set; }
|
|
public string IdpSingleSignOnServiceUrl { get; set; }
|
|
public string IdpSingleLogoutServiceUrl { get; set; }
|
|
public string IdpArtifactResolutionServiceUrl { get => null; set { /*IGNORE*/ } }
|
|
public string IdpX509PublicCert { get; set; }
|
|
public string IdpOutboundSigningAlgorithm { get; set; }
|
|
public bool? IdpAllowUnsolicitedAuthnResponse { get; set; }
|
|
public bool? IdpDisableOutboundLogoutRequests { get; set; }
|
|
public bool? IdpWantAuthnRequestsSigned { get; set; }
|
|
|
|
public IEnumerable<ValidationResult> Validate(ValidationContext context)
|
|
{
|
|
var i18nService = context.GetService(typeof(II18nService)) as I18nService;
|
|
|
|
if (ConfigType == SsoType.OpenIdConnect)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Authority))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("AuthorityValidationError"),
|
|
new[] { nameof(Authority) });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(ClientId))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientIdValidationError"),
|
|
new[] { nameof(ClientId) });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(ClientSecret))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientSecretValidationError"),
|
|
new[] { nameof(ClientSecret) });
|
|
}
|
|
}
|
|
else if (ConfigType == SsoType.Saml2)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(IdpEntityId))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpEntityIdValidationError"),
|
|
new[] { nameof(IdpEntityId) });
|
|
}
|
|
|
|
if (!Uri.IsWellFormedUriString(IdpEntityId, UriKind.Absolute) && string.IsNullOrWhiteSpace(IdpSingleSignOnServiceUrl))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"),
|
|
new[] { nameof(IdpSingleSignOnServiceUrl) });
|
|
}
|
|
|
|
if (InvalidServiceUrl(IdpSingleSignOnServiceUrl))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlInvalid"),
|
|
new[] { nameof(IdpSingleSignOnServiceUrl) });
|
|
}
|
|
|
|
if (InvalidServiceUrl(IdpSingleLogoutServiceUrl))
|
|
{
|
|
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleLogoutServiceUrlInvalid"),
|
|
new[] { nameof(IdpSingleLogoutServiceUrl) });
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(IdpX509PublicCert))
|
|
{
|
|
// Validate the certificate is in a valid format
|
|
ValidationResult failedResult = null;
|
|
try
|
|
{
|
|
var certData = CoreHelpers.Base64UrlDecode(StripPemCertificateElements(IdpX509PublicCert));
|
|
new X509Certificate2(certData);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertInvalidFormatValidationError"),
|
|
new[] { nameof(IdpX509PublicCert) });
|
|
}
|
|
catch (CryptographicException cryptoEx)
|
|
{
|
|
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertCryptographicExceptionValidationError", cryptoEx.Message),
|
|
new[] { nameof(IdpX509PublicCert) });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertValidationError", ex.Message),
|
|
new[] { nameof(IdpX509PublicCert) });
|
|
}
|
|
if (failedResult != null)
|
|
{
|
|
yield return failedResult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public SsoConfigurationData ToConfigurationData()
|
|
{
|
|
return new SsoConfigurationData
|
|
{
|
|
ConfigType = ConfigType,
|
|
MemberDecryptionType = MemberDecryptionType,
|
|
KeyConnectorUrl = KeyConnectorUrl,
|
|
Authority = Authority,
|
|
ClientId = ClientId,
|
|
ClientSecret = ClientSecret,
|
|
MetadataAddress = MetadataAddress,
|
|
GetClaimsFromUserInfoEndpoint = GetClaimsFromUserInfoEndpoint.GetValueOrDefault(),
|
|
RedirectBehavior = RedirectBehavior,
|
|
IdpEntityId = IdpEntityId,
|
|
IdpBindingType = IdpBindingType,
|
|
IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl,
|
|
IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl,
|
|
IdpArtifactResolutionServiceUrl = null,
|
|
IdpX509PublicCert = StripPemCertificateElements(IdpX509PublicCert),
|
|
IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm,
|
|
IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse.GetValueOrDefault(),
|
|
IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests.GetValueOrDefault(),
|
|
IdpWantAuthnRequestsSigned = IdpWantAuthnRequestsSigned.GetValueOrDefault(),
|
|
SpNameIdFormat = SpNameIdFormat,
|
|
SpOutboundSigningAlgorithm = SpOutboundSigningAlgorithm ?? SamlSigningAlgorithms.Sha256,
|
|
SpSigningBehavior = SpSigningBehavior,
|
|
SpWantAssertionsSigned = SpWantAssertionsSigned.GetValueOrDefault(),
|
|
SpValidateCertificates = SpValidateCertificates.GetValueOrDefault(),
|
|
SpMinIncomingSigningAlgorithm = SpMinIncomingSigningAlgorithm,
|
|
AdditionalScopes = AdditionalScopes,
|
|
AdditionalUserIdClaimTypes = AdditionalUserIdClaimTypes,
|
|
AdditionalEmailClaimTypes = AdditionalEmailClaimTypes,
|
|
AdditionalNameClaimTypes = AdditionalNameClaimTypes,
|
|
AcrValues = AcrValues,
|
|
ExpectedReturnAcrValue = ExpectedReturnAcrValue,
|
|
};
|
|
}
|
|
|
|
private string StripPemCertificateElements(string certificateText)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(certificateText))
|
|
{
|
|
return null;
|
|
}
|
|
return Regex.Replace(certificateText,
|
|
@"(((BEGIN|END) CERTIFICATE)|([\-\n\r\t\s\f]))",
|
|
string.Empty,
|
|
RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
}
|
|
|
|
private bool InvalidServiceUrl(string url)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
{
|
|
return false;
|
|
}
|
|
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
|
{
|
|
return true;
|
|
}
|
|
return Regex.IsMatch(url, "[<>\"]");
|
|
}
|
|
}
|