mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
Add support for crypto agent (#1623)
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Models.Table;
|
||||
using System;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using IdentityServer4.Validation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -9,7 +10,9 @@ using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Context;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.Models.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityModel;
|
||||
@ -20,6 +23,7 @@ namespace Bit.Core.IdentityServer
|
||||
ICustomTokenRequestValidator
|
||||
{
|
||||
private UserManager<User> _userManager;
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
|
||||
public CustomTokenRequestValidator(
|
||||
UserManager<User> userManager,
|
||||
@ -35,12 +39,14 @@ namespace Bit.Core.IdentityServer
|
||||
ILogger<ResourceOwnerPasswordValidator> logger,
|
||||
ICurrentContext currentContext,
|
||||
GlobalSettings globalSettings,
|
||||
IPolicyRepository policyRepository)
|
||||
IPolicyRepository policyRepository,
|
||||
ISsoConfigRepository ssoConfigRepository)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
}
|
||||
|
||||
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
|
||||
@ -52,6 +58,25 @@ namespace Bit.Core.IdentityServer
|
||||
return;
|
||||
}
|
||||
await ValidateAsync(context, context.Result.ValidatedRequest);
|
||||
|
||||
if (context.Result.CustomResponse != null)
|
||||
{
|
||||
var organizationClaim = context.Result.ValidatedRequest.Subject?.FindFirst(c => c.Type == "organizationId");
|
||||
var organizationId = organizationClaim?.Value ?? "";
|
||||
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(new Guid(organizationId));
|
||||
var ssoConfigData = ssoConfig.GetData();
|
||||
|
||||
if (ssoConfigData is { UseCryptoAgent: true } && !string.IsNullOrEmpty(ssoConfigData.CryptoAgentUrl))
|
||||
{
|
||||
context.Result.CustomResponse["CryptoAgentUrl"] = ssoConfigData.CryptoAgentUrl;
|
||||
// Prevent clients redirecting to set-password
|
||||
// TODO: Figure out if we can move this logic to the clients since this might break older clients
|
||||
// although we will have issues either way with some clients supporting crypto anent and some not
|
||||
// suggestion: We should roll out the clients before enabling it server wise
|
||||
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async override Task<(User, bool)> ValidateContextAsync(CustomTokenRequestValidationContext context)
|
||||
|
@ -0,0 +1,29 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Models.Api.Request.Accounts
|
||||
{
|
||||
public class SetCryptoAgentKeyRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
[Required]
|
||||
public KeysRequestModel Keys { get; set; }
|
||||
[Required]
|
||||
public KdfType Kdf { get; set; }
|
||||
[Required]
|
||||
public int KdfIterations { get; set; }
|
||||
[Required]
|
||||
public string OrgIdentifier { get; set; }
|
||||
|
||||
public User ToUser(User existingUser)
|
||||
{
|
||||
existingUser.Kdf = Kdf;
|
||||
existingUser.KdfIterations = KdfIterations;
|
||||
existingUser.Key = Key;
|
||||
Keys.ToUser(existingUser);
|
||||
return existingUser;
|
||||
}
|
||||
}
|
||||
}
|
@ -31,10 +31,7 @@ namespace Bit.Core.Models.Api
|
||||
{
|
||||
existingConfig.Enabled = Enabled;
|
||||
var configurationData = Data.ToConfigurationData();
|
||||
existingConfig.Data = JsonSerializer.Serialize(configurationData, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
existingConfig.SetData(configurationData);
|
||||
return existingConfig;
|
||||
}
|
||||
}
|
||||
@ -46,6 +43,8 @@ namespace Bit.Core.Models.Api
|
||||
public SsoConfigurationDataRequest(SsoConfigurationData configurationData)
|
||||
{
|
||||
ConfigType = configurationData.ConfigType;
|
||||
UseCryptoAgent = configurationData.UseCryptoAgent;
|
||||
CryptoAgentUrl = configurationData.CryptoAgentUrl;
|
||||
Authority = configurationData.Authority;
|
||||
ClientId = configurationData.ClientId;
|
||||
ClientSecret = configurationData.ClientSecret;
|
||||
@ -79,6 +78,10 @@ namespace Bit.Core.Models.Api
|
||||
[Required]
|
||||
public SsoType ConfigType { get; set; }
|
||||
|
||||
// Crypto Agent
|
||||
public bool UseCryptoAgent { get; set; }
|
||||
public string CryptoAgentUrl { get; set; }
|
||||
|
||||
// OIDC
|
||||
public string Authority { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
@ -193,6 +196,8 @@ namespace Bit.Core.Models.Api
|
||||
return new SsoConfigurationData
|
||||
{
|
||||
ConfigType = ConfigType,
|
||||
UseCryptoAgent = UseCryptoAgent,
|
||||
CryptoAgentUrl = CryptoAgentUrl,
|
||||
Authority = Authority,
|
||||
ClientId = ClientId,
|
||||
ClientSecret = ClientSecret,
|
||||
|
@ -13,10 +13,7 @@ namespace Bit.Core.Models.Api
|
||||
if (config != null)
|
||||
{
|
||||
Enabled = config.Enabled;
|
||||
Data = JsonSerializer.Deserialize<SsoConfigurationData>(config.Data, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
Data = config.GetData();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -15,6 +15,10 @@ namespace Bit.Core.Models.Data
|
||||
|
||||
public SsoType ConfigType { get; set; }
|
||||
|
||||
// Crypto Agent
|
||||
public bool UseCryptoAgent { get; set; }
|
||||
public string CryptoAgentUrl { get; set; }
|
||||
|
||||
// OIDC
|
||||
public string Authority { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Models.Table
|
||||
{
|
||||
@ -10,11 +12,26 @@ namespace Bit.Core.Models.Table
|
||||
public string Data { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
|
||||
|
||||
private JsonSerializerOptions _jsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
// int will be auto-populated
|
||||
Id = 0;
|
||||
}
|
||||
|
||||
public SsoConfigurationData GetData()
|
||||
{
|
||||
return JsonSerializer.Deserialize<SsoConfigurationData>(Data, _jsonSerializerOptions);
|
||||
}
|
||||
|
||||
public void SetData(SsoConfigurationData data)
|
||||
{
|
||||
Data = JsonSerializer.Serialize(data, _jsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ namespace Bit.Core.Models.Table
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public bool UsesCryptoAgent { get; set; }
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ namespace Bit.Core.Services
|
||||
string token, string key);
|
||||
Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key);
|
||||
Task<IdentityResult> SetPasswordAsync(User user, string newMasterPassword, string key, string orgIdentifier = null);
|
||||
Task<IdentityResult> SetCryptoAgentKeyAsync(User user, string key, string orgIdentifier);
|
||||
Task<IdentityResult> AdminResetPasswordAsync(OrganizationUserType type, Guid orgId, Guid id, string newMasterPassword, string key);
|
||||
Task<IdentityResult> UpdateTempPasswordAsync(User user, string newMasterPassword, string key, string hint);
|
||||
Task<IdentityResult> ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key,
|
||||
|
@ -635,6 +635,33 @@ namespace Bit.Core.Services
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> SetCryptoAgentKeyAsync(User user, string key, string orgIdentifier)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (user.UsesCryptoAgent)
|
||||
{
|
||||
Logger.LogWarning("Already uses crypto agent.");
|
||||
return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword());
|
||||
}
|
||||
|
||||
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
|
||||
user.Key = key;
|
||||
user.UsesCryptoAgent = true;
|
||||
|
||||
await _userRepository.ReplaceAsync(user);
|
||||
// TODO: Use correct event
|
||||
await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword);
|
||||
|
||||
await _organizationService.AcceptUserAsync(orgIdentifier, user, this);
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IdentityResult> AdminResetPasswordAsync(OrganizationUserType callingUserType, Guid orgId, Guid id, string newMasterPassword, string key)
|
||||
{
|
||||
|
Reference in New Issue
Block a user