1
0
mirror of https://github.com/bitwarden/server synced 2025-12-27 21:53:24 +00:00

Merge remote-tracking branch 'origin/master' into feature/sm-billing

This commit is contained in:
Thomas Rittson
2023-07-04 14:44:02 +10:00
87 changed files with 1259 additions and 665 deletions

View File

@@ -1,6 +1,7 @@
using Bit.Core;
using Bit.Core.Jobs;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Quartz;
namespace Bit.Admin.Auth.Jobs;
@@ -8,20 +9,26 @@ namespace Bit.Admin.Auth.Jobs;
public class DeleteAuthRequestsJob : BaseJob
{
private readonly IAuthRequestRepository _authRepo;
private readonly IGlobalSettings _globalSettings;
public DeleteAuthRequestsJob(
IAuthRequestRepository authrepo,
IGlobalSettings globalSettings,
ILogger<DeleteAuthRequestsJob> logger)
: base(logger)
{
_authRepo = authrepo;
_globalSettings = globalSettings;
}
protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: Start");
var count = await _authRepo.DeleteExpiredAsync();
_logger.LogInformation(Constants.BypassFiltersEventId, $"{count} records deleted from AuthRequests.");
var count = await _authRepo.DeleteExpiredAsync(
_globalSettings.PasswordlessAuth.UserRequestExpiration,
_globalSettings.PasswordlessAuth.AdminRequestExpiration,
_globalSettings.PasswordlessAuth.AfterAdminApprovalExpiration);
_logger.LogInformation(Constants.BypassFiltersEventId, "{Count} records deleted from AuthRequests.", count);
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: End");
}
}

View File

@@ -17,8 +17,8 @@
"jquery": "3.5.1",
"merge-stream": "2.0.0",
"popper.js": "1.16.1",
"sass": "^1.49.7",
"toastr": "^2.1.4"
"sass": "1.49.7",
"toastr": "2.1.4"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -3925,9 +3925,9 @@
}
},
"node_modules/sass/node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
@@ -4694,7 +4694,7 @@
"node_modules/toastr": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=",
"integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
"dev": true,
"dependencies": {
"jquery": ">=1.12.0"
@@ -8168,9 +8168,9 @@
},
"dependencies": {
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@@ -8774,7 +8774,7 @@
"toastr": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=",
"integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
"dev": true,
"requires": {
"jquery": ">=1.12.0"

View File

@@ -16,7 +16,7 @@
"jquery": "3.5.1",
"merge-stream": "2.0.0",
"popper.js": "1.16.1",
"sass": "^1.49.7",
"toastr": "^2.1.4"
"sass": "1.49.7",
"toastr": "2.1.4"
}
}

View File

@@ -2834,7 +2834,7 @@
"commercial.core": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3"
"Core": "2023.5.1"
}
},
"core": {
@@ -2882,7 +2882,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2890,7 +2890,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2902,7 +2902,7 @@
"migrator": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.Extensions.Logging": "6.0.0",
"dbup-sqlserver": "5.0.8"
}
@@ -2910,30 +2910,30 @@
"mysqlmigrations": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
},
"postgresmigrations": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
},
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
},
"sqlitemigrations": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -9,6 +9,7 @@ using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -19,6 +20,7 @@ namespace Bit.Api.Controllers;
[Authorize("Application")]
public class OrganizationUsersController : Controller
{
private readonly IEnableAccessSecretsManagerCommand _enableAccessSecretsManagerCommand;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
@@ -29,6 +31,7 @@ public class OrganizationUsersController : Controller
private readonly ICurrentContext _currentContext;
public OrganizationUsersController(
IEnableAccessSecretsManagerCommand enableAccessSecretsManagerCommand,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
@@ -38,6 +41,7 @@ public class OrganizationUsersController : Controller
IPolicyRepository policyRepository,
ICurrentContext currentContext)
{
_enableAccessSecretsManagerCommand = enableAccessSecretsManagerCommand;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
@@ -420,6 +424,29 @@ public class OrganizationUsersController : Controller
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
}
[HttpPatch("enable-secrets-manager")]
[HttpPut("enable-secrets-manager")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkEnableSecretsManagerAsync(Guid orgId,
[FromBody] OrganizationUserBulkRequestModel model)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var orgUsers = (await _organizationUserRepository.GetManyAsync(model.Ids))
.Where(ou => ou.OrganizationId == orgId).ToList();
if (orgUsers.Count == 0)
{
throw new BadRequestException("Users invalid.");
}
var results = await _enableAccessSecretsManagerCommand.EnableUsersAsync(orgUsers);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.organizationUser.Id, r.error)));
}
private async Task RestoreOrRevokeUserAsync(
Guid orgId,
Guid id,

View File

@@ -28,6 +28,7 @@ public class ConfigResponseModel : ResponseModel
GitHash = AssemblyHelpers.GetGitHash();
Environment = new EnvironmentConfigResponseModel
{
CloudRegion = globalSettings.BaseServiceUri.CloudRegion,
Vault = globalSettings.BaseServiceUri.Vault,
Api = globalSettings.BaseServiceUri.Api,
Identity = globalSettings.BaseServiceUri.Identity,
@@ -46,6 +47,7 @@ public class ServerConfigResponseModel
public class EnvironmentConfigResponseModel
{
public string CloudRegion { get; set; }
public string Vault { get; set; }
public string Api { get; set; }
public string Identity { get; set; }

View File

@@ -73,9 +73,8 @@ public class ProjectsController : Controller
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var result = await _createProjectCommand.CreateAsync(project, userId);
var result = await _createProjectCommand.CreateAsync(project, userId, _currentContext.ClientType);
// Creating a project means you have read & write permission.
return new ProjectResponseModel(result, true, true);

View File

@@ -136,7 +136,10 @@ public class Startup
services.AddCoreLocalizationServices();
//health check
services.AddHealthChecks(globalSettings);
if (!globalSettings.SelfHosted)
{
services.AddHealthChecks(globalSettings);
}
#if OSS
services.AddOosServices();
@@ -215,12 +218,15 @@ public class Startup
{
endpoints.MapDefaultControllerRoute();
endpoints.MapHealthChecks("/healthz");
endpoints.MapHealthChecks("/healthz/extended", new HealthCheckOptions
if (!globalSettings.SelfHosted)
{
ResponseWriter = HealthCheckServiceExtensions.WriteResponse
});
endpoints.MapHealthChecks("/healthz");
endpoints.MapHealthChecks("/healthz/extended", new HealthCheckOptions
{
ResponseWriter = HealthCheckServiceExtensions.WriteResponse
});
}
});
// Add Swagger

View File

@@ -1,5 +1,6 @@
using Bit.Core.IdentityServer;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.SharedWeb.Health;
using Microsoft.OpenApi.Models;
@@ -80,35 +81,35 @@ public static class ServiceCollectionExtensions
builder.AddUrlGroup(identityUri, "identity");
if (!string.IsNullOrEmpty(globalSettings.SqlServer.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.SqlServer.ConnectionString))
{
builder.AddSqlServer(globalSettings.SqlServer.ConnectionString);
}
if (!string.IsNullOrEmpty(globalSettings.Redis.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Redis.ConnectionString))
{
builder.AddRedis(globalSettings.Redis.ConnectionString);
}
if (!string.IsNullOrEmpty(globalSettings.Storage.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
{
builder.AddAzureQueueStorage(globalSettings.Storage.ConnectionString, name: "storage_queue")
.AddAzureQueueStorage(globalSettings.Events.ConnectionString, name: "events_queue");
}
if (!string.IsNullOrEmpty(globalSettings.Notifications.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Notifications.ConnectionString))
{
builder.AddAzureQueueStorage(globalSettings.Notifications.ConnectionString,
name: "notifications_queue");
}
if (!string.IsNullOrEmpty(globalSettings.ServiceBus.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString))
{
builder.AddAzureServiceBusTopic(_ => globalSettings.ServiceBus.ConnectionString,
_ => globalSettings.ServiceBus.ApplicationCacheTopicName, name: "service_bus");
}
if (!string.IsNullOrEmpty(globalSettings.Mail.SendGridApiKey))
if (CoreHelpers.SettingHasValue(globalSettings.Mail.SendGridApiKey))
{
builder.AddSendGrid(globalSettings.Mail.SendGridApiKey);
}

View File

@@ -2907,85 +2907,85 @@
"commercial.core": {
"type": "Project",
"dependencies": {
"Core": "[2023.5.0, )"
"Core": "2023.5.1"
}
},
"commercial.infrastructure.entityframework": {
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )",
"Core": "[2023.5.0, )",
"Infrastructure.EntityFramework": "[2023.5.0, )"
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
},
"core": {
"type": "Project",
"dependencies": {
"AWSSDK.SQS": "[3.7.2.47, )",
"AWSSDK.SimpleEmail": "[3.7.0.150, )",
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",
"IdentityServer4.AccessTokenValidation": "[3.0.1, )",
"LaunchDarkly.ServerSdk": "[7.0.0, )",
"MailKit": "[3.2.0, )",
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )",
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )",
"Microsoft.Azure.NotificationHubs": "[4.1.0, )",
"Microsoft.Azure.ServiceBus": "[5.2.0, )",
"Microsoft.Data.SqlClient": "[5.0.1, )",
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )",
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )",
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )",
"Newtonsoft.Json": "[13.0.1, )",
"Otp.NET": "[1.2.2, )",
"Quartz": "[3.4.0, )",
"SendGrid": "[9.27.0, )",
"Sentry.Serilog": "[3.16.0, )",
"Serilog.AspNetCore": "[5.0.0, )",
"Serilog.Extensions.Logging": "[3.1.0, )",
"Serilog.Extensions.Logging.File": "[2.0.0, )",
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )",
"Serilog.Sinks.SyslogMessages": "[2.0.6, )",
"Stripe.net": "[40.0.0, )",
"YubicoDotNetClient": "[1.2.0, )"
"AWSSDK.SQS": "3.7.2.47",
"AWSSDK.SimpleEmail": "3.7.0.150",
"AspNetCoreRateLimit": "4.0.2",
"AspNetCoreRateLimit.Redis": "1.0.1",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
"Azure.Storage.Blobs": "12.14.1",
"Azure.Storage.Queues": "12.12.0",
"BitPay.Light": "1.0.1907",
"Braintree": "5.12.0",
"DnsClient": "1.7.0",
"Fido2.AspNet": "3.0.1",
"Handlebars.Net": "2.1.2",
"IdentityServer4": "4.1.2",
"IdentityServer4.AccessTokenValidation": "3.0.1",
"LaunchDarkly.ServerSdk": "7.0.0",
"MailKit": "3.2.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
"Microsoft.Azure.Cosmos.Table": "1.0.8",
"Microsoft.Azure.NotificationHubs": "4.1.0",
"Microsoft.Azure.ServiceBus": "5.2.0",
"Microsoft.Data.SqlClient": "5.0.1",
"Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
"Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
"Microsoft.Extensions.Identity.Stores": "6.0.4",
"Newtonsoft.Json": "13.0.1",
"Otp.NET": "1.2.2",
"Quartz": "3.4.0",
"SendGrid": "9.27.0",
"Sentry.Serilog": "3.16.0",
"Serilog.AspNetCore": "5.0.0",
"Serilog.Extensions.Logging": "3.1.0",
"Serilog.Extensions.Logging.File": "2.0.0",
"Serilog.Sinks.AzureCosmosDB": "2.0.0",
"Serilog.Sinks.SyslogMessages": "2.0.6",
"Stripe.net": "40.0.0",
"YubicoDotNetClient": "1.2.0"
}
},
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "[2023.5.0, )",
"Dapper": "[2.0.123, )"
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
"infrastructure.entityframework": {
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )",
"Core": "[2023.5.0, )",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )",
"Npgsql.EntityFrameworkCore.PostgreSQL": "[6.0.8, )",
"Pomelo.EntityFrameworkCore.MySql": "[6.0.2, )",
"linq2db.EntityFrameworkCore": "[6.11.0, )"
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
"Npgsql.EntityFrameworkCore.PostgreSQL": "6.0.8",
"Pomelo.EntityFrameworkCore.MySql": "6.0.2",
"linq2db.EntityFrameworkCore": "6.11.0"
}
},
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "[2023.5.0, )",
"Infrastructure.Dapper": "[2023.5.0, )",
"Infrastructure.EntityFramework": "[2023.5.0, )"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2794,7 +2794,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -5,7 +5,7 @@ namespace Bit.Core.Repositories;
public interface IAuthRequestRepository : IRepository<AuthRequest, Guid>
{
Task<int> DeleteExpiredAsync();
Task<int> DeleteExpiredAsync(TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration);
Task<ICollection<AuthRequest>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<OrganizationAdminAuthRequest>> GetManyPendingByOrganizationIdAsync(Guid organizationId);
Task<ICollection<OrganizationAdminAuthRequest>> GetManyAdminApprovalRequestsByManyIdsAsync(Guid organizationId, IEnumerable<Guid> ids);

View File

@@ -3,4 +3,7 @@
public interface IPasswordlessAuthSettings
{
bool KnownDevicesOnly { get; set; }
TimeSpan UserRequestExpiration { get; set; }
TimeSpan AdminRequestExpiration { get; set; }
TimeSpan AfterAdminApprovalExpiration { get; set; }
}

View File

@@ -15,6 +15,8 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Tokens;
@@ -29,6 +31,7 @@ public static class OrganizationServiceCollectionExtensions
public static void AddOrganizationServices(this IServiceCollection services, IGlobalSettings globalSettings)
{
services.AddScoped<IOrganizationService, OrganizationService>();
services.AddScoped<IEnableAccessSecretsManagerCommand, EnableAccessSecretsManagerCommand>();
services.AddTokenizers();
services.AddOrganizationGroupCommands();
services.AddOrganizationConnectionCommands();

View File

@@ -0,0 +1,43 @@
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
public class EnableAccessSecretsManagerCommand : IEnableAccessSecretsManagerCommand
{
private readonly IOrganizationUserRepository _organizationUserRepository;
public EnableAccessSecretsManagerCommand(IOrganizationUserRepository organizationUserRepository)
{
_organizationUserRepository = organizationUserRepository;
}
public async Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers)
{
var results = new List<(OrganizationUser organizationUser, string error)>();
var usersToEnable = new List<OrganizationUser>();
foreach (var orgUser in organizationUsers)
{
if (orgUser.AccessSecretsManager)
{
results.Add((orgUser, "User already has access to Secrets Manager"));
}
else
{
orgUser.AccessSecretsManager = true;
usersToEnable.Add(orgUser);
results.Add((orgUser, ""));
}
}
if (usersToEnable.Any())
{
await _organizationUserRepository.ReplaceManyAsync(usersToEnable);
}
return results;
}
}

View File

@@ -0,0 +1,9 @@
using Bit.Core.Entities;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
public interface IEnableAccessSecretsManagerCommand
{
Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers);
}

View File

@@ -1,8 +1,9 @@
using Bit.Core.SecretsManager.Entities;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces;
public interface ICreateProjectCommand
{
Task<Project> CreateAsync(Project project, Guid userId);
Task<Project> CreateAsync(Project project, Guid userId, ClientType clientType);
}

View File

@@ -143,6 +143,7 @@ public class GlobalSettings : IGlobalSettings
_globalSettings = globalSettings;
}
public string CloudRegion { get; set; }
public string Vault { get; set; }
public string VaultWithHash => $"{Vault}/#";
@@ -534,6 +535,9 @@ public class GlobalSettings : IGlobalSettings
public class PasswordlessAuthSettings : IPasswordlessAuthSettings
{
public bool KnownDevicesOnly { get; set; } = true;
public TimeSpan UserRequestExpiration { get; set; } = TimeSpan.FromMinutes(15);
public TimeSpan AdminRequestExpiration { get; set; } = TimeSpan.FromDays(7);
public TimeSpan AfterAdminApprovalExpiration { get; set; } = TimeSpan.FromHours(12);
}
public class DomainVerificationSettings : IDomainVerificationSettings

View File

@@ -3,6 +3,7 @@ namespace Bit.Core.Settings;
public interface IBaseServiceUriSettings
{
string CloudRegion { get; set; }
string Vault { get; set; }
string VaultWithHash { get; }
string Api { get; set; }

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2794,7 +2794,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2794,7 +2794,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -2796,7 +2796,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2804,7 +2804,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2816,9 +2816,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -2808,7 +2808,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2816,7 +2816,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2828,9 +2828,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -19,13 +19,19 @@ public class AuthRequestRepository : Repository<AuthRequest, Guid>, IAuthRequest
: base(connectionString, readOnlyConnectionString)
{ }
public async Task<int> DeleteExpiredAsync()
public async Task<int> DeleteExpiredAsync(
TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration)
{
using (var connection = new SqlConnection(ConnectionString))
{
return await connection.ExecuteAsync(
$"[{Schema}].[AuthRequest_DeleteIfExpired]",
null,
new
{
UserExpirationSeconds = (int)userRequestExpiration.TotalSeconds,
AdminExpirationSeconds = (int)adminRequestExpiration.TotalSeconds,
AdminApprovalExpirationSeconds = (int)afterAdminApprovalExpiration.TotalSeconds,
},
commandType: CommandType.StoredProcedure);
}
}

View File

@@ -15,15 +15,20 @@ public class AuthRequestRepository : Repository<Core.Auth.Entities.AuthRequest,
public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.AuthRequests)
{ }
public async Task<int> DeleteExpiredAsync()
public async Task<int> DeleteExpiredAsync(
TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var expiredRequests = await dbContext.AuthRequests.Where(a => a.CreationDate < DateTime.Now.AddMinutes(-15)).ToListAsync();
var expiredRequests = await dbContext.AuthRequests
.Where(a => (a.Type != AuthRequestType.AdminApproval && a.CreationDate.AddSeconds(userRequestExpiration.TotalSeconds) < DateTime.UtcNow)
|| (a.Type == AuthRequestType.AdminApproval && a.Approved != true && a.CreationDate.AddSeconds(adminRequestExpiration.TotalSeconds) < DateTime.UtcNow)
|| (a.Type == AuthRequestType.AdminApproval && a.Approved == true && a.ResponseDate.Value.AddSeconds(afterAdminApprovalExpiration.TotalSeconds) < DateTime.UtcNow))
.ToListAsync();
dbContext.AuthRequests.RemoveRange(expiredRequests);
await dbContext.SaveChangesAsync();
return 1;
return await dbContext.SaveChangesAsync();
}
}

View File

@@ -50,7 +50,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
where ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null
select new { c, ou, o, cc, cu, gu, g, cg }.c;
select c;
var query2 = from c in dbContext.Ciphers
where c.UserId == _userId
@@ -79,14 +79,23 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
private static Guid? GetFolderId(Guid? userId, Cipher cipher)
{
if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.Folders))
try
{
var folders = JsonSerializer.Deserialize<Dictionary<Guid, Guid>>(cipher.Folders);
if (folders.TryGetValue(userId.Value, out var folder))
if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.Folders))
{
return folder;
var folders = JsonSerializer.Deserialize<Dictionary<Guid, Guid>>(cipher.Folders);
if (folders.TryGetValue(userId.Value, out var folder))
{
return folder;
}
}
return null;
}
catch
{
// Some Folders might be in an invalid format like: '{ "", "<ValidGuid>" }'
return null;
}
return null;
}
}

View File

@@ -1,4 +1,6 @@
using AutoMapper;
using System.Text.Json;
using System.Text.Json.Nodes;
using AutoMapper;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.Core.Vault.Enums;
@@ -13,8 +15,8 @@ using Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NS = Newtonsoft.Json;
using NSL = Newtonsoft.Json.Linq;
using User = Bit.Core.Entities.User;
namespace Bit.Infrastructure.EntityFramework.Vault.Repositories;
@@ -198,9 +200,9 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
{
var dbContext = GetDatabaseContext(scope);
var cipher = await dbContext.Ciphers.FindAsync(cipherId);
var attachmentsJson = JObject.Parse(cipher.Attachments);
var attachmentsJson = NSL.JObject.Parse(cipher.Attachments);
attachmentsJson.Remove(attachmentId);
cipher.Attachments = JsonConvert.SerializeObject(attachmentsJson);
cipher.Attachments = NS.JsonConvert.SerializeObject(attachmentsJson);
await dbContext.SaveChangesAsync();
if (cipher.OrganizationId.HasValue)
@@ -396,8 +398,8 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
await idsToMove.ForEachAsync(cipher =>
{
var foldersJson = string.IsNullOrWhiteSpace(cipher.Folders) ?
new JObject() :
JObject.Parse(cipher.Folders);
new NSL.JObject() :
NSL.JObject.Parse(cipher.Folders);
if (folderId.HasValue)
{
@@ -409,7 +411,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
foldersJson.Remove(userId.ToString());
}
dbContext.Attach(cipher);
cipher.Folders = JsonConvert.SerializeObject(foldersJson);
cipher.Folders = NS.JsonConvert.SerializeObject(foldersJson);
});
await dbContext.UserBumpAccountRevisionDateAsync(userId);
await dbContext.SaveChangesAsync();
@@ -418,27 +420,27 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
public async Task ReplaceAsync(CipherDetails cipher)
{
cipher.UserId = cipher.OrganizationId.HasValue ?
null :
cipher.UserId;
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.Ciphers.FindAsync(cipher.Id);
if (entity != null)
{
var userIdKey = $"\"{cipher.UserId}\"";
if (cipher.Favorite)
{
if (cipher.Favorites == null)
{
cipher.Favorites = $"{{{userIdKey}:true}}";
var jsonObject = new JsonObject(new[]
{
new KeyValuePair<string, JsonNode>(cipher.UserId.Value.ToString(), true),
});
cipher.Favorites = JsonSerializer.Serialize(jsonObject);
}
else
{
var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites);
favorites.Add(cipher.UserId.Value, true);
cipher.Favorites = JsonConvert.SerializeObject(favorites);
cipher.Favorites = JsonSerializer.Serialize(favorites);
}
}
else
@@ -447,32 +449,45 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
{
var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites);
favorites.Remove(cipher.UserId.Value);
cipher.Favorites = JsonConvert.SerializeObject(favorites);
cipher.Favorites = JsonSerializer.Serialize(favorites);
}
}
if (cipher.FolderId.HasValue)
{
if (cipher.Folders == null)
{
cipher.Folders = $"{{{userIdKey}:\"{cipher.FolderId}\"}}";
var jsonObject = new JsonObject(new[]
{
new KeyValuePair<string, JsonNode>(cipher.UserId.Value.ToString(), cipher.FolderId),
});
cipher.Folders = JsonSerializer.Serialize(jsonObject);
}
else
{
var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders);
folders.Add(cipher.UserId.Value, cipher.FolderId.Value);
cipher.Folders = JsonConvert.SerializeObject(folders);
cipher.Folders = JsonSerializer.Serialize(folders);
}
}
else
{
if (cipher.Folders != null && cipher.Folders.Contains(cipher.UserId.Value.ToString()))
{
var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites);
var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders);
folders.Remove(cipher.UserId.Value);
cipher.Favorites = JsonConvert.SerializeObject(folders);
cipher.Folders = JsonSerializer.Serialize(folders);
}
}
var mappedEntity = Mapper.Map<Cipher>((Core.Vault.Entities.Cipher)cipher);
// Check if this cipher is a part of an organization, and if so do
// not save the UserId into the database. This must be done after we
// set the user specific data like Folders and Favorites because
// the UserId key is used for that
cipher.UserId = cipher.OrganizationId.HasValue ?
null :
cipher.UserId;
var mappedEntity = Mapper.Map<Cipher>(cipher);
dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity);
if (cipher.OrganizationId.HasValue)
@@ -701,10 +716,10 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
var cipher = await dbContext.Ciphers.FindAsync(attachment.Id);
var attachments = string.IsNullOrWhiteSpace(cipher.Attachments) ?
new Dictionary<string, CipherAttachment.MetaData>() :
JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
var metaData = JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData);
NS.JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
var metaData = NS.JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData);
attachments[attachment.AttachmentId] = metaData;
cipher.Attachments = JsonConvert.SerializeObject(attachments);
cipher.Attachments = NS.JsonConvert.SerializeObject(attachments);
await dbContext.SaveChangesAsync();
if (attachment.OrganizationId.HasValue)
@@ -744,7 +759,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
var dbContext = GetDatabaseContext(scope);
var cipher = await dbContext.Ciphers.FindAsync(id);
var foldersJson = JObject.Parse(cipher.Folders);
var foldersJson = NSL.JObject.Parse(cipher.Folders);
if (foldersJson == null && folderId.HasValue)
{
foldersJson.Add(userId.ToString(), folderId.Value);
@@ -758,7 +773,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
foldersJson.Remove(userId.ToString());
}
var favoritesJson = JObject.Parse(cipher.Favorites);
var favoritesJson = NSL.JObject.Parse(cipher.Favorites);
if (favorite)
{
favoritesJson.Add(userId.ToString(), favorite);

View File

@@ -2836,7 +2836,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2844,7 +2844,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2856,9 +2856,9 @@
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Infrastructure.Dapper": "2023.4.3",
"Infrastructure.EntityFramework": "2023.4.3"
"Core": "2023.5.1",
"Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.5.1"
}
}
}

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "2023.4.3",
"Core": "2023.5.1",
"Dapper": "2.0.123"
}
},
@@ -2794,7 +2794,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3",
"Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -1,6 +1,19 @@
CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired]
-- UserExpirationSeconds to 15 minutes (15 * 60)
-- AdminExpirationSeconds to 7 days (7 * 24 * 60 * 60)
-- AdminApprovalExpirationSeconds to 12 hour (12 * 60 * 60)
CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired]
@UserExpirationSeconds INT = 900,
@AdminExpirationSeconds INT = 604800,
@AdminApprovalExpirationSeconds INT = 43200
AS
BEGIN
SET NOCOUNT OFF
DELETE FROM [dbo].[AuthRequest] WHERE [CreationDate] < DATEADD(minute, -15, GETUTCDATE());
DELETE FROM [dbo].[AuthRequest]
-- User requests expire after 15 minutes (by default) of their creation
WHERE ([Type] != 2 AND DATEADD(second, @UserExpirationSeconds, [CreationDate]) < GETUTCDATE())
-- Admin requests expire after 7 days (by default) of their creation if they have not been approved
OR ([Type] = 2 AND ([Approved] IS NULL OR [Approved] = 0) AND DATEADD(second, @AdminExpirationSeconds,[CreationDate]) < GETUTCDATE())
-- Admin requests expire after 12 hours (by default) of their approval
OR ([Type] = 2 AND [Approved] = 1 AND DATEADD(second, @AdminApprovalExpirationSeconds, [ResponseDate]) < GETUTCDATE());
END