mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
sso config for crypto agent and api
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
src/Core/Models/Api/Response/SsoConfigResponseModel.cs
Normal file
21
src/Core/Models/Api/Response/SsoConfigResponseModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user