1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

[SM-501] Add support for revoking access tokens (#2692)

* Add support for revoking access tokens
This commit is contained in:
Oscar Hinton
2023-02-16 10:51:02 +01:00
committed by GitHub
parent e6635ff590
commit 7a209aa3bb
13 changed files with 315 additions and 5 deletions

View File

@ -19,20 +19,23 @@ namespace Bit.Api.SecretsManager.Controllers;
public class ServiceAccountsController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IApiKeyRepository _apiKeyRepository;
private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
private readonly ICreateServiceAccountCommand _createServiceAccountCommand;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
private readonly IUserService _userService;
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
public ServiceAccountsController(
ICurrentContext currentContext,
IUserService userService,
IServiceAccountRepository serviceAccountRepository,
IApiKeyRepository apiKeyRepository,
ICreateAccessTokenCommand createAccessTokenCommand,
IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand)
ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand,
IRevokeAccessTokensCommand revokeAccessTokensCommand)
{
_currentContext = currentContext;
_userService = userService;
@ -40,6 +43,7 @@ public class ServiceAccountsController : Controller
_apiKeyRepository = apiKeyRepository;
_createServiceAccountCommand = createServiceAccountCommand;
_updateServiceAccountCommand = updateServiceAccountCommand;
_revokeAccessTokensCommand = revokeAccessTokensCommand;
_createAccessTokenCommand = createAccessTokenCommand;
}
@ -129,4 +133,37 @@ public class ServiceAccountsController : Controller
var result = await _createAccessTokenCommand.CreateAsync(request.ToApiKey(id), userId);
return new AccessTokenCreationResponseModel(result);
}
[HttpPost("{id}/access-tokens/revoke")]
public async Task RevokeAccessTokensAsync(Guid id, [FromBody] RevokeAccessTokensRequest request)
{
var userId = _userService.GetProperUserId(User).Value;
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
await _revokeAccessTokensCommand.RevokeAsync(serviceAccount, request.Ids);
}
}

View File

@ -0,0 +1,7 @@
using System.ComponentModel.DataAnnotations;
public class RevokeAccessTokensRequest
{
[Required]
public Guid[] Ids { get; set; }
}

View File

@ -0,0 +1,8 @@
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
public interface IRevokeAccessTokensCommand
{
Task RevokeAsync(ServiceAccount serviceAccount, IEnumerable<Guid> ids);
}

View File

@ -8,4 +8,5 @@ public interface IApiKeyRepository : IRepository<ApiKey, Guid>
{
Task<ApiKeyDetails> GetDetailsByIdAsync(Guid id);
Task<ICollection<ApiKey>> GetManyByServiceAccountIdAsync(Guid id);
Task DeleteManyAsync(IEnumerable<ApiKey> objs);
}

View File

@ -42,4 +42,13 @@ public class ApiKeyRepository : Repository<ApiKey, Guid>, IApiKeyRepository
return results.ToList();
}
public async Task DeleteManyAsync(IEnumerable<ApiKey> objs)
{
using var connection = new SqlConnection(ConnectionString);
await connection.QueryAsync<ApiKey>(
$"[{Schema}].[ApiKey_DeleteByIds]",
new { Ids = objs.Select(obj => obj.Id).ToGuidIdArrayTVP() },
commandType: CommandType.StoredProcedure);
}
}

View File

@ -36,4 +36,13 @@ public class ApiKeyRepository : Repository<Core.SecretsManager.Entities.ApiKey,
return Mapper.Map<List<Core.SecretsManager.Entities.ApiKey>>(apiKeys);
}
public async Task DeleteManyAsync(IEnumerable<Core.SecretsManager.Entities.ApiKey> objs)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = objs.Select(obj => Mapper.Map<ApiKey>(obj));
dbContext.RemoveRange(entities);
await dbContext.SaveChangesAsync();
}
}

View File

@ -0,0 +1,23 @@
CREATE PROCEDURE [dbo].[ApiKey_DeleteByIds]
@Ids [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @BatchSize INT = 100
WHILE @BatchSize > 0
BEGIN
BEGIN TRANSACTION ApiKey_DeleteMany
DELETE TOP(@BatchSize) AK
FROM
[dbo].[ApiKey] AK
INNER JOIN
@Ids I ON I.Id = AK.Id
SET @BatchSize = @@ROWCOUNT
COMMIT TRANSACTION ApiKey_DeleteMany
END
END

View File

@ -444,6 +444,7 @@
<Build Include="dbo\Views\UserView.sql" />
<Build Include="SecretsManager\dbo\Stored Procedures\ApiKey\ApiKeyDetails_ReadById.sql" />
<Build Include="SecretsManager\dbo\Stored Procedures\ApiKey\ApiKey_Create.sql" />
<Build Include="SecretsManager\dbo\Stored Procedures\ApiKey\ApiKey_DeleteByIds.sql" />
<Build Include="SecretsManager\dbo\Stored Procedures\ApiKey\ApiKey_ReadByServiceAccountId.sql" />
<Build Include="SecretsManager\dbo\Tables\AccessPolicy.sql" />
<Build Include="SecretsManager\dbo\Tables\ApiKey.sql" />