GreenField: Create API Key

This commit is contained in:
nicolas.dorier 2020-03-27 14:17:31 +09:00
parent 927c09ff7b
commit 39a8c3fe47
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
6 changed files with 143 additions and 4 deletions

View file

@ -1,3 +1,4 @@
using System;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,7 +13,15 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token); var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token);
return await HandleResponse<ApiKeyData>(response); return await HandleResponse<ApiKeyData>(response);
} }
public virtual async Task<ApiKeyData> CreateAPIKey(CreateApiKeyRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default) public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
{ {
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);

View file

@ -55,6 +55,33 @@ namespace BTCPayServer.Tests
await AssertHttpError(401, async () => await clientBasic.RevokeCurrentAPIKeyInfo()); await AssertHttpError(401, async () => await clientBasic.RevokeCurrentAPIKeyInfo());
} }
} }
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateAPIKeyViaAPI()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var acc = tester.NewAccount();
await acc.GrantAccessAsync();
var unrestricted = await acc.CreateClient();
var apiKey = await unrestricted.CreateAPIKey(new CreateApiKeyRequest()
{
Label = "Hello world",
Permissions = new Permission[] { Permission.Create(Policies.CanViewProfile) }
});
Assert.Equal("Hello world", apiKey.Label);
var p = Assert.Single(apiKey.Permissions);
Assert.Equal(Policies.CanViewProfile, p.Policy);
var restricted = acc.CreateClientFromAPIKey(apiKey.ApiKey);
await AssertHttpError(403, async () => await restricted.CreateAPIKey(new CreateApiKeyRequest()
{
Label = "Hello world2",
Permissions = new Permission[] { Permission.Create(Policies.CanViewProfile) }
}));
}
}
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]

View file

@ -87,6 +87,12 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString())); Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId); await store.Pair(pairingCode.ToString(), StoreId);
} }
public BTCPayServerClient CreateClientFromAPIKey(string apiKey)
{
return new BTCPayServerClient(parent.PayTester.ServerUri, apiKey);
}
public void CreateStore() public void CreateStore()
{ {
CreateStoreAsync().GetAwaiter().GetResult(); CreateStoreAsync().GetAwaiter().GetResult();

View file

@ -223,5 +223,5 @@
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" /> <_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
</ItemGroup> </ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions> <ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
</Project> </Project>

View file

@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using BTCPayServer.Security.GreenField; using BTCPayServer.Security.GreenField;
using NBitcoin.DataEncoders;
using NBitcoin;
namespace BTCPayServer.Controllers.GreenField namespace BTCPayServer.Controllers.GreenField
{ {
@ -35,9 +37,27 @@ namespace BTCPayServer.Controllers.GreenField
return Ok(FromModel(data)); return Ok(FromModel(data));
} }
[HttpPost("~/api/v1/api-keys")]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<ActionResult<ApiKeyData>> CreateKey(CreateApiKeyRequest request)
{
if (request is null)
return BadRequest();
var key = new APIKeyData()
{
Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)),
Type = APIKeyType.Permanent,
UserId = _userManager.GetUserId(User),
Label = request.Label
};
key.Permissions = string.Join(";", request.Permissions.Select(p => p.ToString()).Distinct().ToArray());
await _apiKeyRepository.CreateKey(key);
return Ok(FromModel(key));
}
[HttpDelete("~/api/v1/api-keys/current")] [HttpDelete("~/api/v1/api-keys/current")]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)] [Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)]
public async Task<ActionResult<ApiKeyData>> RevokeKey() public async Task<IActionResult> RevokeKey()
{ {
if (!ControllerContext.HttpContext.GetAPIKey(out var apiKey)) if (!ControllerContext.HttpContext.GetAPIKey(out var apiKey))
{ {

View file

@ -77,7 +77,7 @@
} }
} }
}, },
"security": [ ] "security": []
} }
}, },
"/api/v1/users/me": { "/api/v1/users/me": {
@ -237,6 +237,83 @@
} }
] ]
} }
},
"/api/v1/api-keys": {
"post": {
"tags": [
"API Keys"
],
"summary": "Create a new API Key",
"description": "Create a new API Key",
"responses": {
"200": {
"description": "Information about the new api key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiKeyData"
}
}
}
}
},
"requestBody": {
"x-name": "request",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"label": {
"type": "string",
"description": "The label of the new API Key",
"nullable": true
},
"permissions": {
"type": "array",
"description": "The permissions granted to this API Key (See API Key Authentication)",
"nullable": true,
"items": {
"type": "string"
}
}
}
}
}
}
},
"security": [
{
"API Key": [ "unrestricted" ],
"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" ]
}
]
}
} }
}, },
"components": { "components": {