diff --git a/BTCPayServer.Client/Permissions.cs b/BTCPayServer.Client/Permissions.cs index 88adc5389..2acaf0d0d 100644 --- a/BTCPayServer.Client/Permissions.cs +++ b/BTCPayServer.Client/Permissions.cs @@ -123,14 +123,6 @@ namespace BTCPayServer.Client yield return pp; } } - public static IEnumerable 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) { diff --git a/BTCPayServer.Data/Data/APIKeyData.cs b/BTCPayServer.Data/Data/APIKeyData.cs index 00722dcdd..129611e84 100644 --- a/BTCPayServer.Data/Data/APIKeyData.cs +++ b/BTCPayServer.Data/Data/APIKeyData.cs @@ -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, diff --git a/BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs b/BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs new file mode 100644 index 000000000..5b901f003 --- /dev/null +++ b/BTCPayServer.Data/Migrations/20200402065615_AddApiKeyBlob.cs @@ -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( + 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( + name: "Permissions", + table: "ApiKeys", + type: "TEXT", + nullable: true); + } + } +} diff --git a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs index 3ee8f2a07..f27375070 100644 --- a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,10 +22,10 @@ namespace BTCPayServer.Migrations .HasColumnType("TEXT") .HasMaxLength(50); - b.Property("Label") - .HasColumnType("TEXT"); + b.Property("Blob") + .HasColumnType("BLOB"); - b.Property("Permissions") + b.Property("Label") .HasColumnType("TEXT"); b.Property("StoreId") diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs index 28c226279..1b42a93de 100644 --- a/BTCPayServer.Tests/ApiKeysTests.cs +++ b/BTCPayServer.Tests/ApiKeysTests.cs @@ -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(async () => { @@ -133,7 +133,7 @@ namespace BTCPayServer.Tests var apiKeyRepo = s.Server.PayTester.GetService(); 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(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(accessToken, $"api/v1/api-keys/current", tester.PayTester.HttpClient); var permissions = apikeydata.Permissions; diff --git a/BTCPayServer/Controllers/GreenField/ApiKeysController.cs b/BTCPayServer/Controllers/GreenField/ApiKeysController.cs index b05af629d..b50efe9ba 100644 --- a/BTCPayServer/Controllers/GreenField/ApiKeysController.cs +++ b/BTCPayServer/Controllers/GreenField/ApiKeysController.cs @@ -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 }; diff --git a/BTCPayServer/Controllers/ManageController.APIKeys.cs b/BTCPayServer/Controllers/ManageController.APIKeys.cs index c0f6def40..ede4732b9 100644 --- a/BTCPayServer/Controllers/ManageController.APIKeys.cs +++ b/BTCPayServer/Controllers/ManageController.APIKeys.cs @@ -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; } diff --git a/BTCPayServer/Data/APIKeyDataExtensions.cs b/BTCPayServer/Data/APIKeyDataExtensions.cs new file mode 100644 index 000000000..101ac117d --- /dev/null +++ b/BTCPayServer/Data/APIKeyDataExtensions.cs @@ -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(); + 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; + } + } +} diff --git a/BTCPayServer/Security/GreenField/APIKeysAuthenticationHandler.cs b/BTCPayServer/Security/GreenField/APIKeysAuthenticationHandler.cs index ec30b1c13..f6b212558 100644 --- a/BTCPayServer/Security/GreenField/APIKeysAuthenticationHandler.cs +++ b/BTCPayServer/Security/GreenField/APIKeysAuthenticationHandler.cs @@ -52,7 +52,7 @@ namespace BTCPayServer.Security.GreenField List claims = new List(); 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)), diff --git a/BTCPayServer/Views/Manage/APIKeys.cshtml b/BTCPayServer/Views/Manage/APIKeys.cshtml index 2ff26577e..ff8caa7e1 100644 --- a/BTCPayServer/Views/Manage/APIKeys.cshtml +++ b/BTCPayServer/Views/Manage/APIKeys.cshtml @@ -22,13 +22,16 @@ @keyData.Label @keyData.Id - @if (string.IsNullOrEmpty(keyData.Permissions)) + @{ + var permissions = keyData.GetBlob().Permissions; + } + @if (!permissions.Any()) { No permissions } else { - @string.Join(", ", Permission.ToPermissions(keyData.Permissions).Select(c => c.ToString()).Distinct().ToArray()) + @string.Join(", ", Permission.ToPermissions(permissions).Select(c => c.ToString()).Distinct().ToArray()) } diff --git a/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml b/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml index 105df24b2..5217d7482 100644 --- a/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml +++ b/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml @@ -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); }