A api key can always revoke itself, add a route to delete any api key

This commit is contained in:
nicolas.dorier 2020-03-27 14:46:51 +09:00
parent 39a8c3fe47
commit 6d7b57ea3b
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
5 changed files with 69 additions and 43 deletions

View file

@ -27,5 +27,13 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token); var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
HandleResponse(response); HandleResponse(response);
} }
public virtual async Task RevokeAPIKey(string apikey, CancellationToken token = default)
{
if (apikey == null)
throw new ArgumentNullException(nameof(apikey));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
HandleResponse(response);
}
} }
} }

View file

@ -37,7 +37,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount(); var user = tester.NewAccount();
user.GrantAccess(); user.GrantAccess();
await user.MakeAdmin(); await user.MakeAdmin();
var client = await user.CreateClient(Policies.Unrestricted); var client = await user.CreateClient(Policies.CanViewProfile);
var clientBasic = await user.CreateClient(); var clientBasic = await user.CreateClient();
//Get current api key //Get current api key
var apiKeyData = await client.GetCurrentAPIKeyInfo(); var apiKeyData = await client.GetCurrentAPIKeyInfo();
@ -57,7 +57,7 @@ namespace BTCPayServer.Tests
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanCreateAPIKeyViaAPI() public async Task CanCreateAndDeleteAPIKeyViaAPI()
{ {
using (var tester = ServerTester.Create()) using (var tester = ServerTester.Create())
{ {
@ -80,6 +80,9 @@ namespace BTCPayServer.Tests
Label = "Hello world2", Label = "Hello world2",
Permissions = new Permission[] { Permission.Create(Policies.CanViewProfile) } Permissions = new Permission[] { Permission.Create(Policies.CanViewProfile) }
})); }));
await unrestricted.RevokeAPIKey(apiKey.ApiKey);
await AssertHttpError(404, async () => await unrestricted.RevokeAPIKey(apiKey.ApiKey));
} }
} }

View file

@ -56,15 +56,26 @@ namespace BTCPayServer.Controllers.GreenField
} }
[HttpDelete("~/api/v1/api-keys/current")] [HttpDelete("~/api/v1/api-keys/current")]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)]
public async Task<IActionResult> RevokeKey() public Task<IActionResult> RevokeCurrentKey()
{ {
if (!ControllerContext.HttpContext.GetAPIKey(out var apiKey)) if (!ControllerContext.HttpContext.GetAPIKey(out var apiKey))
{ {
return NotFound(); // Should be impossible (we force apikey auth)
return Task.FromResult<IActionResult>(BadRequest());
} }
await _apiKeyRepository.Remove(apiKey, _userManager.GetUserId(User)); return RevokeKey(apiKey);
}
[HttpDelete("~/api/v1/api-keys/{apikey}", Order = 1)]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> RevokeKey(string apikey)
{
if (string.IsNullOrEmpty(apikey))
return BadRequest();
if (await _apiKeyRepository.Remove(apikey, _userManager.GetUserId(User)))
return Ok(); return Ok();
else
return NotFound();
} }
private static ApiKeyData FromModel(APIKeyData data) private static ApiKeyData FromModel(APIKeyData data)

View file

@ -53,15 +53,18 @@ namespace BTCPayServer.Security.GreenField
} }
} }
public async Task Remove(string id, string getUserId) public async Task<bool> Remove(string id, string getUserId)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using (var context = _applicationDbContextFactory.CreateContext())
{ {
var key = await EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(context.ApiKeys, var key = await EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(context.ApiKeys,
data => data.Id == id && data.UserId == getUserId); data => data.Id == id && data.UserId == getUserId);
if (key == null)
return false;
context.ApiKeys.Remove(key); context.ApiKeys.Remove(key);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
return true;
} }
public class APIKeyQuery public class APIKeyQuery

View file

@ -188,23 +188,48 @@
] ]
} }
}, },
"/api/v1/api-keys/{apikey}": {
"delete": {
"tags": [
"API Keys"
],
"summary": "Revoke an API Key",
"description": "Revoke the current API key so that it cannot be used anymore",
"parameters": [
{
"name": "apikey",
"in": "path",
"required": true,
"description": "The API Key to revoke",
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "The key has been deleted"
},
"404": {
"description": "The key is not found for this user"
}
},
"security": [
{
"API Key": [ "unrestricted" ],
"Basic": []
}
]
}
},
"/api/v1/api-keys/current": { "/api/v1/api-keys/current": {
"get": { "get": {
"tags": [ "tags": [
"API Keys" "API Keys"
], ],
"summary": "Get current API Key information", "summary": "Get the current API Key information",
"description": "View information about the current API key", "description": "View information about the current API key",
"responses": { "responses": {
"200": { "200": {
"description": "Information about the current api key", "description": "The key has been deleted"
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiKeyData"
}
}
}
} }
}, },
"security": [ "security": [
@ -233,7 +258,7 @@
}, },
"security": [ "security": [
{ {
"API Key": [ "unrestricted" ] "API Key": []
} }
] ]
} }
@ -289,30 +314,6 @@
"Basic": [] "Basic": []
} }
] ]
},
"delete": {
"tags": [
"API Keys"
],
"summary": "Revoke the current API Key",
"description": "Revoke the current API key so that it cannot be used anymore",
"responses": {
"200": {
"description": "The key was revoked and is no longer usable",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiKeyData"
}
}
}
}
},
"security": [
{
"API Key": [ "unrestricted" ]
}
]
} }
} }
}, },