1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

[SM-394] Secrets Manager (#2164)

Long lived feature branch for Secrets Manager

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com>
Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com>
Co-authored-by: Thomas Avery <tavery@bitwarden.com>
Co-authored-by: Colton Hurst <colton@coltonhurst.com>
This commit is contained in:
Oscar Hinton
2023-01-13 15:02:53 +01:00
committed by GitHub
parent 09e524c9a2
commit 1f0fc43278
188 changed files with 21346 additions and 329 deletions

View File

@ -24,6 +24,7 @@
<When Condition="!$(DefineConstants.Contains('OSS'))">
<ItemGroup>
<ProjectReference Include="..\..\bitwarden_license\src\Commercial.Core\Commercial.Core.csproj" />
<ProjectReference Include="..\..\bitwarden_license\src\Commercial.Infrastructure.EntityFramework\Commercial.Infrastructure.EntityFramework.csproj" />
</ItemGroup>
</When>
</Choose>

View File

@ -0,0 +1,77 @@
using Bit.Api.Models.Response;
using Bit.Api.SecretManagerFeatures.Models.Request;
using Bit.Api.SecretManagerFeatures.Models.Response;
using Bit.Api.Utilities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretManagerFeatures.Projects.Interfaces;
using Bit.Core.Services;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[SecretsManager]
public class ProjectsController : Controller
{
private readonly IUserService _userService;
private readonly IProjectRepository _projectRepository;
private readonly ICreateProjectCommand _createProjectCommand;
private readonly IUpdateProjectCommand _updateProjectCommand;
private readonly IDeleteProjectCommand _deleteProjectCommand;
public ProjectsController(
IUserService userService,
IProjectRepository projectRepository,
ICreateProjectCommand createProjectCommand,
IUpdateProjectCommand updateProjectCommand,
IDeleteProjectCommand deleteProjectCommand)
{
_userService = userService;
_projectRepository = projectRepository;
_createProjectCommand = createProjectCommand;
_updateProjectCommand = updateProjectCommand;
_deleteProjectCommand = deleteProjectCommand;
}
[HttpPost("organizations/{organizationId}/projects")]
public async Task<ProjectResponseModel> CreateAsync([FromRoute] Guid organizationId, [FromBody] ProjectCreateRequestModel createRequest)
{
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId));
return new ProjectResponseModel(result);
}
[HttpPut("projects/{id}")]
public async Task<ProjectResponseModel> UpdateProjectAsync([FromRoute] Guid id, [FromBody] ProjectUpdateRequestModel updateRequest)
{
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id));
return new ProjectResponseModel(result);
}
[HttpGet("organizations/{organizationId}/projects")]
public async Task<ListResponseModel<ProjectResponseModel>> GetProjectsByOrganizationAsync([FromRoute] Guid organizationId)
{
var userId = _userService.GetProperUserId(User).Value;
var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId);
var responses = projects.Select(project => new ProjectResponseModel(project));
return new ListResponseModel<ProjectResponseModel>(responses);
}
[HttpGet("projects/{id}")]
public async Task<ProjectResponseModel> GetProjectAsync([FromRoute] Guid id)
{
var project = await _projectRepository.GetByIdAsync(id);
if (project == null)
{
throw new NotFoundException();
}
return new ProjectResponseModel(project);
}
[HttpPost("projects/delete")]
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteProjectsAsync([FromBody] List<Guid> ids)
{
var results = await _deleteProjectCommand.DeleteProjects(ids);
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
return new ListResponseModel<BulkDeleteResponseModel>(responses);
}
}

View File

@ -0,0 +1,80 @@
using Bit.Api.Models.Response;
using Bit.Api.SecretManagerFeatures.Models.Request;
using Bit.Api.SecretManagerFeatures.Models.Response;
using Bit.Api.Utilities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretManagerFeatures.Secrets.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[SecretsManager]
[Authorize("secrets")]
public class SecretsController : Controller
{
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly ICreateSecretCommand _createSecretCommand;
private readonly IUpdateSecretCommand _updateSecretCommand;
private readonly IDeleteSecretCommand _deleteSecretCommand;
public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand)
{
_secretRepository = secretRepository;
_projectRepository = projectRepository;
_createSecretCommand = createSecretCommand;
_updateSecretCommand = updateSecretCommand;
_deleteSecretCommand = deleteSecretCommand;
}
[HttpGet("organizations/{organizationId}/secrets")]
public async Task<SecretWithProjectsListResponseModel> GetSecretsByOrganizationAsync([FromRoute] Guid organizationId)
{
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
return new SecretWithProjectsListResponseModel(secrets);
}
[HttpGet("secrets/{id}")]
public async Task<SecretResponseModel> GetSecretAsync([FromRoute] Guid id)
{
var secret = await _secretRepository.GetByIdAsync(id);
if (secret == null)
{
throw new NotFoundException();
}
return new SecretResponseModel(secret);
}
[HttpGet("projects/{projectId}/secrets")]
public async Task<SecretWithProjectsListResponseModel> GetSecretsByProjectAsync([FromRoute] Guid projectId)
{
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId);
var responses = secrets.Select(s => new SecretResponseModel(s));
return new SecretWithProjectsListResponseModel(secrets);
}
[HttpPost("organizations/{organizationId}/secrets")]
public async Task<SecretResponseModel> CreateSecretAsync([FromRoute] Guid organizationId, [FromBody] SecretCreateRequestModel createRequest)
{
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
return new SecretResponseModel(result);
}
[HttpPut("secrets/{id}")]
public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
{
var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id));
return new SecretResponseModel(result);
}
// TODO Once permissions are setup for Secrets Manager need to enforce them on delete.
[HttpPost("secrets/delete")]
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
{
var results = await _deleteSecretCommand.DeleteSecrets(ids);
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
return new ListResponseModel<BulkDeleteResponseModel>(responses);
}
}

View File

@ -0,0 +1,72 @@
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.SecretsManager;
using Bit.Api.SecretManagerFeatures.Models.Request;
using Bit.Api.SecretManagerFeatures.Models.Response;
using Bit.Api.Utilities;
using Bit.Core.Repositories;
using Bit.Core.SecretManagerFeatures.AccessTokens.Interfaces;
using Bit.Core.SecretManagerFeatures.ServiceAccounts.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[SecretsManager]
[Route("service-accounts")]
public class ServiceAccountsController : Controller
{
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IApiKeyRepository _apiKeyRepository;
private readonly ICreateServiceAccountCommand _createServiceAccountCommand;
private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
public ServiceAccountsController(
IServiceAccountRepository serviceAccountRepository,
ICreateAccessTokenCommand createAccessTokenCommand,
IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand)
{
_serviceAccountRepository = serviceAccountRepository;
_apiKeyRepository = apiKeyRepository;
_createServiceAccountCommand = createServiceAccountCommand;
_updateServiceAccountCommand = updateServiceAccountCommand;
_createAccessTokenCommand = createAccessTokenCommand;
}
[HttpGet("/organizations/{organizationId}/service-accounts")]
public async Task<ListResponseModel<ServiceAccountResponseModel>> GetServiceAccountsByOrganizationAsync([FromRoute] Guid organizationId)
{
var serviceAccounts = await _serviceAccountRepository.GetManyByOrganizationIdAsync(organizationId);
var responses = serviceAccounts.Select(serviceAccount => new ServiceAccountResponseModel(serviceAccount));
return new ListResponseModel<ServiceAccountResponseModel>(responses);
}
[HttpPost("/organizations/{organizationId}/service-accounts")]
public async Task<ServiceAccountResponseModel> CreateServiceAccountAsync([FromRoute] Guid organizationId, [FromBody] ServiceAccountCreateRequestModel createRequest)
{
var result = await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId));
return new ServiceAccountResponseModel(result);
}
[HttpPut("{id}")]
public async Task<ServiceAccountResponseModel> UpdateServiceAccountAsync([FromRoute] Guid id, [FromBody] ServiceAccountUpdateRequestModel updateRequest)
{
var result = await _updateServiceAccountCommand.UpdateAsync(updateRequest.ToServiceAccount(id));
return new ServiceAccountResponseModel(result);
}
[HttpGet("{id}/access-tokens")]
public async Task<ListResponseModel<AccessTokenResponseModel>> GetAccessTokens([FromRoute] Guid id)
{
var accessTokens = await _apiKeyRepository.GetManyByServiceAccountIdAsync(id);
var responses = accessTokens.Select(token => new AccessTokenResponseModel(token));
return new ListResponseModel<AccessTokenResponseModel>(responses);
}
[HttpPost("{id}/access-tokens")]
public async Task<AccessTokenCreationResponseModel> CreateAccessTokenAsync([FromRoute] Guid id, [FromBody] AccessTokenCreateRequestModel request)
{
var result = await _createAccessTokenCommand.CreateAsync(request.ToApiKey(id));
return new AccessTokenCreationResponseModel(result);
}
}

View File

@ -43,6 +43,7 @@ public class OrganizationResponseModel : ResponseModel
Use2fa = organization.Use2fa;
UseApi = organization.UseApi;
UseResetPassword = organization.UseResetPassword;
UseSecretsManager = organization.UseSecretsManager;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
SelfHost = organization.SelfHost;
@ -75,6 +76,7 @@ public class OrganizationResponseModel : ResponseModel
public bool UseTotp { get; set; }
public bool Use2fa { get; set; }
public bool UseApi { get; set; }
public bool UseSecretsManager { get; set; }
public bool UseResetPassword { get; set; }
public bool UsersGetPremium { get; set; }
public bool UseCustomPermissions { get; set; }

View File

@ -25,6 +25,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
Use2fa = organization.Use2fa;
UseApi = organization.UseApi;
UseResetPassword = organization.UseResetPassword;
UseSecretsManager = organization.UseSecretsManager;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
SelfHost = organization.SelfHost;
@ -73,6 +74,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
public bool Use2fa { get; set; }
public bool UseApi { get; set; }
public bool UseResetPassword { get; set; }
public bool UseSecretsManager { get; set; }
public bool UsersGetPremium { get; set; }
public bool UseCustomPermissions { get; set; }
public bool SelfHost { get; set; }

View File

@ -0,0 +1,27 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.SecretsManager;
public class AccessTokenResponseModel : ResponseModel
{
public AccessTokenResponseModel(ApiKey apiKey, string obj = "accessToken")
: base(obj)
{
Id = apiKey.Id;
Name = apiKey.Name;
Scopes = apiKey.GetScopes();
ExpireAt = apiKey.ExpireAt;
CreationDate = apiKey.CreationDate;
RevisionDate = apiKey.RevisionDate;
}
public Guid Id { get; }
public string Name { get; }
public ICollection<string> Scopes { get; }
public DateTime? ExpireAt { get; }
public DateTime CreationDate { get; }
public DateTime RevisionDate { get; }
}

View File

@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class AccessTokenCreateRequestModel
{
[Required]
[EncryptedString]
[EncryptedStringLength(200)]
public string Name { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(4000)]
public string EncryptedPayload { get; set; }
[Required]
[EncryptedString]
public string Key { get; set; }
public DateTime? ExpireAt { get; set; }
public ApiKey ToApiKey(Guid serviceAccountId)
{
return new ApiKey()
{
ServiceAccountId = serviceAccountId,
Name = Name,
Key = Key,
ExpireAt = ExpireAt,
Scope = "[\"api.secrets\"]",
EncryptedPayload = EncryptedPayload,
};
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class ProjectCreateRequestModel
{
[Required]
[EncryptedString]
public string Name { get; set; }
public Project ToProject(Guid organizationId)
{
return new Project()
{
OrganizationId = organizationId,
Name = Name,
};
}
}

View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class ProjectUpdateRequestModel
{
[Required]
[EncryptedString]
public string Name { get; set; }
public Project ToProject(Guid id)
{
return new Project()
{
Id = id,
Name = Name,
};
}
}

View File

@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class SecretCreateRequestModel
{
[Required]
[EncryptedString]
public string Key { get; set; }
[Required]
[EncryptedString]
public string Value { get; set; }
[Required]
[EncryptedString]
public string Note { get; set; }
public Guid[] ProjectIds { get; set; }
public Secret ToSecret(Guid organizationId)
{
return new Secret()
{
OrganizationId = organizationId,
Key = Key,
Value = Value,
Note = Note,
DeletedDate = null,
Projects = ProjectIds != null && ProjectIds.Any() ? ProjectIds.Select(x => new Project() { Id = x }).ToList() : null,
};
}
}

View File

@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class SecretUpdateRequestModel
{
[Required]
[EncryptedString]
public string Key { get; set; }
[Required]
[EncryptedString]
public string Value { get; set; }
[Required]
[EncryptedString]
public string Note { get; set; }
public Guid[] ProjectIds { get; set; }
public Secret ToSecret(Guid id)
{
return new Secret()
{
Id = id,
Key = Key,
Value = Value,
Note = Note,
DeletedDate = null,
Projects = ProjectIds != null && ProjectIds.Any() ? ProjectIds.Select(x => new Project() { Id = x }).ToList() : null,
};
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class ServiceAccountUpdateRequestModel
{
[Required]
[EncryptedString]
public string Name { get; set; }
public ServiceAccount ToServiceAccount(Guid id)
{
return new ServiceAccount()
{
Id = id,
Name = Name,
};
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Api.SecretManagerFeatures.Models.Request;
public class ServiceAccountCreateRequestModel
{
[Required]
[EncryptedString]
public string Name { get; set; }
public ServiceAccount ToServiceAccount(Guid organizationId)
{
return new ServiceAccount()
{
OrganizationId = organizationId,
Name = Name,
};
}
}

View File

@ -0,0 +1,25 @@
#nullable enable
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class AccessTokenCreationResponseModel : ResponseModel
{
public AccessTokenCreationResponseModel(ApiKey apiKey, string obj = "accessTokenCreation") : base(obj)
{
Id = apiKey.Id;
Name = apiKey.Name;
ClientSecret = apiKey.ClientSecret;
ExpireAt = apiKey.ExpireAt;
CreationDate = apiKey.CreationDate;
RevisionDate = apiKey.RevisionDate;
}
public Guid Id { get; }
public string Name { get; }
public string ClientSecret { get; }
public DateTime? ExpireAt { get; }
public DateTime CreationDate { get; }
public DateTime RevisionDate { get; }
}

View File

@ -0,0 +1,25 @@
#nullable enable
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class BulkDeleteResponseModel : ResponseModel
{
public BulkDeleteResponseModel(Guid id, string error, string obj = "BulkDeleteResponseModel") : base(obj)
{
Id = id;
if (string.IsNullOrWhiteSpace(error))
{
Error = null;
}
else
{
Error = error;
}
}
public Guid Id { get; set; }
public string? Error { get; set; }
}

View File

@ -0,0 +1,34 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class ProjectResponseModel : ResponseModel
{
public ProjectResponseModel(Project project, string obj = "project")
: base(obj)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
Id = project.Id.ToString();
OrganizationId = project.OrganizationId.ToString();
Name = project.Name;
CreationDate = project.CreationDate;
RevisionDate = project.RevisionDate;
}
public string Id { get; set; }
public string OrganizationId { get; set; }
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public IEnumerable<Guid> Secrets { get; set; }
}

View File

@ -0,0 +1,53 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class SecretResponseModel : ResponseModel
{
public SecretResponseModel(Secret secret, string obj = "secret")
: base(obj)
{
if (secret == null)
{
throw new ArgumentNullException(nameof(secret));
}
Id = secret.Id.ToString();
OrganizationId = secret.OrganizationId.ToString();
Key = secret.Key;
Value = secret.Value;
Note = secret.Note;
CreationDate = secret.CreationDate;
RevisionDate = secret.RevisionDate;
Projects = secret.Projects?.Select(p => new InnerProject(p));
}
public string Id { get; set; }
public string OrganizationId { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Note { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public IEnumerable<InnerProject> Projects { get; set; }
public class InnerProject
{
public InnerProject(Project project)
{
Id = project.Id;
Name = project.Name;
}
public Guid Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,55 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class SecretWithProjectsListResponseModel : ResponseModel
{
public SecretWithProjectsListResponseModel(IEnumerable<Secret> secrets, string obj = "SecretsWithProjectsList") : base(obj)
{
Secrets = secrets.Select(s => new InnerSecret(s));
Projects = secrets.SelectMany(s => s.Projects).DistinctBy(p => p.Id).Select(p => new InnerProject(p));
}
public IEnumerable<InnerSecret> Secrets { get; set; }
public IEnumerable<InnerProject> Projects { get; set; }
public class InnerProject
{
public InnerProject(Project project)
{
Id = project.Id;
Name = project.Name;
}
public Guid Id { get; set; }
public string Name { get; set; }
}
public class InnerSecret
{
public InnerSecret(Secret secret)
{
Id = secret.Id.ToString();
OrganizationId = secret.OrganizationId.ToString();
Key = secret.Key;
CreationDate = secret.CreationDate;
RevisionDate = secret.RevisionDate;
Projects = secret.Projects?.Select(p => new InnerProject(p));
}
public string Id { get; set; }
public string OrganizationId { get; set; }
public string Key { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public IEnumerable<InnerProject> Projects { get; set; }
}
}

View File

@ -0,0 +1,33 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.SecretManagerFeatures.Models.Response;
public class ServiceAccountResponseModel : ResponseModel
{
public ServiceAccountResponseModel(ServiceAccount serviceAccount, string obj = "serviceAccount")
: base(obj)
{
if (serviceAccount == null)
{
throw new ArgumentNullException(nameof(serviceAccount));
}
Id = serviceAccount.Id.ToString();
OrganizationId = serviceAccount.OrganizationId.ToString();
Name = serviceAccount.Name;
CreationDate = serviceAccount.CreationDate;
RevisionDate = serviceAccount.RevisionDate;
}
public string Id { get; set; }
public string OrganizationId { get; set; }
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
}

View File

@ -8,6 +8,7 @@ using Stripe;
using Bit.Core.Utilities;
using IdentityModel;
using System.Globalization;
using Bit.Core.IdentityServer;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi.Models;
using Bit.SharedWeb.Utilities;
@ -15,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
#if !OSS
using Bit.Commercial.Core.Utilities;
using Bit.Commercial.Infrastructure.EntityFramework;
#endif
namespace Bit.Api;
@ -84,34 +86,42 @@ public class Startup
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external");
policy.RequireClaim(JwtClaimTypes.Scope, "api");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.Api);
});
config.AddPolicy("Web", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external");
policy.RequireClaim(JwtClaimTypes.Scope, "api");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.Api);
policy.RequireClaim(JwtClaimTypes.ClientId, "web");
});
config.AddPolicy("Push", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.Scope, "api.push");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.ApiPush);
});
config.AddPolicy("Licensing", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.Scope, "api.licensing");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.ApiLicensing);
});
config.AddPolicy("Organization", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.Scope, "api.organization");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.ApiOrganization);
});
config.AddPolicy("Installation", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim(JwtClaimTypes.Scope, "api.installation");
policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.ApiInstallation);
});
config.AddPolicy("Secrets", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireAssertion(ctx => ctx.User.HasClaim(c =>
c.Type == JwtClaimTypes.Scope &&
(c.Value.Contains(ApiScopes.Api) || c.Value.Contains(ApiScopes.ApiSecrets))
));
});
});
@ -125,7 +135,9 @@ public class Startup
#if OSS
services.AddOosServices();
#else
services.AddCommCoreServices();
services.AddCommercialCoreServices();
services.AddCommercialSecretsManagerServices();
services.AddCommercialEFRepositories();
#endif
// MVC

View File

@ -8,8 +8,9 @@ public class SecretsManagerAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var env = context.HttpContext.RequestServices.GetService<IHostEnvironment>();
if (!env.IsDevelopment())
var isDev = context.HttpContext.RequestServices.GetService<IHostEnvironment>().IsDevelopment();
var isEE = Environment.GetEnvironmentVariable("EE_TESTING_ENV") != null;
if (!isDev && !isEE)
{
context.Result = new NotFoundResult();
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Settings;
using Bit.Core.IdentityServer;
using Bit.Core.Settings;
using Microsoft.OpenApi.Models;
namespace Bit.Api.Utilities;
@ -38,7 +39,7 @@ public static class ServiceCollectionExtensions
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
Scopes = new Dictionary<string, string>
{
{ "api.organization", "Organization APIs" },
{ ApiScopes.ApiOrganization, "Organization APIs" },
},
}
},
@ -55,7 +56,7 @@ public static class ServiceCollectionExtensions
Id = "oauth2-client-credentials"
},
},
new[] { "api.organization" }
new[] { ApiScopes.ApiOrganization }
}
});

View File

@ -562,6 +562,15 @@
"Microsoft.Extensions.DependencyModel": "6.0.0"
}
},
"Microsoft.EntityFrameworkCore.SqlServer": {
"type": "Transitive",
"resolved": "6.0.12",
"contentHash": "bdKnSz1w+WZz9QYWhs3wwGuMn4YssjdR+HOBpzChQ6C3+dblq4Pammm5fzugcPOhTgCiWftOT2jPOT5hEy4bYg==",
"dependencies": {
"Microsoft.Data.SqlClient": "2.1.4",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12"
}
},
"Microsoft.Extensions.ApiDescription.Server": {
"type": "Transitive",
"resolved": "3.0.0",
@ -2761,6 +2770,14 @@
"Core": "[2022.12.0, )"
}
},
"commercial.infrastructure.entityframework": {
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[11.0.0, )",
"Core": "[2022.12.0, )",
"Infrastructure.EntityFramework": "[2022.12.0, )"
}
},
"core": {
"type": "Project",
"dependencies": {
@ -2814,6 +2831,7 @@
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[11.0.0, )",
"Core": "[2022.12.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, )",