diff --git a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs index f53afd20f8..967c77d982 100644 --- a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs +++ b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs @@ -54,6 +54,7 @@ namespace Bit.Portal.Models AdditionalEmailClaimTypes = configurationData.AdditionalEmailClaimTypes; AdditionalNameClaimTypes = configurationData.AdditionalNameClaimTypes; AcrValues = configurationData.AcrValues; + ExpectedReturnAcrValue = configurationData.ExpectedReturnAcrValue; } [Required] @@ -87,6 +88,8 @@ namespace Bit.Portal.Models public string AdditionalNameClaimTypes { get; set; } [Display(Name = "AcrValues")] public string AcrValues { get; set; } + [Display(Name = "ExpectedReturnAcrValue")] + public string ExpectedReturnAcrValue { get; set; } // SAML2 SP [Display(Name = "SpEntityId")] @@ -238,6 +241,7 @@ namespace Bit.Portal.Models AdditionalEmailClaimTypes = AdditionalEmailClaimTypes, AdditionalNameClaimTypes = AdditionalNameClaimTypes, AcrValues = AcrValues, + ExpectedReturnAcrValue = ExpectedReturnAcrValue, }; } diff --git a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml index 8136067827..66767123f3 100644 --- a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml +++ b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml @@ -164,6 +164,12 @@ +
+
+ + +
+
diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index a2cfeefeef..c9db0290e0 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -326,6 +326,16 @@ namespace Bit.Sso.Controllers var externalUser = result.Principal; + // Validate acr claim against expectation before going further + if (!string.IsNullOrWhiteSpace(ssoConfigData.ExpectedReturnAcrValue)) + { + var acrClaim = externalUser.FindFirst(JwtClaimTypes.AuthenticationContextClassReference); + if (acrClaim?.Value != ssoConfigData.ExpectedReturnAcrValue) + { + throw new Exception(_i18nService.T("AcrMissingOrInvalid")); + } + } + // Ensure the NameIdentifier used is not a transient name ID, if so, we need a different attribute // for the user identifier. static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs index 623e2dee37..7b457d029c 100644 --- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs +++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs @@ -333,19 +333,21 @@ namespace Bit.Core.Business.Sso { oidcOptions.Scope.AddIfNotExists(scope); } + if (!string.IsNullOrWhiteSpace(config.ExpectedReturnAcrValue)) + { + oidcOptions.Scope.AddIfNotExists(OpenIdConnectScopes.Acr); + } 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 + oidcOptions.Events ??= new OpenIdConnectEvents(); + oidcOptions.Events.OnRedirectToIdentityProvider = ctx => { - OnRedirectToIdentityProvider = ctx => - { - ctx.ProtocolMessage.AcrValues = config.AcrValues; - return Task.CompletedTask; - } + ctx.ProtocolMessage.AcrValues = config.AcrValues; + return Task.CompletedTask; }; } diff --git a/bitwarden_license/src/Sso/Utilities/OpenIdConnectScopes.cs b/bitwarden_license/src/Sso/Utilities/OpenIdConnectScopes.cs index 54b6e0a119..983ce8b33f 100644 --- a/bitwarden_license/src/Sso/Utilities/OpenIdConnectScopes.cs +++ b/bitwarden_license/src/Sso/Utilities/OpenIdConnectScopes.cs @@ -49,5 +49,16 @@ /// not present (not logged in). /// public const string OfflineAccess = "offline_access"; + + /// + /// OPTIONAL. Authentication Context Class Reference. String specifying + /// an Authentication Context Class Reference value that identifies the + /// Authentication Context Class that the authentication performed + /// satisfied. + /// + /// + /// See: https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.2 + /// + public const string Acr = "acr"; } } diff --git a/src/Core/Models/Data/SsoConfigurationData.cs b/src/Core/Models/Data/SsoConfigurationData.cs index d7c357a49d..19192c3897 100644 --- a/src/Core/Models/Data/SsoConfigurationData.cs +++ b/src/Core/Models/Data/SsoConfigurationData.cs @@ -27,6 +27,7 @@ namespace Bit.Core.Models.Data 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; } diff --git a/src/Core/Resources/SharedResources.en.resx b/src/Core/Resources/SharedResources.en.resx index 1a490bc1ef..df876e3aad 100644 --- a/src/Core/Resources/SharedResources.en.resx +++ b/src/Core/Resources/SharedResources.en.resx @@ -638,10 +638,18 @@ Requested Authentication Context Class Reference values (acr_values) 'acr_values' is an explicit OIDC param, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. It should not be translated. + + Expected "acr" Claim Value In Response (acr validation) + 'acr' is an explicit OIDC claim type, see https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.2 (acr). It should not be translated. + You have been logged out of the Bitwarden Business Portal. Access Denied to this resource. + + Expected authentication context class reference (acr) was not returned with the authentication response or is invalid. + 'acr' is an explicit OIDC claim type, see https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.2 (acr). It should not be translated. +