sso config for crypto agent and api

This commit is contained in:
Kyle Spearrin
2021-09-28 10:00:04 -04:00
parent 182117e1b4
commit 73685e577b
6 changed files with 155 additions and 45 deletions

View File

@@ -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,

View File

@@ -58,6 +58,24 @@
</div>
</div>
<!-- Crypto Agent -->
<div class="cryptoagent-config">
<div class="config-section">
<h2>@i18nService.T("CryptoAgentConfig")</h2>
<div class="form-group">
<div class="form-check">
<input asp-for="Data.UseCryptoAgent" type="checkbox" class="form-check-input">
<label asp-for="Data.UseCryptoAgent" class="form-check-label"></label>
</div>
</div>
<div class="form-group">
<label asp-for="Data.CryptoAgentUrl"></label>
<input asp-for="Data.CryptoAgentUrl" class="form-control">
<span asp-validation-for="Data.CryptoAgentUrl" class="text-danger"></span>
</div>
</div>
</div>
<!-- OIDC -->
<div class="oidc-config">
<div class="config-section">

View File

@@ -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<PreloginResponseModel> PostPrelogin([FromBody]PreloginRequestModel model)
public async Task<PreloginResponseModel> 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<ProfileResponseModel> PutProfile([FromBody]UpdateProfileRequestModel model)
public async Task<ProfileResponseModel> 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<KeysResponseModel> PostKeys([FromBody]KeysRequestModel model)
public async Task<KeysResponseModel> 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<PaymentResponseModel> PostStorage([FromBody]StorageRequestModel model)
public async Task<PaymentResponseModel> 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<ApiKeyResponseModel> ApiKey([FromBody]ApiKeyRequestModel model)
public async Task<ApiKeyResponseModel> 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<ApiKeyResponseModel> RotateApiKey([FromBody]ApiKeyRequestModel model)
public async Task<ApiKeyResponseModel> 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<SsoConfigResponseModel> 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<SsoConfigurationData>(ssoConfig.Data,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
return new SsoConfigResponseModel(ssoConfigData);
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }

View File

@@ -442,22 +442,22 @@
<data name="RequestId" xml:space="preserve">
<value>Request ID</value>
</data>
<data name="Redirecting">
<data name="Redirecting" xml:space="preserve">
<value>Redirecting</value>
</data>
<data name="RedirectingMessage">
<data name="RedirectingMessage" xml:space="preserve">
<value>You are now being returned to the application. Once complete, you may close this tab.</value>
</data>
<data name="IfIdpWantAuthnRequestsSigned">
<data name="IfIdpWantAuthnRequestsSigned" xml:space="preserve">
<value>If IdP Wants Authn Requests Signed</value>
</data>
<data name="Always">
<data name="Always" xml:space="preserve">
<value>Always</value>
</data>
<data name="Never">
<data name="Never" xml:space="preserve">
<value>Never</value>
</data>
<data name="WelcomeToBusinessPortal">
<data name="WelcomeToBusinessPortal" xml:space="preserve">
<value>Welcome to the Bitwarden Business Portal</value>
</data>
<data name="IdpX509PublicCertValidationError" xml:space="preserve">
@@ -469,33 +469,33 @@
<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">
<data name="CopyCallbackPath" xml:space="preserve">
<value>Copy the OIDC callback path to your clipboard</value>
</data>
<data name="CopySignedOutCallbackPath">
<data name="CopySignedOutCallbackPath" xml:space="preserve">
<value>Copy the OIDC signed out callback path to your clipboard</value>
</data>
<data name="CopySpEntityId">
<data name="CopySpEntityId" xml:space="preserve">
<value>Copy the SP Entity Id to your clipboard</value>
</data>
<data name="CopySpMetadataUrl">
<data name="CopySpMetadataUrl" xml:space="preserve">
<value>Copy the SAML 2.0 Metadata URL to your clipboard</value>
</data>
<data name="LaunchSpMetadataUrl">
<data name="LaunchSpMetadataUrl" xml:space="preserve">
<value>View the SAML 2.0 Metadata (opens in a new window)</value>
</data>
<data name="CopySpAcsUrl">
<data name="CopySpAcsUrl" xml:space="preserve">
<value>Copy the Assertion Consumer Service (ACS) URL to your clipboard</value>
</data>
<data name="HttpRedirect">
<data name="HttpRedirect" xml:space="preserve">
<value>Redirect</value>
<comment>A SAML binding type, Redirect</comment>
</data>
<data name="HttpPost">
<data name="HttpPost" xml:space="preserve">
<value>HTTP POST</value>
<comment>A SAML binding type, HTTP POST</comment>
</data>
<data name="Artifact">
<data name="Artifact" xml:space="preserve">
<value>Artifact</value>
<comment>A SAML binding type, Artifact</comment>
</data>
@@ -673,4 +673,19 @@
<data name="ResetPasswordAutoEnrollCheckbox" xml:space="preserve">
<value>Require new users to be enrolled automatically</value>
</data>
</root>
<data name="CryptoAgentConfig" xml:space="preserve">
<value>Crypto Agent Configuration</value>
</data>
<data name="CryptoAgentUrl" xml:space="preserve">
<value>Crypto Agent URL</value>
</data>
<data name="CryptoAgentUrlValidationError" xml:space="preserve">
<value>URL is required when using the Crypto Agent.</value>
</data>
<data name="CryptoAgentUrlValidationFormatError" xml:space="preserve">
<value>Crypto Agent URL is not a valid URL.</value>
</data>
<data name="UseCryptoAgent" xml:space="preserve">
<value>Use Crypto Agent</value>
</data>
</root>