mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
GreenField: Switch to Blob for API Keys
This commit is contained in:
@@ -123,14 +123,6 @@ namespace BTCPayServer.Client
|
|||||||
yield return pp;
|
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)
|
private bool ContainsPolicy(string subpolicy)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,13 +22,19 @@ namespace BTCPayServer.Data
|
|||||||
[MaxLength(50)] public string UserId { get; set; }
|
[MaxLength(50)] public string UserId { get; set; }
|
||||||
|
|
||||||
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
|
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
|
||||||
public string Permissions { get; set; }
|
|
||||||
|
|
||||||
|
public byte[] Blob { get; set; }
|
||||||
public StoreData StoreData { get; set; }
|
public StoreData StoreData { get; set; }
|
||||||
public ApplicationUser User { get; set; }
|
public ApplicationUser User { get; set; }
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class APIKeyBlob
|
||||||
|
{
|
||||||
|
public string[] Permissions { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public enum APIKeyType
|
public enum APIKeyType
|
||||||
{
|
{
|
||||||
Legacy,
|
Legacy,
|
||||||
|
|||||||
42
BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs
Normal file
42
BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,10 +22,10 @@ namespace BTCPayServer.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasMaxLength(50);
|
.HasMaxLength(50);
|
||||||
|
|
||||||
b.Property<string>("Label")
|
b.Property<byte[]>("Blob")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("Permissions")
|
b.Property<string>("Label")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("StoreId")
|
b.Property<string>("StoreId")
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace BTCPayServer.Tests
|
|||||||
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||||
|
|
||||||
//this api key has access to everything
|
//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();
|
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("AddApiKey")).Click();
|
||||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||||
var noPermissionsApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
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 () =>
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
{
|
{
|
||||||
@@ -133,7 +133,7 @@ namespace BTCPayServer.Tests
|
|||||||
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
|
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
|
||||||
|
|
||||||
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
|
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,
|
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||||
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true).ToString();
|
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]));
|
.Select(s1 => new KeyValuePair<string, string>(s1.Split("=")[0], s1.Split("=")[1]));
|
||||||
|
|
||||||
await TestApiAgainstAccessToken(results.Single(pair => pair.Key == "key").Value, tester, user,
|
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,
|
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];
|
expectedPermissions ??= new Permission[0];
|
||||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||||
var permissions = apikeydata.Permissions;
|
var permissions = apikeydata.Permissions;
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
UserId = _userManager.GetUserId(User),
|
UserId = _userManager.GetUserId(User),
|
||||||
Label = request.Label
|
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);
|
await _apiKeyRepository.CreateKey(key);
|
||||||
return Ok(FromModel(key));
|
return Ok(FromModel(key));
|
||||||
}
|
}
|
||||||
@@ -82,7 +85,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
{
|
{
|
||||||
return new ApiKeyData()
|
return new ApiKeyData()
|
||||||
{
|
{
|
||||||
Permissions = Permission.ToPermissions(data.Permissions).ToArray(),
|
Permissions = Permission.ToPermissions(data.GetBlob().Permissions).ToArray(),
|
||||||
ApiKey = data.Id,
|
ApiKey = data.Id,
|
||||||
Label = data.Label ?? string.Empty
|
Label = data.Label ?? string.Empty
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 (permissions.Contains(Permission.Create(Policies.CanModifyStoreSettings)))
|
||||||
{
|
{
|
||||||
if (!viewModel.SelectiveStores &&
|
if (!viewModel.SelectiveStores &&
|
||||||
@@ -238,7 +238,10 @@ namespace BTCPayServer.Controllers
|
|||||||
UserId = _userManager.GetUserId(User),
|
UserId = _userManager.GetUserId(User),
|
||||||
Label = viewModel.Label
|
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);
|
await _apiKeyRepository.CreateKey(key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|||||||
26
BTCPayServer/Data/APIKeyDataExtensions.cs
Normal file
26
BTCPayServer/Data/APIKeyDataExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ namespace BTCPayServer.Security.GreenField
|
|||||||
|
|
||||||
List<Claim> claims = new List<Claim>();
|
List<Claim> claims = new List<Claim>();
|
||||||
claims.Add(new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, key.UserId));
|
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())));
|
new Claim(GreenFieldConstants.ClaimTypes.Permission, permission.ToString())));
|
||||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
||||||
|
|||||||
@@ -22,13 +22,16 @@
|
|||||||
<td>@keyData.Label</td>
|
<td>@keyData.Label</td>
|
||||||
<td>@keyData.Id</td>
|
<td>@keyData.Id</td>
|
||||||
<td>
|
<td>
|
||||||
@if (string.IsNullOrEmpty(keyData.Permissions))
|
@{
|
||||||
|
var permissions = keyData.GetBlob().Permissions;
|
||||||
|
}
|
||||||
|
@if (!permissions.Any())
|
||||||
{
|
{
|
||||||
<span>No permissions</span>
|
<span>No permissions</span>
|
||||||
}
|
}
|
||||||
else
|
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>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
@{
|
@{
|
||||||
Layout = "_Layout";
|
Layout = "_Layout";
|
||||||
ViewData["Title"] = $"Authorize {(Model.ApplicationName ?? "Application")}";
|
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);
|
var hasStorePermission = permissions.Any(p => p.Policy == Policies.CanModifyStoreSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user