GreenField: Switch to Blob for API Keys

This commit is contained in:
Kukks 2020-04-02 08:59:20 +02:00
parent d60b00e8cd
commit c6d75de3d7
11 changed files with 101 additions and 26 deletions

View file

@ -123,14 +123,6 @@ namespace BTCPayServer.Client
yield return pp;
}
}
public static IEnumerable<Permission> ToPermissions(string permissionsFormatted)
{
foreach(var part in permissionsFormatted.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
if (Permission.TryParse(part, out var p))
yield return p;
}
}
private bool ContainsPolicy(string subpolicy)
{

View file

@ -22,13 +22,19 @@ namespace BTCPayServer.Data
[MaxLength(50)] public string UserId { get; set; }
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
public string Permissions { get; set; }
public byte[] Blob { get; set; }
public StoreData StoreData { get; set; }
public ApplicationUser User { get; set; }
public string Label { get; set; }
}
public class APIKeyBlob
{
public string[] Permissions { get; set; }
}
public enum APIKeyType
{
Legacy,

View file

@ -0,0 +1,42 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20200402065615_AddApiKeyBlob")]
public partial class AddApiKeyBlob : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
{
migrationBuilder.DropColumn(
name: "Permissions",
table: "ApiKeys");
}
migrationBuilder.AddColumn<byte[]>(
name: "Blob",
table: "ApiKeys",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
{
migrationBuilder.DropColumn(
name: "Blob",
table: "ApiKeys");
}
migrationBuilder.AddColumn<string>(
name: "Permissions",
table: "ApiKeys",
type: "TEXT",
nullable: true);
}
}
}

View file

@ -22,10 +22,10 @@ namespace BTCPayServer.Migrations
.HasColumnType("TEXT")
.HasMaxLength(50);
b.Property<string>("Label")
.HasColumnType("TEXT");
b.Property<byte[]>("Blob")
.HasColumnType("BLOB");
b.Property<string>("Permissions")
b.Property<string>("Label")
.HasColumnType("TEXT");
b.Property<string>("StoreId")

View file

@ -67,7 +67,7 @@ namespace BTCPayServer.Tests
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
//this api key has access to everything
await TestApiAgainstAccessToken(superApiKey, tester, user, $"{Policies.CanModifyServerSettings};{Policies.CanModifyStoreSettings};{Policies.CanViewProfile}");
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings,Policies.CanModifyStoreSettings, Policies.CanViewProfile);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
@ -100,7 +100,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var noPermissionsApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user, string.Empty);
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
@ -133,7 +133,7 @@ namespace BTCPayServer.Tests
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions);
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetBlob().Permissions);
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true).ToString();
@ -154,15 +154,15 @@ namespace BTCPayServer.Tests
.Select(s1 => new KeyValuePair<string, string>(s1.Split("=")[0], s1.Split("=")[1]));
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions);
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).GetBlob().Permissions);
}
}
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount,
string expectedPermissionsString)
params string[] expectedPermissionsArr)
{
var expectedPermissions = Permission.ToPermissions(expectedPermissionsString).ToArray();
var expectedPermissions = Permission.ToPermissions(expectedPermissionsArr).ToArray();
expectedPermissions ??= new Permission[0];
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
var permissions = apikeydata.Permissions;

View file

@ -50,7 +50,10 @@ namespace BTCPayServer.Controllers.GreenField
UserId = _userManager.GetUserId(User),
Label = request.Label
};
key.Permissions = string.Join(";", request.Permissions.Select(p => p.ToString()).Distinct().ToArray());
key.SetBlob(new APIKeyBlob()
{
Permissions = request.Permissions.Select(p => p.ToString()).Distinct().ToArray()
});
await _apiKeyRepository.CreateKey(key);
return Ok(FromModel(key));
}
@ -82,7 +85,7 @@ namespace BTCPayServer.Controllers.GreenField
{
return new ApiKeyData()
{
Permissions = Permission.ToPermissions(data.Permissions).ToArray(),
Permissions = Permission.ToPermissions(data.GetBlob().Permissions).ToArray(),
ApiKey = data.Id,
Label = data.Label ?? string.Empty
};

View file

@ -129,7 +129,7 @@ namespace BTCPayServer.Controllers
}
}
var permissions = Permission.ToPermissions(viewModel.Permissions).ToHashSet();
var permissions = Permission.ToPermissions(viewModel.Permissions.Split(';')).ToHashSet();
if (permissions.Contains(Permission.Create(Policies.CanModifyStoreSettings)))
{
if (!viewModel.SelectiveStores &&
@ -238,7 +238,10 @@ namespace BTCPayServer.Controllers
UserId = _userManager.GetUserId(User),
Label = viewModel.Label
};
key.Permissions = string.Join(";", GetPermissionsFromViewModel(viewModel).Select(p => p.ToString()).Distinct().ToArray());
key.SetBlob(new APIKeyBlob()
{
Permissions = GetPermissionsFromViewModel(viewModel).Select(p => p.ToString()).Distinct().ToArray()
});
await _apiKeyRepository.CreateKey(key);
return key;
}

View file

@ -0,0 +1,26 @@
using NBXplorer;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
public static class APIKeyDataExtensions
{
public static APIKeyBlob GetBlob(this APIKeyData apiKeyData)
{
var result = apiKeyData.Blob == null
? new APIKeyBlob()
: JObject.Parse(ZipUtils.Unzip(apiKeyData.Blob)).ToObject<APIKeyBlob>();
return result;
}
public static bool SetBlob(this APIKeyData apiKeyData, APIKeyBlob blob)
{
var original = new Serializer(null).ToString(apiKeyData.GetBlob());
var newBlob = new Serializer(null).ToString(blob);
if (original == newBlob)
return false;
apiKeyData.Blob = ZipUtils.Zip(newBlob);
return true;
}
}
}

View file

@ -52,7 +52,7 @@ namespace BTCPayServer.Security.GreenField
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, key.UserId));
claims.AddRange(Permission.ToPermissions(key.Permissions).Select(permission =>
claims.AddRange(Permission.ToPermissions(key.GetBlob().Permissions).Select(permission =>
new Claim(GreenFieldConstants.ClaimTypes.Permission, permission.ToString())));
return AuthenticateResult.Success(new AuthenticationTicket(
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),

View file

@ -22,13 +22,16 @@
<td>@keyData.Label</td>
<td>@keyData.Id</td>
<td>
@if (string.IsNullOrEmpty(keyData.Permissions))
@{
var permissions = keyData.GetBlob().Permissions;
}
@if (!permissions.Any())
{
<span>No permissions</span>
}
else
{
<span>@string.Join(", ", Permission.ToPermissions(keyData.Permissions).Select(c => c.ToString()).Distinct().ToArray())</span>
<span>@string.Join(", ", Permission.ToPermissions(permissions).Select(c => c.ToString()).Distinct().ToArray())</span>
}
</td>
<td class="text-right">

View file

@ -6,7 +6,7 @@
@{
Layout = "_Layout";
ViewData["Title"] = $"Authorize {(Model.ApplicationName ?? "Application")}";
var permissions = Permission.ToPermissions(Model.Permissions);
var permissions = Permission.ToPermissions(Model.Permissions.Split(';'));
var hasStorePermission = permissions.Any(p => p.Policy == Policies.CanModifyStoreSettings);
}