mirror of
https://github.com/bitwarden/server.git
synced 2024-12-26 17:37:36 +01:00
SSO - Added custom scopes and claim types for OIDC (#1133)
* SSO - Added custom scopes and claim types for OIDC * Removed redundant field labels * Added acr_values to OIDC config + request
This commit is contained in:
parent
9f42357705
commit
6cc317c4ba
@ -49,6 +49,11 @@ namespace Bit.Portal.Models
|
||||
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;
|
||||
}
|
||||
|
||||
[Required]
|
||||
@ -72,6 +77,16 @@ namespace Bit.Portal.Models
|
||||
public OpenIdConnectRedirectBehavior RedirectBehavior { get; set; }
|
||||
[Display(Name = "GetClaimsFromUserInfoEndpoint")]
|
||||
public bool GetClaimsFromUserInfoEndpoint { get; set; }
|
||||
[Display(Name = "AdditionalScopes")]
|
||||
public string AdditionalScopes { get; set; }
|
||||
[Display(Name = "AdditionalUserIdClaimTypes")]
|
||||
public string AdditionalUserIdClaimTypes { get; set; }
|
||||
[Display(Name = "AdditionalEmailClaimTypes")]
|
||||
public string AdditionalEmailClaimTypes { get; set; }
|
||||
[Display(Name = "AdditionalNameClaimTypes")]
|
||||
public string AdditionalNameClaimTypes { get; set; }
|
||||
[Display(Name = "AcrValues")]
|
||||
public string AcrValues { get; set; }
|
||||
|
||||
// SAML2 SP
|
||||
[Display(Name = "SpEntityId")]
|
||||
@ -218,6 +233,11 @@ namespace Bit.Portal.Models
|
||||
SpWantAssertionsSigned = SpWantAssertionsSigned,
|
||||
SpValidateCertificates = SpValidateCertificates,
|
||||
SpMinIncomingSigningAlgorithm = SpMinIncomingSigningAlgorithm,
|
||||
AdditionalScopes = AdditionalScopes,
|
||||
AdditionalUserIdClaimTypes = AdditionalUserIdClaimTypes,
|
||||
AdditionalEmailClaimTypes = AdditionalEmailClaimTypes,
|
||||
AdditionalNameClaimTypes = AdditionalNameClaimTypes,
|
||||
AcrValues = AcrValues,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@
|
||||
<h2>@i18nService.T("OpenIdConnectConfig")</h2>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.CallbackPath">@i18nService.T("CallbackPath")</label>
|
||||
<label asp-for="Data.CallbackPath"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Data.CallbackPath" class="form-control" readonly>
|
||||
<div class="input-group-append">
|
||||
@ -79,7 +79,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SignedOutCallbackPath">@i18nService.T("SignedOutCallbackPath")</label>
|
||||
<label asp-for="Data.SignedOutCallbackPath"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Data.SignedOutCallbackPath" class="form-control" readonly>
|
||||
<div class="input-group-append">
|
||||
@ -94,34 +94,34 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.Authority">@i18nService.T("Authority")</label>
|
||||
<label asp-for="Data.Authority"></label>
|
||||
<input asp-for="Data.Authority" class="form-control">
|
||||
<span asp-validation-for="Data.Authority" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.ClientId">@i18nService.T("ClientId")</label>
|
||||
<label asp-for="Data.ClientId"></label>
|
||||
<input asp-for="Data.ClientId" class="form-control">
|
||||
<span asp-validation-for="Data.ClientId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.ClientSecret">@i18nService.T("ClientSecret")</label>
|
||||
<label asp-for="Data.ClientSecret"></label>
|
||||
<input asp-for="Data.ClientSecret" class="form-control">
|
||||
<span asp-validation-for="Data.ClientSecret" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.MetadataAddress">@i18nService.T("MetadataAddress")</label>
|
||||
<label asp-for="Data.MetadataAddress"></label>
|
||||
<input asp-for="Data.MetadataAddress" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.RedirectBehavior">@i18nService.T("RedirectBehavior")</label>
|
||||
<label asp-for="Data.RedirectBehavior"></label>
|
||||
<select asp-for="Data.RedirectBehavior" asp-items="Model.RedirectBehaviors"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
@ -134,6 +134,36 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.AdditionalScopes"></label>
|
||||
<input asp-for="Data.AdditionalScopes" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.AdditionalUserIdClaimTypes"></label>
|
||||
<input asp-for="Data.AdditionalUserIdClaimTypes" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.AdditionalEmailClaimTypes"></label>
|
||||
<input asp-for="Data.AdditionalEmailClaimTypes" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.AdditionalNameClaimTypes"></label>
|
||||
<input asp-for="Data.AdditionalNameClaimTypes" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.AcrValues"></label>
|
||||
<input asp-for="Data.AcrValues" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -143,7 +173,7 @@
|
||||
<h2>@i18nService.T("SamlSpConfig")</h2>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpEntityId">@i18nService.T("SpEntityId")</label>
|
||||
<label asp-for="Data.SpEntityId"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Data.SpEntityId" class="form-control" readonly>
|
||||
<div class="input-group-append">
|
||||
@ -158,7 +188,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpMetadataUrl">@i18nService.T("SpMetadataUrl")</label>
|
||||
<label asp-for="Data.SpMetadataUrl"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Data.SpMetadataUrl" class="form-control" readonly>
|
||||
<div class="input-group-append">
|
||||
@ -182,7 +212,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpAcsUrl">@i18nService.T("SpAcsUrl")</label>
|
||||
<label asp-for="Data.SpAcsUrl"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Data.SpAcsUrl" class="form-control" readonly>
|
||||
<div class="input-group-append">
|
||||
@ -199,28 +229,28 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpNameIdFormat">@i18nService.T("NameIdFormat")</label>
|
||||
<label asp-for="Data.SpNameIdFormat"></label>
|
||||
<select asp-for="Data.SpNameIdFormat" asp-items="Model.SpNameIdFormats"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpOutboundSigningAlgorithm">@i18nService.T("OutboundSigningAlgorithm")</label>
|
||||
<label asp-for="Data.SpOutboundSigningAlgorithm"></label>
|
||||
<select asp-for="Data.SpOutboundSigningAlgorithm" asp-items="Model.SigningAlgorithms"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpSigningBehavior">@i18nService.T("SigningBehavior")</label>
|
||||
<label asp-for="Data.SpSigningBehavior"></label>
|
||||
<select asp-for="Data.SpSigningBehavior" asp-items="Model.SigningBehaviors"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.SpMinIncomingSigningAlgorithm">@i18nService.T("MinIncomingSigningAlgorithm")</label>
|
||||
<label asp-for="Data.SpMinIncomingSigningAlgorithm"></label>
|
||||
<select asp-for="Data.SpMinIncomingSigningAlgorithm" asp-items="Model.SigningAlgorithms"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
@ -245,7 +275,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.IdpEntityId">@i18nService.T("EntityId")</label>
|
||||
<label asp-for="Data.IdpEntityId"></label>
|
||||
<input asp-for="Data.IdpEntityId" class="form-control">
|
||||
<span asp-validation-for="Data.IdpEntityId" class="text-danger"></span>
|
||||
</div>
|
||||
@ -258,14 +288,14 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.IdpSingleSignOnServiceUrl">@i18nService.T("SingleSignOnServiceUrl")</label>
|
||||
<label asp-for="Data.IdpSingleSignOnServiceUrl"></label>
|
||||
<input asp-for="Data.IdpSingleSignOnServiceUrl" class="form-control">
|
||||
<span asp-validation-for="Data.IdpSingleSignOnServiceUrl" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.IdpSingleLogoutServiceUrl">@i18nService.T("SingleLogoutServiceUrl")</label>
|
||||
<label asp-for="Data.IdpSingleLogoutServiceUrl"></label>
|
||||
<input asp-for="Data.IdpSingleLogoutServiceUrl" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
@ -278,7 +308,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 form-group">
|
||||
<label asp-for="Data.IdpX509PublicCert">@i18nService.T("X509PublicCert")</label>
|
||||
<label asp-for="Data.IdpX509PublicCert"></label>
|
||||
<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>
|
||||
|
@ -23,6 +23,8 @@ using System.Threading.Tasks;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Sso.Controllers
|
||||
{
|
||||
@ -204,7 +206,7 @@ namespace Bit.Sso.Controllers
|
||||
{
|
||||
// Read external identity from the temporary cookie
|
||||
var result = await HttpContext.AuthenticateAsync(
|
||||
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
|
||||
if (result?.Succeeded != true)
|
||||
{
|
||||
throw new Exception(_i18nService.T("ExternalAuthenticationError"));
|
||||
@ -215,7 +217,7 @@ namespace Bit.Sso.Controllers
|
||||
_logger.LogDebug("External claims: {@claims}", externalClaims);
|
||||
|
||||
// Lookup our user and external provider info
|
||||
var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
|
||||
var (user, provider, providerUserId, claims, ssoConfigData) = await FindUserFromExternalProviderAsync(result);
|
||||
if (user == null)
|
||||
{
|
||||
// This might be where you might initiate a custom workflow for user registration
|
||||
@ -223,7 +225,7 @@ namespace Bit.Sso.Controllers
|
||||
// simply auto-provisions new external user
|
||||
var userIdentifier = result.Properties.Items.Keys.Contains("user_identifier") ?
|
||||
result.Properties.Items["user_identifier"] : null;
|
||||
user = await AutoProvisionUserAsync(provider, providerUserId, claims, userIdentifier);
|
||||
user = await AutoProvisionUserAsync(provider, providerUserId, claims, userIdentifier, ssoConfigData);
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
@ -305,9 +307,23 @@ namespace Bit.Sso.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)>
|
||||
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims, SsoConfigurationData config)>
|
||||
FindUserFromExternalProviderAsync(AuthenticateResult result)
|
||||
{
|
||||
var provider = result.Properties.Items["scheme"];
|
||||
var orgId = new Guid(provider);
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgId);
|
||||
if (ssoConfig == null || !ssoConfig.Enabled)
|
||||
{
|
||||
throw new Exception(_i18nService.T("OrganizationOrSsoConfigNotFound"));
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
var ssoConfigData = JsonSerializer.Deserialize<SsoConfigurationData>(ssoConfig.Data, options);
|
||||
|
||||
var externalUser = result.Principal;
|
||||
|
||||
// Ensure the NameIdentifier used is not a transient name ID, if so, we need a different attribute
|
||||
@ -320,7 +336,9 @@ namespace Bit.Sso.Controllers
|
||||
// Try to determine the unique id of the external user (issued by the provider)
|
||||
// the most common claim type for that are the sub claim and the NameIdentifier
|
||||
// depending on the external provider, some other claim type might be used
|
||||
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
|
||||
var customUserIdClaimTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();
|
||||
var userIdClaim = externalUser.FindFirst(c => customUserIdClaimTypes.Contains(c.Type)) ??
|
||||
externalUser.FindFirst(JwtClaimTypes.Subject) ??
|
||||
externalUser.FindFirst(nameIdIsNotTransient) ??
|
||||
// Some SAML providers may use the `uid` attribute for this
|
||||
// where a transient NameID has been sent in the subject
|
||||
@ -333,26 +351,19 @@ namespace Bit.Sso.Controllers
|
||||
var claims = externalUser.Claims.ToList();
|
||||
claims.Remove(userIdClaim);
|
||||
|
||||
var provider = result.Properties.Items["scheme"];
|
||||
// find external user
|
||||
var providerUserId = userIdClaim.Value;
|
||||
|
||||
// find external user
|
||||
var orgId = new Guid(provider);
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgId);
|
||||
if (ssoConfig == null || !ssoConfig.Enabled)
|
||||
{
|
||||
throw new Exception(_i18nService.T("OrganizationOrSsoConfigNotFound"));
|
||||
}
|
||||
var user = await _userRepository.GetBySsoUserAsync(providerUserId, orgId);
|
||||
|
||||
return (user, provider, providerUserId, claims);
|
||||
return (user, provider, providerUserId, claims, ssoConfigData);
|
||||
}
|
||||
|
||||
private async Task<User> AutoProvisionUserAsync(string provider, string providerUserId,
|
||||
IEnumerable<Claim> claims, string userIdentifier)
|
||||
IEnumerable<Claim> claims, string userIdentifier, SsoConfigurationData config)
|
||||
{
|
||||
var name = GetName(claims);
|
||||
var email = GetEmailAddress(claims);
|
||||
var name = GetName(claims, config.GetAdditionalNameClaimTypes());
|
||||
var email = GetEmailAddress(claims, config.GetAdditionalEmailClaimTypes());
|
||||
if (string.IsNullOrWhiteSpace(email) && providerUserId.Contains("@"))
|
||||
{
|
||||
email = providerUserId;
|
||||
@ -498,12 +509,13 @@ namespace Bit.Sso.Controllers
|
||||
return user;
|
||||
}
|
||||
|
||||
private string GetEmailAddress(IEnumerable<Claim> claims)
|
||||
private string GetEmailAddress(IEnumerable<Claim> claims, IEnumerable<string> additionalClaimTypes)
|
||||
{
|
||||
var filteredClaims = claims.Where(c => !string.IsNullOrWhiteSpace(c.Value) && c.Value.Contains("@"));
|
||||
|
||||
var email = filteredClaims.GetFirstMatch(JwtClaimTypes.Email, ClaimTypes.Email,
|
||||
SamlClaimTypes.Email, "mail", "emailaddress");
|
||||
var email = filteredClaims.GetFirstMatch(additionalClaimTypes.ToArray()) ??
|
||||
filteredClaims.GetFirstMatch(JwtClaimTypes.Email, ClaimTypes.Email,
|
||||
SamlClaimTypes.Email, "mail", "emailaddress");
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
return email;
|
||||
@ -519,12 +531,13 @@ namespace Bit.Sso.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetName(IEnumerable<Claim> claims)
|
||||
private string GetName(IEnumerable<Claim> claims, IEnumerable<string> additionalClaimTypes)
|
||||
{
|
||||
var filteredClaims = claims.Where(c => !string.IsNullOrWhiteSpace(c.Value));
|
||||
|
||||
var name = filteredClaims.GetFirstMatch(JwtClaimTypes.Name, ClaimTypes.Name,
|
||||
SamlClaimTypes.DisplayName, SamlClaimTypes.CommonName, "displayname", "cn");
|
||||
var name = filteredClaims.GetFirstMatch(additionalClaimTypes.ToArray()) ??
|
||||
filteredClaims.GetFirstMatch(JwtClaimTypes.Name, ClaimTypes.Name,
|
||||
SamlClaimTypes.DisplayName, SamlClaimTypes.CommonName, "displayname", "cn");
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return name;
|
||||
|
@ -10,6 +10,7 @@ using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Sso;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Sso.Models;
|
||||
using Bit.Sso.Utilities;
|
||||
using IdentityModel;
|
||||
@ -324,21 +325,30 @@ namespace Bit.Core.Business.Sso
|
||||
AuthenticationMethod = config.RedirectBehavior,
|
||||
GetClaimsFromUserInfoEndpoint = config.GetClaimsFromUserInfoEndpoint,
|
||||
};
|
||||
if (!oidcOptions.Scope.Contains(OpenIdConnectScopes.OpenId))
|
||||
oidcOptions.Scope
|
||||
.AddIfNotExists(OpenIdConnectScopes.OpenId)
|
||||
.AddIfNotExists(OpenIdConnectScopes.Email)
|
||||
.AddIfNotExists(OpenIdConnectScopes.Profile);
|
||||
foreach (var scope in config.GetAdditionalScopes())
|
||||
{
|
||||
oidcOptions.Scope.Add(OpenIdConnectScopes.OpenId);
|
||||
}
|
||||
if (!oidcOptions.Scope.Contains(OpenIdConnectScopes.Email))
|
||||
{
|
||||
oidcOptions.Scope.Add(OpenIdConnectScopes.Email);
|
||||
}
|
||||
if (!oidcOptions.Scope.Contains(OpenIdConnectScopes.Profile))
|
||||
{
|
||||
oidcOptions.Scope.Add(OpenIdConnectScopes.Profile);
|
||||
oidcOptions.Scope.AddIfNotExists(scope);
|
||||
}
|
||||
|
||||
oidcOptions.StateDataFormat = new DistributedCacheStateDataFormatter(_httpContextAccessor, name);
|
||||
|
||||
// see: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest (acr_values)
|
||||
if (!string.IsNullOrWhiteSpace(config.AcrValues))
|
||||
{
|
||||
oidcOptions.Events = new OpenIdConnectEvents
|
||||
{
|
||||
OnRedirectToIdentityProvider = ctx =>
|
||||
{
|
||||
ctx.ProtocolMessage.AcrValues = config.AcrValues;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new DynamicAuthenticationScheme(name, name, typeof(OpenIdConnectHandler),
|
||||
oidcOptions, SsoType.OpenIdConnect);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Sso;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
@ -20,6 +22,11 @@ namespace Bit.Core.Models.Data
|
||||
public string MetadataAddress { get; set; }
|
||||
public OpenIdConnectRedirectBehavior RedirectBehavior { get; set; } = OpenIdConnectRedirectBehavior.FormPost;
|
||||
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; }
|
||||
|
||||
// SAML2 IDP
|
||||
public string IdpEntityId { get; set; }
|
||||
@ -67,6 +74,30 @@ namespace Bit.Core.Models.Data
|
||||
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 string BuildSsoUrl(string relativePath, string ssoUri)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ssoUri) ||
|
||||
|
@ -604,4 +604,20 @@
|
||||
<data name="PersonalOwnershipCheckboxDesc" xml:space="preserve">
|
||||
<value>Disable personal ownership for organization users</value>
|
||||
</data>
|
||||
<data name="AdditionalScopes" xml:space="preserve">
|
||||
<value>Additional/Custom Scopes (comma delimited)</value>
|
||||
</data>
|
||||
<data name="AdditionalUserIdClaimTypes" xml:space="preserve">
|
||||
<value>Additional/Custom User ID Claim Types (comma delimited)</value>
|
||||
</data>
|
||||
<data name="AdditionalEmailClaimTypes" xml:space="preserve">
|
||||
<value>Additional/Custom Email Claim Types (comma delimited)</value>
|
||||
</data>
|
||||
<data name="AdditionalNameClaimTypes" xml:space="preserve">
|
||||
<value>Additional/Custom Name Claim Types (comma delimited)</value>
|
||||
</data>
|
||||
<data name="AcrValues" xml:space="preserve">
|
||||
<value>Requested Authentication Context Class Reference values (acr_values)</value>
|
||||
<comment>'acr_values' is an explicit OIDC param, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. It should not be translated.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
@ -810,5 +810,15 @@ namespace Bit.Core.Utilities
|
||||
|
||||
return System.Text.Json.JsonSerializer.Deserialize<T>(jsonData, options);
|
||||
}
|
||||
|
||||
public static ICollection<T> AddIfNotExists<T>(this ICollection<T> list, T item)
|
||||
{
|
||||
if (list.Contains(item))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
list.Add(item);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user