mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
Merge branch 'master' into flexible-collections/deprecate-custom-collection-perm
This commit is contained in:
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -277,7 +277,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve github PAT secrets
|
- name: Retrieve github PAT secrets
|
||||||
id: retrieve-secret-pat
|
id: retrieve-secret-pat
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/get-keyvault-secrets@master
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
@ -528,7 +528,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve github PAT secrets
|
- name: Retrieve github PAT secrets
|
||||||
id: retrieve-secret-pat
|
id: retrieve-secret-pat
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/get-keyvault-secrets@master
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
@ -603,7 +603,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/get-keyvault-secrets@master
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
|
@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/get-keyvault-secrets@master
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -41,7 +41,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check Release Version
|
- name: Check Release Version
|
||||||
id: version
|
id: version
|
||||||
uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/release-version-check@master
|
||||||
with:
|
with:
|
||||||
release-type: ${{ github.event.inputs.release_type }}
|
release-type: ${{ github.event.inputs.release_type }}
|
||||||
project-type: dotnet
|
project-type: dotnet
|
||||||
@ -89,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download latest Release ${{ matrix.name }} asset
|
- name: Download latest Release ${{ matrix.name }} asset
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/download-artifacts@master
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -98,7 +98,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Dry Run - Download latest Release ${{ matrix.name }} asset
|
- name: Dry Run - Download latest Release ${{ matrix.name }} asset
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/download-artifacts@master
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -274,7 +274,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Download latest Release Docker Stubs
|
- name: Download latest Release Docker Stubs
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/download-artifacts@master
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -287,7 +287,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Dry Run - Download latest Release Docker Stubs
|
- name: Dry Run - Download latest Release Docker Stubs
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/download-artifacts@master
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
4
.github/workflows/version-bump.yml
vendored
4
.github/workflows/version-bump.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/get-keyvault-secrets@master
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||||
@ -40,7 +40,7 @@ jobs:
|
|||||||
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
|
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
- name: Bump Version - Props
|
- name: Bump Version - Props
|
||||||
uses: bitwarden/gh-actions/version-bump@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/version-bump@master
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "Directory.Build.props"
|
file_path: "Directory.Build.props"
|
||||||
|
2
.github/workflows/workflow-linter.yml
vendored
2
.github/workflows/workflow-linter.yml
vendored
@ -8,4 +8,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
call-workflow:
|
call-workflow:
|
||||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@c970b0fb89bd966749280e832928db62040812bf
|
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||||
|
@ -75,7 +75,7 @@ public class WebAuthnController : Controller
|
|||||||
throw new BadRequestException("The token associated with your request is expired. A valid token is required to continue.");
|
throw new BadRequestException("The token associated with your request is expired. A valid token is required to continue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var success = await _userService.CompleteWebAuthLoginRegistrationAsync(user, model.Name, tokenable.Options, model.DeviceResponse);
|
var success = await _userService.CompleteWebAuthLoginRegistrationAsync(user, model.Name, tokenable.Options, model.DeviceResponse, model.SupportsPrf, model.EncryptedUserKey, model.EncryptedPublicKey, model.EncryptedPrivateKey);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Unable to complete WebAuthn registration.");
|
throw new BadRequestException("Unable to complete WebAuthn registration.");
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
#nullable enable
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -17,7 +15,7 @@ public class SetPasswordRequestModel : IValidatableObject
|
|||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
[StringLength(50)]
|
[StringLength(50)]
|
||||||
public string MasterPasswordHint { get; set; }
|
public string MasterPasswordHint { get; set; }
|
||||||
public KeysRequestModel? Keys { get; set; }
|
public KeysRequestModel Keys { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public KdfType Kdf { get; set; }
|
public KdfType Kdf { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Fido2NetLib;
|
using Fido2NetLib;
|
||||||
|
|
||||||
namespace Bit.Api.Auth.Models.Request.Webauthn;
|
namespace Bit.Api.Auth.Models.Request.Webauthn;
|
||||||
@ -13,5 +14,20 @@ public class WebAuthnCredentialRequestModel
|
|||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool SupportsPrf { get; set; }
|
||||||
|
|
||||||
|
[EncryptedString]
|
||||||
|
[EncryptedStringLength(2000)]
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
|
||||||
|
[EncryptedString]
|
||||||
|
[EncryptedStringLength(2000)]
|
||||||
|
public string EncryptedPublicKey { get; set; }
|
||||||
|
|
||||||
|
[EncryptedString]
|
||||||
|
[EncryptedStringLength(2000)]
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
|
|
||||||
namespace Bit.Api.Auth.Models.Response.WebAuthn;
|
namespace Bit.Api.Auth.Models.Response.WebAuthn;
|
||||||
@ -11,10 +12,10 @@ public class WebAuthnCredentialResponseModel : ResponseModel
|
|||||||
{
|
{
|
||||||
Id = credential.Id.ToString();
|
Id = credential.Id.ToString();
|
||||||
Name = credential.Name;
|
Name = credential.Name;
|
||||||
PrfSupport = false;
|
PrfStatus = credential.GetPrfStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool PrfSupport { get; set; }
|
public WebAuthnPrfStatus PrfStatus { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
@ -18,8 +19,11 @@ public class WebAuthnCredential : ITableObject<Guid>
|
|||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public Guid AaGuid { get; set; }
|
public Guid AaGuid { get; set; }
|
||||||
|
[MaxLength(2000)]
|
||||||
public string EncryptedUserKey { get; set; }
|
public string EncryptedUserKey { get; set; }
|
||||||
|
[MaxLength(2000)]
|
||||||
public string EncryptedPrivateKey { get; set; }
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
[MaxLength(2000)]
|
||||||
public string EncryptedPublicKey { get; set; }
|
public string EncryptedPublicKey { get; set; }
|
||||||
public bool SupportsPrf { get; set; }
|
public bool SupportsPrf { get; set; }
|
||||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||||
@ -29,4 +33,19 @@ public class WebAuthnCredential : ITableObject<Guid>
|
|||||||
{
|
{
|
||||||
Id = CoreHelpers.GenerateComb();
|
Id = CoreHelpers.GenerateComb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WebAuthnPrfStatus GetPrfStatus()
|
||||||
|
{
|
||||||
|
if (!SupportsPrf)
|
||||||
|
{
|
||||||
|
return WebAuthnPrfStatus.Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EncryptedUserKey != null && EncryptedPrivateKey != null && EncryptedPublicKey != null)
|
||||||
|
{
|
||||||
|
return WebAuthnPrfStatus.Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebAuthnPrfStatus.Supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
src/Core/Auth/Enums/WebAuthnPrfStatus.cs
Normal file
8
src/Core/Auth/Enums/WebAuthnPrfStatus.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Bit.Core.Auth.Enums;
|
||||||
|
|
||||||
|
public enum WebAuthnPrfStatus
|
||||||
|
{
|
||||||
|
Enabled = 0,
|
||||||
|
Supported = 1,
|
||||||
|
Unsupported = 2
|
||||||
|
}
|
@ -28,7 +28,7 @@ public interface IUserService
|
|||||||
Task<bool> DeleteWebAuthnKeyAsync(User user, int id);
|
Task<bool> DeleteWebAuthnKeyAsync(User user, int id);
|
||||||
Task<bool> CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse);
|
Task<bool> CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse);
|
||||||
Task<CredentialCreateOptions> StartWebAuthnLoginRegistrationAsync(User user);
|
Task<CredentialCreateOptions> StartWebAuthnLoginRegistrationAsync(User user);
|
||||||
Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse);
|
Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf, string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null);
|
||||||
Task<AssertionOptions> StartWebAuthnLoginAssertionAsync(User user);
|
Task<AssertionOptions> StartWebAuthnLoginAssertionAsync(User user);
|
||||||
Task<string> CompleteWebAuthLoginAssertionAsync(AuthenticatorAssertionRawResponse assertionResponse, User user);
|
Task<string> CompleteWebAuthLoginAssertionAsync(AuthenticatorAssertionRawResponse assertionResponse, User user);
|
||||||
Task SendEmailVerificationAsync(User user);
|
Task SendEmailVerificationAsync(User user);
|
||||||
|
@ -552,9 +552,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name,
|
public async Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options,
|
||||||
CredentialCreateOptions options,
|
AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf,
|
||||||
AuthenticatorAttestationRawResponse attestationResponse)
|
string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null)
|
||||||
{
|
{
|
||||||
var existingCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id);
|
var existingCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id);
|
||||||
if (existingCredentials.Count >= 5)
|
if (existingCredentials.Count >= 5)
|
||||||
@ -575,7 +575,11 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
Type = success.Result.CredType,
|
Type = success.Result.CredType,
|
||||||
AaGuid = success.Result.Aaguid,
|
AaGuid = success.Result.Aaguid,
|
||||||
Counter = (int)success.Result.Counter,
|
Counter = (int)success.Result.Counter,
|
||||||
UserId = user.Id
|
UserId = user.Id,
|
||||||
|
SupportsPrf = supportsPrf,
|
||||||
|
EncryptedUserKey = encryptedUserKey,
|
||||||
|
EncryptedPublicKey = encryptedPublicKey,
|
||||||
|
EncryptedPrivateKey = encryptedPrivateKey
|
||||||
};
|
};
|
||||||
|
|
||||||
await _webAuthnCredentialRepository.CreateAsync(credential);
|
await _webAuthnCredentialRepository.CreateAsync(credential);
|
||||||
|
@ -116,7 +116,7 @@ public class WebAuthnControllerTests
|
|||||||
.GetUserByPrincipalAsync(default)
|
.GetUserByPrincipalAsync(default)
|
||||||
.ReturnsForAnyArgs(user);
|
.ReturnsForAnyArgs(user);
|
||||||
sutProvider.GetDependency<IUserService>()
|
sutProvider.GetDependency<IUserService>()
|
||||||
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>())
|
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>(), requestModel.SupportsPrf, requestModel.EncryptedUserKey, requestModel.EncryptedPublicKey, requestModel.EncryptedPrivateKey)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
||||||
.Unprotect(requestModel.Token)
|
.Unprotect(requestModel.Token)
|
||||||
|
@ -195,7 +195,7 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(existingCredentials);
|
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(existingCredentials);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.CompleteWebAuthLoginRegistrationAsync(user, "name", options, response);
|
var result = await sutProvider.Sut.CompleteWebAuthLoginRegistrationAsync(user, "name", options, response, false, null, null, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
Reference in New Issue
Block a user