1
0
mirror of https://github.com/bitwarden/server.git synced 2025-01-11 20:10:38 +01:00

Added X.509 cert validation copy value buttons (#923)

This commit is contained in:
Chad Scharf 2020-09-09 11:32:33 -04:00 committed by GitHub
parent 1c3ba46246
commit b429f6908d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 27 deletions

View File

@ -6,6 +6,10 @@ using Bit.Core;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Sso; using Bit.Core.Sso;
using U2F.Core.Utils;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace Bit.Portal.Models namespace Bit.Portal.Models
{ {
@ -144,6 +148,35 @@ namespace Bit.Portal.Models
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"), yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"),
new[] { nameof(model.IdpSingleSignOnServiceUrl) }); new[] { nameof(model.IdpSingleSignOnServiceUrl) });
} }
if (!string.IsNullOrWhiteSpace(model.IdpX509PublicCert))
{
// Validate the certificate is in a valid format
ValidationResult failedResult = null;
try
{
var certData = StripPemCertificateElements(model.IdpX509PublicCert).Base64StringToByteArray();
new X509Certificate2(certData);
}
catch (FormatException)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertInvalidFormatValidationError"),
new[] { nameof(model.IdpX509PublicCert) });
}
catch (CryptographicException cryptoEx)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertCryptographicExceptionValidationError", cryptoEx.Message),
new[] { nameof(model.IdpX509PublicCert) });
}
catch (Exception ex)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertValidationError", ex.Message),
new[] { nameof(model.IdpX509PublicCert) });
}
if (failedResult != null)
{
yield return failedResult;
}
}
} }
} }
@ -162,7 +195,7 @@ namespace Bit.Portal.Models
IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl, IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl,
IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl, IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl,
IdpArtifactResolutionServiceUrl = IdpArtifactResolutionServiceUrl, IdpArtifactResolutionServiceUrl = IdpArtifactResolutionServiceUrl,
IdpX509PublicCert = IdpX509PublicCert, IdpX509PublicCert = StripPemCertificateElements(IdpX509PublicCert),
IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm, IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm,
IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse, IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse,
IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests, IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests,
@ -174,5 +207,13 @@ namespace Bit.Portal.Models
SpValidateCertificates = SpValidateCertificates, SpValidateCertificates = SpValidateCertificates,
}; };
} }
private string StripPemCertificateElements(string certificateText)
{
return Regex.Replace(certificateText,
@"(((BEGIN|END) CERTIFICATE)|([\-\n\r\t\s\f]))",
string.Empty,
RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
} }
} }

View File

@ -224,3 +224,8 @@ input, select, textarea {
.alert.validation-summary-errors > ul { .alert.validation-summary-errors > ul {
margin-bottom: 0; margin-bottom: 0;
} }
.input-validation-error {
border: solid 1px $danger;
border-color: $danger;
}

View File

@ -25,6 +25,16 @@
$('#Data_ConfigType').change(function () { $('#Data_ConfigType').change(function () {
toggleVisibility(); toggleVisibility();
}); });
$('.copy-button').on('click', function () {
const $control = $(this).closest('.form-group').find('input[type="text"], textarea');
if ($control.length > 0) {
const elem = $control[0];
elem.select();
elem.setSelectionRange(0, $control.val().length);
document.execCommand('copy');
}
});
}); });
</script> </script>
} }
@ -42,7 +52,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.ConfigType"></label> <label asp-for="Data.ConfigType"></label>
<select asp-for="Data.ConfigType" asp-items="Model.ConfigTypes" class="form-control"></select> <select asp-for="Data.ConfigType" asp-items="Model.ConfigTypes" class="form-control"></select>
</div> </div>
@ -53,47 +63,64 @@
<div class="config-section"> <div class="config-section">
<h2>@i18nService.T("OpenIdConnectConfig")</h2> <h2>@i18nService.T("OpenIdConnectConfig")</h2>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.CallbackPath">@i18nService.T("CallbackPath")</label> <label asp-for="Data.CallbackPath">@i18nService.T("CallbackPath")</label>
<div class="input-group">
<input asp-for="Data.CallbackPath" class="form-control" readonly> <input asp-for="Data.CallbackPath" class="form-control" readonly>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary copy-button"
aria-label="@i18nService.T("CopyCallbackPath")" title="@i18nService.T("CopyCallbackPath")"
tabindex="-1">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SignedOutCallbackPath">@i18nService.T("SignedOutCallbackPath")</label> <label asp-for="Data.SignedOutCallbackPath">@i18nService.T("SignedOutCallbackPath")</label>
<div class="input-group">
<input asp-for="Data.SignedOutCallbackPath" class="form-control" readonly> <input asp-for="Data.SignedOutCallbackPath" class="form-control" readonly>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary copy-button"
aria-label="@i18nService.T("CopySignedOutCallbackPath")" title="@i18nService.T("CopySignedOutCallbackPath")"
tabindex="-1">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.Authority">@i18nService.T("Authority")</label> <label asp-for="Data.Authority">@i18nService.T("Authority")</label>
<input asp-for="Data.Authority" class="form-control"> <input asp-for="Data.Authority" class="form-control">
<span asp-validation-for="Data.Authority" class="text-danger"></span> <span asp-validation-for="Data.Authority" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.ClientId">@i18nService.T("ClientId")</label> <label asp-for="Data.ClientId">@i18nService.T("ClientId")</label>
<input asp-for="Data.ClientId" class="form-control"> <input asp-for="Data.ClientId" class="form-control">
<span asp-validation-for="Data.ClientId" class="text-danger"></span> <span asp-validation-for="Data.ClientId" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.ClientSecret">@i18nService.T("ClientSecret")</label> <label asp-for="Data.ClientSecret">@i18nService.T("ClientSecret")</label>
<input asp-for="Data.ClientSecret" class="form-control"> <input asp-for="Data.ClientSecret" class="form-control">
<span asp-validation-for="Data.ClientSecret" class="text-danger"></span> <span asp-validation-for="Data.ClientSecret" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.MetadataAddress">@i18nService.T("MetadataAddress")</label> <label asp-for="Data.MetadataAddress">@i18nService.T("MetadataAddress")</label>
<input asp-for="Data.MetadataAddress" class="form-control"> <input asp-for="Data.MetadataAddress" class="form-control">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<div class="form-check"> <div class="form-check">
<input asp-for="Data.GetClaimsFromUserInfoEndpoint" type="checkbox" class="form-check-input"> <input asp-for="Data.GetClaimsFromUserInfoEndpoint" type="checkbox" class="form-check-input">
<label asp-for="Data.GetClaimsFromUserInfoEndpoint" class="form-check-label"></label> <label asp-for="Data.GetClaimsFromUserInfoEndpoint" class="form-check-label"></label>
@ -108,33 +135,53 @@
<div class="config-section"> <div class="config-section">
<h2>@i18nService.T("SamlSpConfig")</h2> <h2>@i18nService.T("SamlSpConfig")</h2>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SpEntityId">@i18nService.T("SpEntityId")</label> <label asp-for="Data.SpEntityId">@i18nService.T("SpEntityId")</label>
<div class="input-group">
<input asp-for="Data.SpEntityId" class="form-control" readonly> <input asp-for="Data.SpEntityId" class="form-control" readonly>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary copy-button"
aria-label="@i18nService.T("CopySpEntityId")" title="@i18nService.T("CopySpEntityId")"
tabindex="-1">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SpAcsUrl">@i18nService.T("SpAcsUrl")</label> <label asp-for="Data.SpAcsUrl">@i18nService.T("SpAcsUrl")</label>
<div class="input-group">
<input asp-for="Data.SpAcsUrl" class="form-control" readonly> <input asp-for="Data.SpAcsUrl" class="form-control" readonly>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary copy-button"
aria-label="@i18nService.T("CopySpAcsUrl")" title="@i18nService.T("CopySpAcsUrl")"
tabindex="-1">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="col-1">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SpNameIdFormat">@i18nService.T("NameIdFormat")</label> <label asp-for="Data.SpNameIdFormat">@i18nService.T("NameIdFormat")</label>
<select asp-for="Data.SpNameIdFormat" asp-items="Model.SpNameIdFormats" <select asp-for="Data.SpNameIdFormat" asp-items="Model.SpNameIdFormats"
class="form-control"></select> class="form-control"></select>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SpOutboundSigningAlgorithm">@i18nService.T("OutboundSigningAlgorithm")</label> <label asp-for="Data.SpOutboundSigningAlgorithm">@i18nService.T("OutboundSigningAlgorithm")</label>
<select asp-for="Data.SpOutboundSigningAlgorithm" asp-items="Model.SigningAlgorithms" <select asp-for="Data.SpOutboundSigningAlgorithm" asp-items="Model.SigningAlgorithms"
class="form-control"></select> class="form-control"></select>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.SpSigningBehavior">@i18nService.T("SigningBehavior")</label> <label asp-for="Data.SpSigningBehavior">@i18nService.T("SigningBehavior")</label>
<select asp-for="Data.SpSigningBehavior" asp-items="Model.SigningBehaviors" <select asp-for="Data.SpSigningBehavior" asp-items="Model.SigningBehaviors"
class="form-control"></select> class="form-control"></select>
@ -159,46 +206,47 @@
<h2>@i18nService.T("SamlIdpConfig")</h2> <h2>@i18nService.T("SamlIdpConfig")</h2>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpEntityId">@i18nService.T("EntityId")</label> <label asp-for="Data.IdpEntityId">@i18nService.T("EntityId")</label>
<input asp-for="Data.IdpEntityId" class="form-control"> <input asp-for="Data.IdpEntityId" class="form-control">
<span asp-validation-for="Data.IdpEntityId" class="text-danger"></span> <span asp-validation-for="Data.IdpEntityId" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpBindingType"></label> <label asp-for="Data.IdpBindingType"></label>
<select asp-for="Data.IdpBindingType" asp-items="Model.BindingTypes" class="form-control"></select> <select asp-for="Data.IdpBindingType" asp-items="Model.BindingTypes" class="form-control"></select>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpSingleSignOnServiceUrl">@i18nService.T("SingleSignOnServiceUrl")</label> <label asp-for="Data.IdpSingleSignOnServiceUrl">@i18nService.T("SingleSignOnServiceUrl")</label>
<input asp-for="Data.IdpSingleSignOnServiceUrl" class="form-control"> <input asp-for="Data.IdpSingleSignOnServiceUrl" class="form-control">
<span asp-validation-for="Data.IdpSingleSignOnServiceUrl" class="text-danger"></span> <span asp-validation-for="Data.IdpSingleSignOnServiceUrl" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpSingleLogoutServiceUrl">@i18nService.T("SingleLogoutServiceUrl")</label> <label asp-for="Data.IdpSingleLogoutServiceUrl">@i18nService.T("SingleLogoutServiceUrl")</label>
<input asp-for="Data.IdpSingleLogoutServiceUrl" class="form-control"> <input asp-for="Data.IdpSingleLogoutServiceUrl" class="form-control">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpArtifactResolutionServiceUrl"></label> <label asp-for="Data.IdpArtifactResolutionServiceUrl"></label>
<input asp-for="Data.IdpArtifactResolutionServiceUrl" class="form-control"> <input asp-for="Data.IdpArtifactResolutionServiceUrl" class="form-control">
<span asp-validation-for="Data.IdpArtifactResolutionServiceUrl" class="text-danger"></span> <span asp-validation-for="Data.IdpArtifactResolutionServiceUrl" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpX509PublicCert">@i18nService.T("X509PublicCert")</label> <label asp-for="Data.IdpX509PublicCert">@i18nService.T("X509PublicCert")</label>
<textarea asp-for="Data.IdpX509PublicCert" class="form-control"></textarea> <textarea asp-for="Data.IdpX509PublicCert" class="form-control form-control-sm text-monospace" rows="6"></textarea>
<span asp-validation-for="Data.IdpX509PublicCert" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 form-group"> <div class="col-7 form-group">
<label asp-for="Data.IdpOutboundSigningAlgorithm"></label> <label asp-for="Data.IdpOutboundSigningAlgorithm"></label>
<select asp-for="Data.IdpOutboundSigningAlgorithm" asp-items="Model.SigningAlgorithms" <select asp-for="Data.IdpOutboundSigningAlgorithm" asp-items="Model.SigningAlgorithms"
class="form-control"></select> class="form-control"></select>

View File

@ -276,15 +276,19 @@
</data> </data>
<data name="NotConfigured" xml:space="preserve"> <data name="NotConfigured" xml:space="preserve">
<value>Not Configured</value> <value>Not Configured</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="Unspecified" xml:space="preserve"> <data name="Unspecified" xml:space="preserve">
<value>Unspecified</value> <value>Unspecified</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="EmailAddress" xml:space="preserve"> <data name="EmailAddress" xml:space="preserve">
<value>Email Address</value> <value>Email Address</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="X509SubjectName" xml:space="preserve"> <data name="X509SubjectName" xml:space="preserve">
<value>X.509 Subject Name</value> <value>X.509 Subject Name</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="WindowsDomainQualifiedName" xml:space="preserve"> <data name="WindowsDomainQualifiedName" xml:space="preserve">
<value>Windows Domain Qualified Name</value> <value>Windows Domain Qualified Name</value>
@ -297,9 +301,11 @@
</data> </data>
<data name="Persistent" xml:space="preserve"> <data name="Persistent" xml:space="preserve">
<value>Persistent</value> <value>Persistent</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="Transient" xml:space="preserve"> <data name="Transient" xml:space="preserve">
<value>Transient</value> <value>Transient</value>
<comment>A SAML Name ID format</comment>
</data> </data>
<data name="PrivateKey" xml:space="preserve"> <data name="PrivateKey" xml:space="preserve">
<value>Private Key</value> <value>Private Key</value>
@ -448,4 +454,37 @@
<data name="WelcomeToBusinessPortal"> <data name="WelcomeToBusinessPortal">
<value>Welcome to the Bitwarden Business Portal</value> <value>Welcome to the Bitwarden Business Portal</value>
</data> </data>
<data name="IdpX509PublicCertValidationError" xml:space="preserve">
<value>The IdP public certificate provided is invalid: {0}</value>
</data>
<data name="IdpX509PublicCertInvalidFormatValidationError" xml:space="preserve">
<value>The IdP public certificate provided is not a valid Base64 encoded string, contains illegal characters or whitespace, or is incomplete.</value>
</data>
<data name="IdpX509PublicCertCryptographicExceptionValidationError" xml:space="preserve">
<value>The IdP public certificate provided does not appear to be a valid certificate, please ensure this is a valid, Base64 encoded PEM or CER format public certificate valid for signing: {0}</value>
</data>
<data name="CopyCallbackPath">
<value>Copy the OIDC callback path to your clipboard</value>
</data>
<data name="CopySignedOutCallbackPath">
<value>Copy the OIDC signed out callback path to your clipboard</value>
</data>
<data name="CopySpEntityId">
<value>Copy the SP Entity Id to your clipboard</value>
</data>
<data name="CopySpAcsUrl">
<value>Copy the Assertion Consumer Service (ACS) URL to your clipboard</value>
</data>
<data name="HttpRedirect">
<value>Redirect</value>
<comment>A SAML binding type, Redirect</comment>
</data>
<data name="HttpPost">
<value>HTTP POST</value>
<comment>A SAML binding type, HTTP POST</comment>
</data>
<data name="Artifact">
<value>Artifact</value>
<comment>A SAML binding type, Artifact</comment>
</data>
</root> </root>