diff --git a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs
index 967c77d982..4d6f2a93e4 100644
--- a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs
+++ b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs
@@ -22,6 +22,8 @@ namespace Bit.Portal.Models
Guid organizationId)
{
ConfigType = configurationData.ConfigType;
+ UseCryptoAgent = configurationData.UseCryptoAgent;
+ CryptoAgentUrl = configurationData.CryptoAgentUrl;
Authority = configurationData.Authority;
ClientId = configurationData.ClientId;
ClientSecret = configurationData.ClientSecret;
@@ -60,6 +62,10 @@ namespace Bit.Portal.Models
[Required]
[Display(Name = "ConfigType")]
public SsoType ConfigType { get; set; }
+ [Display(Name = "UseCryptoAgent")]
+ public bool UseCryptoAgent { get; set; }
+ [Display(Name = "CryptoAgentUrl")]
+ public string CryptoAgentUrl { get; set; }
// OIDC
[Display(Name = "Authority")]
@@ -138,6 +144,21 @@ namespace Bit.Portal.Models
var i18nService = context.GetService(typeof(II18nService)) as I18nService;
var model = context.ObjectInstance as SsoConfigDataViewModel;
+ if (model.UseCryptoAgent && string.IsNullOrWhiteSpace(model.CryptoAgentUrl))
+ {
+ yield return new ValidationResult(
+ i18nService.GetLocalizedHtmlString("CryptoAgentUrlValidationError"),
+ new[] { nameof(model.CryptoAgentUrl) });
+ }
+
+ if (!string.IsNullOrWhiteSpace(model.CryptoAgentUrl) &&
+ !Uri.TryCreate(model.CryptoAgentUrl, UriKind.Absolute, out _))
+ {
+ yield return new ValidationResult(
+ i18nService.GetLocalizedHtmlString("CryptoAgentUrlValidationFormatError"),
+ new[] { nameof(model.CryptoAgentUrl) });
+ }
+
if (model.ConfigType == SsoType.OpenIdConnect)
{
if (string.IsNullOrWhiteSpace(model.Authority))
@@ -214,6 +235,8 @@ namespace Bit.Portal.Models
return new SsoConfigurationData
{
ConfigType = ConfigType,
+ UseCryptoAgent = UseCryptoAgent,
+ CryptoAgentUrl = CryptoAgentUrl,
Authority = Authority,
ClientId = ClientId,
ClientSecret = ClientSecret,
diff --git a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml
index 66767123f3..62d63e98d3 100644
--- a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml
+++ b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml
@@ -58,6 +58,24 @@
+
+
+
+
@i18nService.T("CryptoAgentConfig")
+
+
+
+
+
+
+
+
+
diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs
index 52ff4b082f..e54e143bdb 100644
--- a/src/Api/Controllers/AccountsController.cs
+++ b/src/Api/Controllers/AccountsController.cs
@@ -16,9 +16,9 @@ using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using System.Threading.Tasks;
using Bit.Core.Enums.Provider;
+using System.Text.Json;
namespace Bit.Api.Controllers
{
@@ -37,6 +37,7 @@ namespace Bit.Api.Controllers
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
+ private readonly ISsoConfigRepository _ssoConfigRepository;
public AccountsController(
GlobalSettings globalSettings,
@@ -46,11 +47,11 @@ namespace Bit.Api.Controllers
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IPaymentService paymentService,
- ISsoUserRepository ssoUserRepository,
IUserRepository userRepository,
IUserService userService,
ISendRepository sendRepository,
- ISendService sendService)
+ ISendService sendService,
+ ISsoConfigRepository ssoConfigRepository)
{
_cipherRepository = cipherRepository;
_folderRepository = folderRepository;
@@ -63,11 +64,12 @@ namespace Bit.Api.Controllers
_userService = userService;
_sendRepository = sendRepository;
_sendService = sendService;
+ _ssoConfigRepository = ssoConfigRepository;
}
[HttpPost("prelogin")]
[AllowAnonymous]
- public async Task
PostPrelogin([FromBody]PreloginRequestModel model)
+ public async Task PostPrelogin([FromBody] PreloginRequestModel model)
{
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
if (kdfInformation == null)
@@ -84,7 +86,7 @@ namespace Bit.Api.Controllers
[HttpPost("register")]
[AllowAnonymous]
[CaptchaProtected]
- public async Task PostRegister([FromBody]RegisterRequestModel model)
+ public async Task PostRegister([FromBody] RegisterRequestModel model)
{
var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash,
model.Token, model.OrganizationUserId);
@@ -104,13 +106,13 @@ namespace Bit.Api.Controllers
[HttpPost("password-hint")]
[AllowAnonymous]
- public async Task PostPasswordHint([FromBody]PasswordHintRequestModel model)
+ public async Task PostPasswordHint([FromBody] PasswordHintRequestModel model)
{
await _userService.SendMasterPasswordHintAsync(model.Email);
}
[HttpPost("email-token")]
- public async Task PostEmailToken([FromBody]EmailTokenRequestModel model)
+ public async Task PostEmailToken([FromBody] EmailTokenRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -128,7 +130,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("email")]
- public async Task PostEmail([FromBody]EmailRequestModel model)
+ public async Task PostEmail([FromBody] EmailRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -166,7 +168,7 @@ namespace Bit.Api.Controllers
[HttpPost("verify-email-token")]
[AllowAnonymous]
- public async Task PostVerifyEmailToken([FromBody]VerifyEmailRequestModel model)
+ public async Task PostVerifyEmailToken([FromBody] VerifyEmailRequestModel model)
{
var user = await _userService.GetUserByIdAsync(new Guid(model.UserId));
if (user == null)
@@ -189,7 +191,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("password")]
- public async Task PostPassword([FromBody]PasswordRequestModel model)
+ public async Task PostPassword([FromBody] PasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -214,7 +216,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("set-password")]
- public async Task PostSetPasswordAsync([FromBody]SetPasswordRequestModel model)
+ public async Task PostSetPasswordAsync([FromBody] SetPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -222,7 +224,7 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException();
}
- var result = await _userService.SetPasswordAsync(model.ToUser(user), model.MasterPasswordHash, model.Key,
+ var result = await _userService.SetPasswordAsync(model.ToUser(user), model.MasterPasswordHash, model.Key,
model.OrgIdentifier);
if (result.Succeeded)
{
@@ -238,7 +240,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("verify-password")]
- public async Task PostVerifyPassword([FromBody]VerifyPasswordRequestModel model)
+ public async Task PostVerifyPassword([FromBody] VerifyPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -257,7 +259,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("kdf")]
- public async Task PostKdf([FromBody]KdfRequestModel model)
+ public async Task PostKdf([FromBody] KdfRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -282,7 +284,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("key")]
- public async Task PostKey([FromBody]UpdateKeyRequestModel model)
+ public async Task PostKey([FromBody] UpdateKeyRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -338,7 +340,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("security-stamp")]
- public async Task PostSecurityStamp([FromBody]SecurityStampRequestModel model)
+ public async Task PostSecurityStamp([FromBody] SecurityStampRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -394,7 +396,7 @@ namespace Bit.Api.Controllers
[HttpPut("profile")]
[HttpPost("profile")]
- public async Task PutProfile([FromBody]UpdateProfileRequestModel model)
+ public async Task PutProfile([FromBody] UpdateProfileRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -422,7 +424,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("keys")]
- public async Task PostKeys([FromBody]KeysRequestModel model)
+ public async Task PostKeys([FromBody] KeysRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -448,7 +450,7 @@ namespace Bit.Api.Controllers
[HttpDelete]
[HttpPost("delete")]
- public async Task Delete([FromBody]DeleteAccountRequestModel model)
+ public async Task Delete([FromBody] DeleteAccountRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -480,14 +482,14 @@ namespace Bit.Api.Controllers
[AllowAnonymous]
[HttpPost("delete-recover")]
- public async Task PostDeleteRecover([FromBody]DeleteRecoverRequestModel model)
+ public async Task PostDeleteRecover([FromBody] DeleteRecoverRequestModel model)
{
await _userService.SendDeleteConfirmationAsync(model.Email);
}
[HttpPost("delete-recover-token")]
[AllowAnonymous]
- public async Task PostDeleteRecoverToken([FromBody]VerifyDeleteRecoverRequestModel model)
+ public async Task PostDeleteRecoverToken([FromBody] VerifyDeleteRecoverRequestModel model)
{
var user = await _userService.GetUserByIdAsync(new Guid(model.UserId));
if (user == null)
@@ -511,7 +513,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("iap-check")]
- public async Task PostIapCheck([FromBody]IapCheckRequestModel model)
+ public async Task PostIapCheck([FromBody] IapCheckRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -605,7 +607,7 @@ namespace Bit.Api.Controllers
[HttpPost("payment")]
[SelfHosted(NotSelfHostedOnly = true)]
- public async Task PostPayment([FromBody]PaymentRequestModel model)
+ public async Task PostPayment([FromBody] PaymentRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -623,7 +625,7 @@ namespace Bit.Api.Controllers
[HttpPost("storage")]
[SelfHosted(NotSelfHostedOnly = true)]
- public async Task PostStorage([FromBody]StorageRequestModel model)
+ public async Task PostStorage([FromBody] StorageRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -719,7 +721,7 @@ namespace Bit.Api.Controllers
[HttpPut("tax")]
[SelfHosted(NotSelfHostedOnly = true)]
- public async Task PutTaxInfo([FromBody]TaxInfoUpdateRequestModel model)
+ public async Task PutTaxInfo([FromBody] TaxInfoUpdateRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -757,7 +759,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("api-key")]
- public async Task ApiKey([FromBody]ApiKeyRequestModel model)
+ public async Task ApiKey([FromBody] ApiKeyRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -778,7 +780,7 @@ namespace Bit.Api.Controllers
}
[HttpPost("rotate-api-key")]
- public async Task RotateApiKey([FromBody]ApiKeyRequestModel model)
+ public async Task RotateApiKey([FromBody] ApiKeyRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -798,9 +800,9 @@ namespace Bit.Api.Controllers
return response;
}
}
-
+
[HttpPut("update-temp-password")]
- public async Task PutUpdateTempPasswordAsync([FromBody]UpdateTempPasswordRequestModel model)
+ public async Task PutUpdateTempPasswordAsync([FromBody] UpdateTempPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@@ -821,5 +823,34 @@ namespace Bit.Api.Controllers
throw new BadRequestException(ModelState);
}
+
+ [HttpGet("sso-config")]
+ public async Task GetSsoConfig([FromQuery] string identifier)
+ {
+ if (string.IsNullOrWhiteSpace(identifier))
+ {
+ throw new BadRequestException("Identifier", "Identifier required.");
+ }
+
+ var ssoConfig = await _ssoConfigRepository.GetByIdentifierAsync(identifier);
+ var validConfig = false;
+ if (ssoConfig != null)
+ {
+ // TODO: Make sure this user has rights to access this org's sso config data
+ validConfig = true;
+ }
+
+ if (!validConfig)
+ {
+ throw new BadRequestException("Identifier", "Invalid identifier.");
+ }
+
+ var ssoConfigData = JsonSerializer.Deserialize(ssoConfig.Data,
+ new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ });
+ return new SsoConfigResponseModel(ssoConfigData);
+ }
}
}
diff --git a/src/Core/Models/Api/Response/SsoConfigResponseModel.cs b/src/Core/Models/Api/Response/SsoConfigResponseModel.cs
new file mode 100644
index 0000000000..f27ee8b44e
--- /dev/null
+++ b/src/Core/Models/Api/Response/SsoConfigResponseModel.cs
@@ -0,0 +1,21 @@
+using Bit.Core.Models.Data;
+
+namespace Bit.Core.Models.Api
+{
+ public class SsoConfigResponseModel
+ {
+ public SsoConfigResponseModel() { }
+
+ public SsoConfigResponseModel(SsoConfigurationData ssoConfigData)
+ {
+ if (ssoConfigData == null)
+ {
+ return;
+ }
+
+ CryptoAgentUrl = ssoConfigData.UseCryptoAgent ? ssoConfigData.CryptoAgentUrl : null;
+ }
+
+ public string CryptoAgentUrl { get; set; }
+ }
+}
diff --git a/src/Core/Models/Data/SsoConfigurationData.cs b/src/Core/Models/Data/SsoConfigurationData.cs
index 19192c3897..789543fa69 100644
--- a/src/Core/Models/Data/SsoConfigurationData.cs
+++ b/src/Core/Models/Data/SsoConfigurationData.cs
@@ -14,6 +14,8 @@ namespace Bit.Core.Models.Data
private const string _saml2ModulePath = "/saml2";
public SsoType ConfigType { get; set; }
+ public bool UseCryptoAgent { get; set; }
+ public string CryptoAgentUrl { get; set; }
// OIDC
public string Authority { get; set; }
diff --git a/src/Core/Resources/SharedResources.en.resx b/src/Core/Resources/SharedResources.en.resx
index c2e6c40520..7499d6955a 100644
--- a/src/Core/Resources/SharedResources.en.resx
+++ b/src/Core/Resources/SharedResources.en.resx
@@ -442,22 +442,22 @@
Request ID
-
+
Redirecting
-
+
You are now being returned to the application. Once complete, you may close this tab.
-
+
If IdP Wants Authn Requests Signed
-
+
Always
-
+
Never
-
+
Welcome to the Bitwarden Business Portal
@@ -469,33 +469,33 @@
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}
-
+
Copy the OIDC callback path to your clipboard
-
+
Copy the OIDC signed out callback path to your clipboard
-
+
Copy the SP Entity Id to your clipboard
-
+
Copy the SAML 2.0 Metadata URL to your clipboard
-
+
View the SAML 2.0 Metadata (opens in a new window)
-
+
Copy the Assertion Consumer Service (ACS) URL to your clipboard
-
+
Redirect
A SAML binding type, Redirect
-
+
HTTP POST
A SAML binding type, HTTP POST
-
+
Artifact
A SAML binding type, Artifact
@@ -673,4 +673,19 @@
Require new users to be enrolled automatically
-
+
+ Crypto Agent Configuration
+
+
+ Crypto Agent URL
+
+
+ URL is required when using the Crypto Agent.
+
+
+ Crypto Agent URL is not a valid URL.
+
+
+ Use Crypto Agent
+
+
\ No newline at end of file