From 29a807696b8863f5ec109d05b845c81c8928cb71 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Thu, 19 Mar 2020 19:11:15 +0900 Subject: [PATCH] Refactor permissions of GreenField --- BTCPayServer.Client/Permissions.cs | 186 ++++++++++++++-- BTCPayServer.Data/Data/APIKeyData.cs | 7 - BTCPayServer.Tests/ApiKeysTests.cs | 125 ++++++----- BTCPayServer.Tests/GreenfieldAPITests.cs | 15 +- BTCPayServer.Tests/SeleniumTester.cs | 6 +- BTCPayServer.Tests/TestAccount.cs | 7 +- BTCPayServer.Tests/UnitTest1.cs | 17 ++ BTCPayServer/BTCPayServer.csproj | 2 +- .../Controllers/InvoiceController.API.cs | 3 +- .../Controllers/InvoiceController.UI.cs | 3 +- .../Controllers/ManageController.APIKeys.cs | 202 ++++++++++++------ .../RestApi/ApiKeys/ApiKeysController.cs | 4 +- .../RestApi/TestApiKeyController.cs | 25 +-- .../RestApi/Users/UsersController.cs | 12 +- BTCPayServer/Controllers/ServerController.cs | 3 +- .../Controllers/StoresController.BTCLike.cs | 3 +- BTCPayServer/Controllers/StoresController.cs | 3 +- BTCPayServer/Controllers/VaultController.cs | 3 +- BTCPayServer/Controllers/WalletsController.cs | 9 +- BTCPayServer/Extensions.cs | 9 + .../APIKeys/APIKeyAuthenticationHandler.cs | 6 +- .../APIKeys/APIKeyAuthorizationHandler.cs | 83 ++++--- .../Security/APIKeys/APIKeyConstants.cs | 15 +- .../Security/APIKeys/APIKeyExtensions.cs | 35 ++- .../Bitpay/BitpayAuthorizationHandler.cs | 3 +- .../Security/CookieAuthorizationHandler.cs | 7 +- BTCPayServer/Security/Policies.cs | 41 +--- .../Monero/UI/MoneroLikeStoreController.cs | 5 +- BTCPayServer/Views/Manage/APIKeys.cshtml | 3 +- BTCPayServer/Views/Manage/AddApiKey.cshtml | 54 ++--- .../Views/Manage/AuthorizeAPIKey.cshtml | 89 +++----- 31 files changed, 581 insertions(+), 404 deletions(-) diff --git a/BTCPayServer.Client/Permissions.cs b/BTCPayServer.Client/Permissions.cs index ef26d83f5..de225cfc2 100644 --- a/BTCPayServer.Client/Permissions.cs +++ b/BTCPayServer.Client/Permissions.cs @@ -5,25 +5,183 @@ using System.Text.Json.Serialization; namespace BTCPayServer.Client { - public static class Permissions + public class Permission { - public const string ServerManagement = nameof(ServerManagement); - public const string StoreManagement = nameof(StoreManagement); - public const string ProfileManagement = nameof(ProfileManagement); + public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings"; + public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings"; + public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings"; + public const string CanCreateInvoice = "btcpay.store.cancreateinvoice"; + public const string CanModifyProfile = "btcpay.user.canmodifyprofile"; + public const string CanViewProfile = "btcpay.user.canviewprofile"; + public const string CanCreateUser = "btcpay.server.cancreateuser"; + public const string Unrestricted = "unrestricted"; - public static string[] GetAllPermissionKeys() + public static IEnumerable AllPolicies { - return new[] + get { - ServerManagement, - StoreManagement, - ProfileManagement - }; + yield return CanCreateInvoice; + yield return CanModifyServerSettings; + yield return CanModifyStoreSettings; + yield return CanViewStoreSettings; + yield return CanModifyProfile; + yield return CanViewProfile; + yield return CanCreateUser; + yield return Unrestricted; + } } - public static string GetStorePermission(string storeId) => $"{nameof(StoreManagement)}:{storeId}"; - public static IEnumerable ExtractStorePermissionsIds(IEnumerable permissions) => permissions - .Where(s => s.StartsWith($"{nameof(StoreManagement)}:", StringComparison.InvariantCulture)) - .Select(s => s.Split(":")[1]); + public static Permission Create(string policy, string storeId = null) + { + if (TryCreatePermission(policy, storeId, out var r)) + return r; + throw new ArgumentException("Invalid Permission"); + } + + public static bool TryCreatePermission(string policy, string storeId, out Permission permission) + { + permission = null; + if (policy == null) + throw new ArgumentNullException(nameof(policy)); + policy = policy.Trim().ToLowerInvariant(); + if (!IsValidPolicy(policy)) + return false; + if (storeId != null && !IsStorePolicy(policy)) + return false; + permission = new Permission(policy, storeId); + return true; + } + + public static bool TryParse(string str, out Permission permission) + { + permission = null; + if (str == null) + throw new ArgumentNullException(nameof(str)); + str = str.Trim(); + var separator = str.IndexOf(':'); + if (separator == -1) + { + str = str.ToLowerInvariant(); + if (!IsValidPolicy(str)) + return false; + permission = new Permission(str, null); + return true; + } + else + { + var policy = str.Substring(0, separator).ToLowerInvariant(); + if (!IsValidPolicy(policy)) + return false; + if (!IsStorePolicy(policy)) + return false; + var storeId = str.Substring(separator + 1); + if (storeId.Length == 0) + return false; + permission = new Permission(policy, storeId); + return true; + } + } + + private static bool IsValidPolicy(string policy) + { + return AllPolicies.Any(p => p.Equals(policy, StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsStorePolicy(string policy) + { + return policy.StartsWith("btcpay.store", StringComparison.OrdinalIgnoreCase); + } + + internal Permission(string policy, string storeId) + { + Policy = policy; + StoreId = storeId; + } + + public bool Contains(Permission subpermission) + { + if (subpermission is null) + throw new ArgumentNullException(nameof(subpermission)); + + if (!ContainsPolicy(subpermission.Policy)) + { + return false; + } + if (!IsStorePolicy(subpermission.Policy)) + return true; + return StoreId == null || subpermission.StoreId == this.StoreId; + } + + public static IEnumerable ToPermissions(string[] permissions) + { + if (permissions == null) + throw new ArgumentNullException(nameof(permissions)); + foreach (var p in permissions) + { + if (TryParse(p, out var pp)) + 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) + { + if (this.Policy == Unrestricted) + return true; + if (this.Policy == subpolicy) + return true; + if (subpolicy == CanViewStoreSettings && this.Policy == CanModifyStoreSettings) + return true; + if (subpolicy == CanCreateInvoice && this.Policy == CanModifyStoreSettings) + return true; + if (subpolicy == CanViewProfile && this.Policy == CanModifyProfile) + return true; + return false; + } + + public string StoreId { get; } + public string Policy { get; } + + public override string ToString() + { + if (StoreId != null) + { + return $"{Policy}:{StoreId}"; + } + return Policy; + } + + public override bool Equals(object obj) + { + Permission item = obj as Permission; + if (item == null) + return false; + return ToString().Equals(item.ToString()); + } + public static bool operator ==(Permission a, Permission b) + { + if (System.Object.ReferenceEquals(a, b)) + return true; + if (((object)a == null) || ((object)b == null)) + return false; + return a.ToString() == b.ToString(); + } + + public static bool operator !=(Permission a, Permission b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } } } diff --git a/BTCPayServer.Data/Data/APIKeyData.cs b/BTCPayServer.Data/Data/APIKeyData.cs index c538a9bd9..00722dcdd 100644 --- a/BTCPayServer.Data/Data/APIKeyData.cs +++ b/BTCPayServer.Data/Data/APIKeyData.cs @@ -27,13 +27,6 @@ namespace BTCPayServer.Data public StoreData StoreData { get; set; } public ApplicationUser User { get; set; } public string Label { get; set; } - public string[] GetPermissions() { return Permissions?.Split(';') ?? new string[0]; } - - public void SetPermissions(IEnumerable permissions) - { - Permissions = string.Join(';', - permissions?.Select(s => s.Replace(";", string.Empty)) ?? new string[0]); - } } public enum APIKeyType diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs index 419fb2a60..e4917fa73 100644 --- a/BTCPayServer.Tests/ApiKeysTests.cs +++ b/BTCPayServer.Tests/ApiKeysTests.cs @@ -42,49 +42,46 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); - - await user.CreateStoreAsync(); + await user.MakeAdmin(false); s.GoToLogin(); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); s.GoToProfile(ManageNavPages.APIKeys); s.Driver.FindElement(By.Id("AddApiKey")).Click(); - if (!user.IsAdmin) - { - //not an admin, so this permission should not show - Assert.DoesNotContain("ServerManagementPermission", s.Driver.PageSource); - await user.MakeAdmin(); - s.Logout(); - s.GoToLogin(); - s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); - s.GoToProfile(ManageNavPages.APIKeys); - s.Driver.FindElement(By.Id("AddApiKey")).Click(); - } + + //not an admin, so this permission should not show + Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource); + await user.MakeAdmin(); + s.Logout(); + s.GoToLogin(); + s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); + s.GoToProfile(ManageNavPages.APIKeys); + s.Driver.FindElement(By.Id("AddApiKey")).Click(); + Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource); //server management should show now - s.SetCheckbox(s, "ServerManagementPermission", true); - s.SetCheckbox(s, "StoreManagementPermission", true); + s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true); + s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); s.Driver.FindElement(By.Id("Generate")).Click(); var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; //this api key has access to everything - await TestApiAgainstAccessToken(superApiKey, tester, user, Permissions.ServerManagement, - Permissions.StoreManagement); + await TestApiAgainstAccessToken(superApiKey, tester, user, $"{Permission.CanModifyServerSettings};{Permission.CanModifyStoreSettings}"); s.Driver.FindElement(By.Id("AddApiKey")).Click(); - s.SetCheckbox(s, "ServerManagementPermission", true); + s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true); s.Driver.FindElement(By.Id("Generate")).Click(); var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user, - Permissions.ServerManagement); + Permission.CanModifyServerSettings); s.Driver.FindElement(By.Id("AddApiKey")).Click(); - s.SetCheckbox(s, "StoreManagementPermission", true); + s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); s.Driver.FindElement(By.Id("Generate")).Click(); var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user, - Permissions.StoreManagement); + Permission.CanModifyStoreSettings); s.Driver.FindElement(By.Id("AddApiKey")).Click(); s.Driver.FindElement(By.CssSelector("button[value=change-store-mode]")).Click(); @@ -96,12 +93,12 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Generate")).Click(); var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user, - Permissions.GetStorePermission(storeId)); + Permission.Create(Permission.CanModifyStoreSettings, storeId).ToString()); 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); + await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user, string.Empty); await Assert.ThrowsAnyAsync(async () => { @@ -118,13 +115,13 @@ namespace BTCPayServer.Tests //strict //selectiveStores var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri, - new[] {Permissions.StoreManagement, Permissions.ServerManagement}).ToString(); + new[] {Permission.CanModifyStoreSettings, Permission.CanModifyServerSettings}).ToString(); s.Driver.Navigate().GoToUrl(authUrl); s.Driver.PageSource.Contains("kukksappname"); - Assert.Equal("hidden", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant()); - Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant()); - Assert.Equal("hidden", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant()); - Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("value").ToLowerInvariant()); + Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant()); + Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant()); + Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant()); + Assert.Equal("true",s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant()); Assert.DoesNotContain("change-store-mode", s.Driver.PageSource); s.Driver.FindElement(By.Id("consent-yes")).Click(); var url = s.Driver.Url; @@ -134,20 +131,20 @@ 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)).GetPermissions()); + (await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions); authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri, - new[] {Permissions.StoreManagement, Permissions.ServerManagement}, false, true).ToString(); + new[] {Permission.CanModifyStoreSettings, Permission.CanModifyServerSettings}, false, true).ToString(); s.Driver.Navigate().GoToUrl(authUrl); Assert.DoesNotContain("kukksappname", s.Driver.PageSource); - Assert.Equal("checkbox", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant()); - Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant()); - Assert.Equal("checkbox", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant()); - Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("value").ToLowerInvariant()); + Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant()); + Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant()); + Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant()); + Assert.Equal("true",s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant()); - s.SetCheckbox(s, "ServerManagementPermission", false); + s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", false); Assert.Contains("change-store-mode", s.Driver.PageSource); s.Driver.FindElement(By.Id("consent-yes")).Click(); url = s.Driver.Url; @@ -155,14 +152,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)).GetPermissions()); + (await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions); } } async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount, - params string[] permissions) + string permissionFormatted) { + var permissions = Permission.ToPermissions(permissionFormatted); var resultUser = await TestApiAgainstAccessToken(accessToken, $"{TestApiPath}/me/id", tester.PayTester.HttpClient); @@ -172,49 +170,68 @@ namespace BTCPayServer.Tests var secondUser = tester.NewAccount(); secondUser.GrantAccess(); - var selectiveStorePermissions = Permissions.ExtractStorePermissionsIds(permissions); - if (permissions.Contains(Permissions.StoreManagement) || selectiveStorePermissions.Any()) + var canModifyAllStores = Permission.Create(Permission.CanModifyStoreSettings, null); + var canModifyServer = Permission.Create(Permission.CanModifyServerSettings, null); + var unrestricted = Permission.Create(Permission.Unrestricted, null); + var selectiveStorePermissions = permissions.Where(p => p.StoreId != null && p.Policy == Permission.CanModifyStoreSettings); + if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Any()) { var resultStores = await TestApiAgainstAccessToken(accessToken, $"{TestApiPath}/me/stores", tester.PayTester.HttpClient); - foreach (string selectiveStorePermission in selectiveStorePermissions) + foreach (var selectiveStorePermission in selectiveStorePermissions) { Assert.True(await TestApiAgainstAccessToken(accessToken, - $"{TestApiPath}/me/stores/{selectiveStorePermission}/can-edit", + $"{TestApiPath}/me/stores/{selectiveStorePermission.StoreId}/can-edit", tester.PayTester.HttpClient)); Assert.Contains(resultStores, - data => data.Id.Equals(selectiveStorePermission, StringComparison.InvariantCultureIgnoreCase)); + data => data.Id.Equals(selectiveStorePermission.StoreId, StringComparison.InvariantCultureIgnoreCase)); } - if (permissions.Contains(Permissions.StoreManagement)) + bool shouldBeAuthorized = false; + if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Contains(Permission.Create(Permission.CanViewStoreSettings, testAccount.StoreId))) { Assert.True(await TestApiAgainstAccessToken(accessToken, - $"{TestApiPath}/me/stores/actions", + $"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view", + tester.PayTester.HttpClient)); + Assert.Contains(resultStores, + data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase)); + shouldBeAuthorized = true; + } + if (permissions.Contains(canModifyAllStores) || selectiveStorePermissions.Contains(Permission.Create(Permission.CanModifyStoreSettings, testAccount.StoreId))) + { + Assert.True(await TestApiAgainstAccessToken(accessToken, + $"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view", tester.PayTester.HttpClient)); - Assert.True(await TestApiAgainstAccessToken(accessToken, $"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit", tester.PayTester.HttpClient)); Assert.Contains(resultStores, data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase)); + shouldBeAuthorized = true; } - else + + if (!shouldBeAuthorized) { await Assert.ThrowsAnyAsync(async () => { await TestApiAgainstAccessToken(accessToken, - $"{TestApiPath}/me/stores/actions", - tester.PayTester.HttpClient); + $"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit", + tester.PayTester.HttpClient); }); + await Assert.ThrowsAnyAsync(async () => + { + await TestApiAgainstAccessToken(accessToken, + $"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view", + tester.PayTester.HttpClient); + }); + Assert.DoesNotContain(resultStores, + data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase)); } - - Assert.DoesNotContain(resultStores, - data => data.Id.Equals(secondUser.StoreId, StringComparison.InvariantCultureIgnoreCase)); } - else if(!permissions.Contains(Permissions.ServerManagement)) + else if(!permissions.Contains(unrestricted)) { await Assert.ThrowsAnyAsync(async () => @@ -231,7 +248,7 @@ namespace BTCPayServer.Tests tester.PayTester.HttpClient); } - if (!permissions.Contains(Permissions.ServerManagement)) + if (!permissions.Contains(unrestricted)) { await Assert.ThrowsAnyAsync(async () => { @@ -245,7 +262,7 @@ namespace BTCPayServer.Tests tester.PayTester.HttpClient); } - if (permissions.Contains(Permissions.ServerManagement)) + if (permissions.Contains(canModifyServer)) { Assert.True(await TestApiAgainstAccessToken(accessToken, $"{TestApiPath}/me/is-admin", diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 4d3ebb483..c0aea9463 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -38,7 +38,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); await user.MakeAdmin(); - var client = await user.CreateClient(Permissions.ServerManagement, Permissions.StoreManagement); + var client = await user.CreateClient(Permission.CanModifyServerSettings, Permission.CanModifyStoreSettings); //Get current api key var apiKeyData = await client.GetCurrentAPIKeyInfo(); Assert.NotNull(apiKeyData); @@ -97,14 +97,14 @@ namespace BTCPayServer.Tests var adminAcc = tester.NewAccount(); adminAcc.UserId = admin.Id; adminAcc.IsAdmin = true; - var adminClient = await adminAcc.CreateClient(Permissions.ProfileManagement); + var adminClient = await adminAcc.CreateClient(Permission.CanModifyProfile); // We should be forbidden to create a new user without proper admin permissions await AssertHttpError(403, async () => await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou" })); await AssertHttpError(403, async () => await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true })); // However, should be ok with the server management permissions - adminClient = await adminAcc.CreateClient(Permissions.ServerManagement); + adminClient = await adminAcc.CreateClient(Permission.CanModifyServerSettings); await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou" }); // Even creating new admin should be ok await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "admin4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true }); @@ -112,7 +112,7 @@ namespace BTCPayServer.Tests var user1Acc = tester.NewAccount(); user1Acc.UserId = user1.Id; user1Acc.IsAdmin = false; - var user1Client = await user1Acc.CreateClient(Permissions.ServerManagement); + var user1Client = await user1Acc.CreateClient(Permission.CanModifyServerSettings); // User1 trying to get server management would still fail to create user await AssertHttpError(403, async () => await user1Client.CreateUser(new CreateApplicationUserRequest() { Email = "test8@gmail.com", Password = "afewfoiewiou" })); @@ -141,9 +141,9 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); await user.MakeAdmin(); - var clientProfile = await user.CreateClient(Permissions.ProfileManagement); - var clientServer = await user.CreateClient(Permissions.ServerManagement); - var clientInsufficient = await user.CreateClient(Permissions.StoreManagement); + var clientProfile = await user.CreateClient(Permission.CanModifyProfile); + var clientServer = await user.CreateClient(Permission.CanModifyServerSettings, Permission.CanViewProfile); + var clientInsufficient = await user.CreateClient(Permission.CanModifyStoreSettings); var apiKeyProfileUserData = await clientProfile.GetCurrentUser(); @@ -153,6 +153,7 @@ namespace BTCPayServer.Tests await Assert.ThrowsAsync(async () => await clientInsufficient.GetCurrentUser()); await clientServer.GetCurrentUser(); + await clientProfile.GetCurrentUser(); await Assert.ThrowsAsync(async () => await clientInsufficient.CreateUser(new CreateApplicationUserRequest() { diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 5a9425e2c..efade2146 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -32,9 +32,9 @@ namespace BTCPayServer.Tests public IWebDriver Driver { get; set; } public ServerTester Server { get; set; } - public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null) + public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) { - var server = ServerTester.Create(scope); + var server = ServerTester.Create(scope, newDb); return new SeleniumTester() { Server = server @@ -259,7 +259,7 @@ namespace BTCPayServer.Tests public void SetCheckbox(SeleniumTester s, string inputName, bool value) { - SetCheckbox(s.Driver.FindElement(By.Name(inputName)), value); + SetCheckbox(s.Driver.FindElement(By.Id(inputName)), value); } public void ScrollToElement(IWebElement element) diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 44769d816..9ecfe14e4 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -38,11 +38,14 @@ namespace BTCPayServer.Tests GrantAccessAsync().GetAwaiter().GetResult(); } - public async Task MakeAdmin() + public async Task MakeAdmin(bool isAdmin = true) { var userManager = parent.PayTester.GetService>(); var u = await userManager.FindByIdAsync(UserId); - await userManager.AddToRoleAsync(u, Roles.ServerAdmin); + if (isAdmin) + await userManager.AddToRoleAsync(u, Roles.ServerAdmin); + else + await userManager.RemoveFromRoleAsync(u, Roles.ServerAdmin); IsAdmin = true; } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 8c71cd8a8..94013f5d0 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -62,6 +62,7 @@ using BTCPayServer.U2F.Models; using BTCPayServer.Security.Bitpay; using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache; using Newtonsoft.Json.Schema; +using BTCPayServer.Client; namespace BTCPayServer.Tests { @@ -3000,6 +3001,22 @@ noninventoryitem: await new ApplicationDbContext(builder.Options).Database.MigrateAsync(); } + [Fact(Timeout = TestTimeout)] + [Trait("Fast", "Fast")] + public void CanUsePermission() + { + Assert.True(Permission.Create(Permission.CanModifyServerSettings).Contains(Permission.Create(Permission.CanModifyServerSettings))); + Assert.True(Permission.Create(Permission.CanModifyProfile).Contains(Permission.Create(Permission.CanViewProfile))); + Assert.True(Permission.Create(Permission.CanModifyStoreSettings).Contains(Permission.Create(Permission.CanViewStoreSettings))); + Assert.False(Permission.Create(Permission.CanViewStoreSettings).Contains(Permission.Create(Permission.CanModifyStoreSettings))); + Assert.False(Permission.Create(Permission.CanModifyServerSettings).Contains(Permission.Create(Permission.CanModifyStoreSettings))); + Assert.True(Permission.Create(Permission.Unrestricted).Contains(Permission.Create(Permission.CanModifyStoreSettings))); + Assert.True(Permission.Create(Permission.Unrestricted).Contains(Permission.Create(Permission.CanModifyStoreSettings, "abc"))); + + Assert.True(Permission.Create(Permission.CanViewStoreSettings).Contains(Permission.Create(Permission.CanViewStoreSettings, "abcd"))); + Assert.False(Permission.Create(Permission.CanModifyStoreSettings, "abcd").Contains(Permission.Create(Permission.CanModifyStoreSettings))); + } + [Fact(Timeout = TestTimeout)] [Trait("Fast", "Fast")] public void CheckRatesProvider() diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index db100f3f6..e39898639 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -223,5 +223,5 @@ <_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" /> - + diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index 0d2f6e6e9..e95a11c17 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Filters; using BTCPayServer.Models; using BTCPayServer.Security; @@ -12,7 +13,7 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers { [BitpayAPIConstraint] - [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Bitpay)] + [Authorize(Permission.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Bitpay)] public class InvoiceControllerAPI : Controller { private InvoiceController _InvoiceController; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 01aef66f1..9eca43d5a 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -6,6 +6,7 @@ using System.Net.Mime; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Filters; @@ -510,7 +511,7 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("invoices/create")] - [Authorize(Policy = Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken) { diff --git a/BTCPayServer/Controllers/ManageController.APIKeys.cs b/BTCPayServer/Controllers/ManageController.APIKeys.cs index fa94b190d..65c9c92bb 100644 --- a/BTCPayServer/Controllers/ManageController.APIKeys.cs +++ b/BTCPayServer/Controllers/ManageController.APIKeys.cs @@ -25,11 +25,11 @@ namespace BTCPayServer.Controllers { ApiKeyDatas = await _apiKeyRepository.GetKeys(new APIKeyRepository.APIKeyQuery() { - UserId = new[] {_userManager.GetUserId(User)} + UserId = new[] { _userManager.GetUserId(User) } }) }); } - + [HttpGet("api-keys/{id}/delete")] public async Task RemoveAPIKey(string id) { @@ -96,22 +96,13 @@ namespace BTCPayServer.Controllers permissions ??= Array.Empty(); - var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel() + var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel(Permission.ToPermissions(permissions)) { Label = applicationName, - ServerManagementPermission = permissions.Contains(Permissions.ServerManagement), - StoreManagementPermission = permissions.Contains(Permissions.StoreManagement), - PermissionsFormatted = permissions, - PermissionValues = permissions.Where(s => - !s.Contains(Permissions.StoreManagement, StringComparison.InvariantCultureIgnoreCase) && - s != Permissions.ServerManagement) - .Select(s => new AddApiKeyViewModel.PermissionValueItem() {Permission = s, Value = true}).ToList(), ApplicationName = applicationName, SelectiveStores = selectiveStores, Strict = strict, }); - - vm.ServerManagementPermission = vm.ServerManagementPermission && vm.IsServerAdmin; return View(vm); } @@ -126,22 +117,20 @@ namespace BTCPayServer.Controllers return ar; } - - if (viewModel.PermissionsFormatted.Contains(Permissions.ServerManagement)) + if (viewModel.Strict) { - if (!viewModel.IsServerAdmin && viewModel.ServerManagementPermission) + for (int i = 0; i < viewModel.PermissionValues.Count; i++) { - viewModel.ServerManagementPermission = false; - } - - if (!viewModel.ServerManagementPermission && viewModel.Strict) - { - ModelState.AddModelError(nameof(viewModel.ServerManagementPermission), - "This permission is required for this application."); + if (viewModel.PermissionValues[i].Forbidden) + { + ModelState.AddModelError($"{viewModel.PermissionValues}[{i}].Value", + $"The permission '{viewModel.PermissionValues[i].Title}' is required for this application."); + } } } - if (viewModel.PermissionsFormatted.Contains(Permissions.StoreManagement)) + var permissions = Permission.ToPermissions(viewModel.Permissions).ToHashSet(); + if (permissions.Contains(Permission.Create(Permission.CanModifyStoreSettings))) { if (!viewModel.SelectiveStores && viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific) @@ -151,10 +140,10 @@ namespace BTCPayServer.Controllers "This application does not allow selective store permissions."); } - if (!viewModel.StoreManagementPermission && !viewModel.SpecificStores.Any() && viewModel.Strict) + if (!viewModel.StoreManagementPermission.Value && !viewModel.SpecificStores.Any() && viewModel.Strict) { ModelState.AddModelError(nameof(viewModel.StoreManagementPermission), - "This permission is required for this application."); + $"This permission '{viewModel.StoreManagementPermission.Title}' is required for this application."); } } @@ -174,8 +163,9 @@ namespace BTCPayServer.Controllers Severity = StatusMessageModel.StatusSeverity.Success, Html = $"API key generated! {key.Id}" }); - return RedirectToAction("APIKeys", new { key = key.Id}); - default: return View(viewModel); + return RedirectToAction("APIKeys", new { key = key.Id }); + default: + return View(viewModel); } } @@ -225,15 +215,15 @@ namespace BTCPayServer.Controllers return View(viewModel); case string x when x.StartsWith("remove-store", StringComparison.InvariantCultureIgnoreCase): - { - ModelState.Clear(); - var index = int.Parse( - viewModel.Command.Substring( - viewModel.Command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), - CultureInfo.InvariantCulture); - viewModel.SpecificStores.RemoveAt(index); - return View(viewModel); - } + { + ModelState.Clear(); + var index = int.Parse( + viewModel.Command.Substring( + viewModel.Command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), + CultureInfo.InvariantCulture); + viewModel.SpecificStores.RemoveAt(index); + return View(viewModel); + } } return null; @@ -248,53 +238,104 @@ namespace BTCPayServer.Controllers UserId = _userManager.GetUserId(User), Label = viewModel.Label }; - key.SetPermissions(GetPermissionsFromViewModel(viewModel)); + key.Permissions = string.Join(";", GetPermissionsFromViewModel(viewModel).Select(p => p.ToString()).Distinct().ToArray()); await _apiKeyRepository.CreateKey(key); return key; } - private IEnumerable GetPermissionsFromViewModel(AddApiKeyViewModel viewModel) + private IEnumerable GetPermissionsFromViewModel(AddApiKeyViewModel viewModel) { - var permissions = viewModel.PermissionValues.Where(tuple => tuple.Value).Select(tuple => tuple.Permission).ToList(); - - if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific) + List permissions = new List(); + foreach (var p in viewModel.PermissionValues.Where(tuple => tuple.Value && !tuple.Forbidden)) { - permissions.AddRange(viewModel.SpecificStores.Select(Permissions.GetStorePermission)); + if (Permission.TryCreatePermission(p.Permission, null, out var pp)) + permissions.Add(pp); } - else if (viewModel.StoreManagementPermission) + if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.AllStores && viewModel.StoreManagementPermission.Value) { - permissions.Add(Permissions.StoreManagement); + permissions.Add(Permission.Create(Permission.CanModifyStoreSettings)); } - - if (viewModel.IsServerAdmin && viewModel.ServerManagementPermission) + else if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific) { - permissions.Add(Permissions.ServerManagement); + permissions.AddRange(viewModel.SpecificStores.Select(s => Permission.Create(Permission.CanModifyStoreSettings, s))); } - return permissions.Distinct(); } private async Task SetViewModelValues(T viewModel) where T : AddApiKeyViewModel { viewModel.Stores = await _StoreRepository.GetStoresByUserId(_userManager.GetUserId(User)); - viewModel.IsServerAdmin = - (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded; - viewModel.PermissionValues ??= Permissions.GetAllPermissionKeys().Where(s => - !s.Contains(Permissions.StoreManagement, StringComparison.InvariantCultureIgnoreCase) && - s != Permissions.ServerManagement) - .Select(s => new AddApiKeyViewModel.PermissionValueItem() {Permission = s, Value = true}).ToList(); + var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded; + viewModel.PermissionValues ??= Permission.AllPolicies.Where(p => p != Permission.CanModifyStoreSettings) + .Select(s => new AddApiKeyViewModel.PermissionValueItem() { Permission = s, Value = false }).ToList(); + if (!isAdmin) + { + foreach (var p in viewModel.PermissionValues) + { + if (p.Permission == Permission.CanCreateUser || + p.Permission == Permission.CanModifyServerSettings) + { + p.Forbidden = true; + } + } + } return viewModel; } public class AddApiKeyViewModel { + public AddApiKeyViewModel() + { + StoreManagementPermission = new PermissionValueItem() + { + Permission = Permission.CanModifyStoreSettings, + Value = false + }; + StoreManagementSelectivePermission = new PermissionValueItem() + { + Permission = $"{Permission.CanModifyStoreSettings}:", + Value = true + }; + } + public AddApiKeyViewModel(IEnumerable permissions):this() + { + StoreManagementPermission.Value = permissions.Any(p => p.Policy == Permission.CanModifyStoreSettings && p.StoreId == null); + PermissionValues = permissions.Where(p => p.Policy != Permission.CanModifyStoreSettings) + .Select(p => new PermissionValueItem() { Permission = p.ToString(), Value = true }) + .ToList(); + } + + public IEnumerable GetPermissions() + { + if (!(PermissionValues is null)) + { + foreach (var p in PermissionValues.Where(o => o.Value)) + { + if (Permission.TryCreatePermission(p.Permission, null, out var pp)) + yield return pp; + } + } + if (this.StoreMode == ApiKeyStoreMode.AllStores) + { + if (StoreManagementPermission.Value) + yield return Permission.Create(Permission.CanModifyStoreSettings); + } + else if (this.StoreMode == ApiKeyStoreMode.Specific && SpecificStores is List) + { + foreach (var p in SpecificStores) + { + if (Permission.TryCreatePermission(Permission.CanModifyStoreSettings, p, out var pp)) + yield return pp; + } + } + } public string Label { get; set; } public StoreData[] Stores { get; set; } + ApiKeyStoreMode _StoreMode; public ApiKeyStoreMode StoreMode { get; set; } public List SpecificStores { get; set; } = new List(); - public bool IsServerAdmin { get; set; } - public bool ServerManagementPermission { get; set; } - public bool StoreManagementPermission { get; set; } + public PermissionValueItem StoreManagementPermission { get; set; } + public PermissionValueItem StoreManagementSelectivePermission { get; set; } public string Command { get; set; } public List PermissionValues { get; set; } @@ -306,29 +347,52 @@ namespace BTCPayServer.Controllers public class PermissionValueItem { + public static readonly Dictionary PermissionDescriptions = new Dictionary() + { + {BTCPayServer.Client.Permission.Unrestricted, ("Unrestricted access", "The app will have unrestricted access to your account.")}, + {BTCPayServer.Client.Permission.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")}, + {BTCPayServer.Client.Permission.CanModifyStoreSettings, ("Modify your stores", "The app will be able to create, view and modify, delete and create new invoices on the all your stores.")}, + {BTCPayServer.Client.Permission.CanViewStoreSettings, ("View your stores", "The app will be able to create, view all your stores.")}, + {$"{BTCPayServer.Client.Permission.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to view, modify, delete and create new invoices on the selected stores.")}, + {BTCPayServer.Client.Permission.CanModifyServerSettings, ("Manage your server", "The app will have total control on your server")}, + {BTCPayServer.Client.Permission.CanViewProfile, ("View your profile", "The app will be able to view your user profile.")}, + {BTCPayServer.Client.Permission.CanModifyProfile, ("Manage your profile", "The app will be able to view and modify your user profile.")}, + {BTCPayServer.Client.Permission.CanCreateInvoice, ("Create an invoice", "The app will be able to create new invoice.")}, + }; + public string Title + { + get + { + return PermissionDescriptions[Permission].Title; + } + } + public string Description + { + get + { + return PermissionDescriptions[Permission].Description; + } + } public string Permission { get; set; } public bool Value { get; set; } + public bool Forbidden { get; set; } } } public class AuthorizeApiKeysViewModel : AddApiKeyViewModel { + public AuthorizeApiKeysViewModel() + { + + } + public AuthorizeApiKeysViewModel(IEnumerable permissions) : base(permissions) + { + Permissions = string.Join(';', permissions.Select(p => p.ToString()).ToArray()); + } public string ApplicationName { get; set; } public bool Strict { get; set; } public bool SelectiveStores { get; set; } public string Permissions { get; set; } - - public string[] PermissionsFormatted - { - get - { - return Permissions?.Split(";", StringSplitOptions.RemoveEmptyEntries)?? Array.Empty(); - } - set - { - Permissions = string.Join(';', value ?? Array.Empty()); - } - } } diff --git a/BTCPayServer/Controllers/RestApi/ApiKeys/ApiKeysController.cs b/BTCPayServer/Controllers/RestApi/ApiKeys/ApiKeysController.cs index ab229a6f7..cc7b7e7de 100644 --- a/BTCPayServer/Controllers/RestApi/ApiKeys/ApiKeysController.cs +++ b/BTCPayServer/Controllers/RestApi/ApiKeys/ApiKeysController.cs @@ -1,4 +1,6 @@ using System.Threading.Tasks; +using System.Linq; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Security; @@ -43,7 +45,7 @@ namespace BTCPayServer.Controllers.RestApi.ApiKeys { return new ApiKeyData() { - Permissions = data.GetPermissions(), + Permissions = Permission.ToPermissions(data.Permissions).Select(c => c.ToString()).ToArray(), ApiKey = data.Id, UserId = data.UserId, Label = data.Label diff --git a/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs b/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs index d47a732dd..ac81ef575 100644 --- a/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs +++ b/BTCPayServer/Controllers/RestApi/TestApiKeyController.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Security; using BTCPayServer.Security.APIKeys; @@ -27,45 +28,45 @@ namespace BTCPayServer.Controllers.RestApi } [HttpGet("me/id")] + [Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] public string GetCurrentUserId() { return _userManager.GetUserId(User); } [HttpGet("me")] + [Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] public async Task GetCurrentUser() { return await _userManager.GetUserAsync(User); } [HttpGet("me/is-admin")] - [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] + [Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] public bool AmIAnAdmin() { return true; } [HttpGet("me/stores")] - [Authorize(Policy = Policies.CanListStoreSettings.Key, - AuthenticationSchemes = AuthenticationSchemes.ApiKey)] - public async Task GetCurrentUserStores() + [Authorize(Policy = Permission.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] + public StoreData[] GetCurrentUserStores() { - return await User.GetStores(_userManager, _storeRepository); + return this.HttpContext.GetStoresData(); } - - [HttpGet("me/stores/actions")] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, + + [HttpGet("me/stores/{storeId}/can-view")] + [Authorize(Policy = Permission.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] - public bool CanDoNonImplicitStoreActions() + public bool CanViewStore(string storeId) { return true; } - [HttpGet("me/stores/{storeId}/can-edit")] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, + [Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] - public bool CanEdit(string storeId) + public bool CanEditStore(string storeId) { return true; } diff --git a/BTCPayServer/Controllers/RestApi/Users/UsersController.cs b/BTCPayServer/Controllers/RestApi/Users/UsersController.cs index 0564075d9..7c2763d32 100644 --- a/BTCPayServer/Controllers/RestApi/Users/UsersController.cs +++ b/BTCPayServer/Controllers/RestApi/Users/UsersController.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using NicolasDorier.RateLimits; +using BTCPayServer.Client; namespace BTCPayServer.Controllers.RestApi.Users { @@ -54,7 +55,7 @@ namespace BTCPayServer.Controllers.RestApi.Users _authorizationService = authorizationService; } - [Authorize(Policy = Policies.CanModifyProfile.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] + [Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)] [HttpGet("~/api/v1/users/me")] public async Task> GetCurrentUser() { @@ -86,26 +87,21 @@ namespace BTCPayServer.Controllers.RestApi.Users if (anyAdmin && request.IsAdministrator is true && !isAuth) return Forbid(AuthenticationSchemes.ApiKey); // You are de-facto admin if there is no other admin, else you need to be auth and pass policy requirements - bool isAdmin = anyAdmin ? (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings.Key))).Succeeded + bool isAdmin = anyAdmin ? (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Permission.CanModifyServerSettings))).Succeeded && isAuth : true; // You need to be admin to create an admin if (request.IsAdministrator is true && !isAdmin) return Forbid(AuthenticationSchemes.ApiKey); - var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser.Key))).Succeeded; if (!isAdmin && policies.LockSubscription) { // If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission + var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Permission.CanCreateUser))).Succeeded; if (!isAuth || !canCreateUser) return Forbid(AuthenticationSchemes.ApiKey); } - // TODO: Check if needed to reenable - // Forbid non-admin users without CanCreateUser permission to create accounts - //if (isAuth && !isAdmin && !canCreateUser) - // return Forbid(AuthenticationSchemes.ApiKey); - var user = new ApplicationUser { UserName = request.Email, diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index b055ce0c1..28b7df6cb 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -35,10 +35,11 @@ using BTCPayServer.Services.Apps; using Microsoft.AspNetCore.Mvc.Rendering; using BTCPayServer.Data; using Microsoft.EntityFrameworkCore; +using BTCPayServer.Client; namespace BTCPayServer.Controllers { - [Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key, + [Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes.Cookie)] public partial class ServerController : Controller { diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs index 8b33bd5bf..eebebf054 100644 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ b/BTCPayServer/Controllers/StoresController.BTCLike.cs @@ -24,6 +24,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using BTCPayServer.Logging; using Microsoft.Extensions.Logging; +using BTCPayServer.Client; namespace BTCPayServer.Controllers { @@ -386,7 +387,7 @@ namespace BTCPayServer.Controllers private async Task CanUseHotWallet() { - var isAdmin = (await _authorizationService.AuthorizeAsync(User, BTCPayServer.Security.Policies.CanModifyServerSettings.Key)).Succeeded; + var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded; if (isAdmin) return true; var policies = await _settingsRepository.GetSettingAsync(); diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index dda5a65ee..fcce5c962 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.HostedServices; @@ -33,7 +34,7 @@ namespace BTCPayServer.Controllers { [Route("stores")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class StoresController : Controller { diff --git a/BTCPayServer/Controllers/VaultController.cs b/BTCPayServer/Controllers/VaultController.cs index 1c29d9105..edb4211a3 100644 --- a/BTCPayServer/Controllers/VaultController.cs +++ b/BTCPayServer/Controllers/VaultController.cs @@ -7,6 +7,7 @@ using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Hwi; using BTCPayServer.ModelBinders; @@ -127,7 +128,7 @@ namespace BTCPayServer.Controllers } await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken); o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken)); - var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings.Key); + var authorization = await _authorizationService.AuthorizeAsync(User, Permission.CanModifyStoreSettings); if (!authorization.Succeeded) { await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken); diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index e4dc74e6d..bce2f827f 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -6,6 +6,7 @@ using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.ModelBinders; @@ -30,7 +31,7 @@ using Newtonsoft.Json; namespace BTCPayServer.Controllers { [Route("wallets")] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class WalletsController : Controller { @@ -366,7 +367,7 @@ namespace BTCPayServer.Controllers private async Task CanUseHotWallet() { - var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded; + var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded; if (isAdmin) return true; var policies = await _settingsRepository.GetSettingAsync(); @@ -839,7 +840,7 @@ namespace BTCPayServer.Controllers var vm = new RescanWalletModel(); vm.IsFullySync = _dashboard.IsFullySynched(walletId.CryptoCode, out var unused); - vm.IsServerAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded; + vm.IsServerAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded; vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true; var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode); var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation); @@ -869,7 +870,7 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("{walletId}/rescan")] - [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WalletRescan( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, RescanWalletModel vm) diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 1d1736423..90e4305f1 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -425,6 +425,15 @@ namespace BTCPayServer ctx.Items["BTCPAY.STOREDATA"] = storeData; } + public static StoreData[] GetStoresData(this HttpContext ctx) + { + return ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[]; + } + public static void SetStoresData(this HttpContext ctx, StoreData[] storeData) + { + ctx.Items["BTCPAY.STORESDATA"] = storeData; + } + private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; public static string ToJson(this object o) { diff --git a/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs b/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs index 056b48180..07eff055d 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyAuthenticationHandler.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Security.Bitpay; using BTCPayServer.Services.Stores; @@ -44,11 +45,8 @@ namespace BTCPayServer.Security.APIKeys } List claims = new List(); - claims.Add(new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, key.UserId)); - claims.AddRange(key.GetPermissions() - .Select(permission => new Claim(APIKeyConstants.ClaimTypes.Permissions, permission))); - + claims.AddRange(Permission.ToPermissions(key.Permissions).Select(permission => new Claim(APIKeyConstants.ClaimTypes.Permission, permission.ToString()))); return AuthenticateResult.Success(new AuthenticationTicket( new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)), APIKeyConstants.AuthenticationType)); } diff --git a/BTCPayServer/Security/APIKeys/APIKeyAuthorizationHandler.cs b/BTCPayServer/Security/APIKeys/APIKeyAuthorizationHandler.cs index c693c2be1..0c06c5760 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyAuthorizationHandler.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyAuthorizationHandler.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -35,52 +36,54 @@ namespace BTCPayServer.Security.APIKeys bool success = false; switch (requirement.Policy) { - case Policies.CanModifyProfile.Key: - success = context.HasPermissions(Permissions.ProfileManagement); + case Permission.CanModifyProfile: + case Permission.CanViewProfile: + success = context.HasPermission(Permission.Create(requirement.Policy)); break; - case Policies.CanListStoreSettings.Key: - var selectiveStorePermissions = - Permissions.ExtractStorePermissionsIds(context.GetPermissions()); - success = context.HasPermissions(Permissions.StoreManagement) || - selectiveStorePermissions.Any(); - break; - case Policies.CanModifyStoreSettings.Key: - string storeId = _HttpContext.GetImplicitStoreId(); - if (!context.HasPermissions(Permissions.StoreManagement) && - !context.HasPermissions(Permissions.GetStorePermission(storeId))) - break; - if (storeId == null) + case Permission.CanViewStoreSettings: + case Permission.CanModifyStoreSettings: + var storeId = _HttpContext.GetImplicitStoreId(); + var userid = _userManager.GetUserId(context.User); + // Specific store action + if (storeId != null) { - success = true; + if (context.HasPermission(Permission.Create(requirement.Policy, storeId))) + { + if (string.IsNullOrEmpty(userid)) + break; + var store = await _storeRepository.FindStore((string)storeId, userid); + if (store == null) + break; + success = true; + _HttpContext.SetStoreData(store); + } } else { - var userid = _userManager.GetUserId(context.User); - if (string.IsNullOrEmpty(userid)) + var stores = await _storeRepository.GetStoresByUserId(userid); + List permissionedStores = new List(); + foreach (var store in stores) + { + if (context.HasPermission(Permission.Create(requirement.Policy, store.Id))) + permissionedStores.Add(store); + } + _HttpContext.SetStoresData(stores.ToArray()); + success = true; + } + break; + case Permission.CanCreateUser: + case Permission.CanModifyServerSettings: + if (context.HasPermission(Permission.Create(requirement.Policy))) + { + var user = await _userManager.GetUserAsync(context.User); + if (user == null) break; - var store = await _storeRepository.FindStore((string)storeId, userid); - if (store == null) + if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin)) break; success = true; - _HttpContext.SetStoreData(store); } - break; - case Policies.CanCreateUser.Key: - case Policies.CanModifyServerSettings.Key: - if (!context.HasPermissions(Permissions.ServerManagement)) - break; - // For this authorization, we still check in database because it is super sensitive. - success = await IsUserAdmin(context.User); - break; - } - - //if you do not have the specific permissions, BUT you have server management, we enable god mode - if (!success && context.HasPermissions(Permissions.ServerManagement) && - requirement.Policy != Policies.CanModifyServerSettings.Key) - { - success = await IsUserAdmin(context.User); } if (success) @@ -88,15 +91,5 @@ namespace BTCPayServer.Security.APIKeys context.Succeed(requirement); } } - - private async Task IsUserAdmin(ClaimsPrincipal contextUser) - { - var user = await _userManager.GetUserAsync(contextUser); - if (user == null) - return false; - if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin)) - return false; - return true; - } } } diff --git a/BTCPayServer/Security/APIKeys/APIKeyConstants.cs b/BTCPayServer/Security/APIKeys/APIKeyConstants.cs index f56598c9c..3ec317a53 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyConstants.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyConstants.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using BTCPayServer.Client; namespace BTCPayServer.Security.APIKeys { @@ -8,19 +9,7 @@ namespace BTCPayServer.Security.APIKeys public static class ClaimTypes { - public const string Permissions = nameof(APIKeys) + "." + nameof(Permissions); - } - - public static class Permissions - { - public static readonly Dictionary PermissionDescriptions = new Dictionary() - { - {Client.Permissions.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")}, - {$"{nameof(Client.Permissions.StoreManagement)}:", ("Manage selected stores", "The app will be able to modify and delete selected stores.")}, - {Client.Permissions.ServerManagement, ("Manage your server", "The app will have total control on your server")}, - {Client.Permissions.ProfileManagement, ("Manage your profile", "The app will be able to view and modify your user profile.")}, - }; - + public const string Permission = "APIKey.Permission"; } } } diff --git a/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs b/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs index 6f5429011..5cf617329 100644 --- a/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs +++ b/BTCPayServer/Security/APIKeys/APIKeyExtensions.cs @@ -29,22 +29,6 @@ namespace BTCPayServer.Security.APIKeys return false; } - public static Task GetStores(this ClaimsPrincipal claimsPrincipal, - UserManager userManager, StoreRepository storeRepository) - { - var permissions = - claimsPrincipal.Claims.Where(claim => claim.Type == APIKeyConstants.ClaimTypes.Permissions) - .Select(claim => claim.Value).ToList(); - - if (permissions.Contains(Permissions.StoreManagement)) - { - return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal)); - } - - var storeIds = Permissions.ExtractStorePermissionsIds(permissions); - return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal), storeIds); - } - public static AuthenticationBuilder AddAPIKeyAuthentication(this AuthenticationBuilder builder) { builder.AddScheme(AuthenticationSchemes.ApiKey, @@ -62,15 +46,24 @@ namespace BTCPayServer.Security.APIKeys public static string[] GetPermissions(this AuthorizationHandlerContext context) { return context.User.Claims.Where(c => - c.Type.Equals(APIKeyConstants.ClaimTypes.Permissions, StringComparison.InvariantCultureIgnoreCase)) + c.Type.Equals(APIKeyConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase)) .Select(claim => claim.Value).ToArray(); } - public static bool HasPermissions(this AuthorizationHandlerContext context, params string[] scopes) + public static bool HasPermission(this AuthorizationHandlerContext context, Permission permission) { - return scopes.All(s => context.User.HasClaim(c => - c.Type.Equals(APIKeyConstants.ClaimTypes.Permissions, StringComparison.InvariantCultureIgnoreCase) && - c.Value.Split(' ').Contains(s))); + foreach (var claim in context.User.Claims.Where(c => + c.Type.Equals(APIKeyConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase))) + { + if (Permission.TryParse(claim.Value, out var claimPermission)) + { + if (claimPermission.Contains(permission)) + { + return true; + } + } + } + return false; } } } diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs index 42bfefa3d..30b100e14 100644 --- a/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs +++ b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; using BTCPayServer.Services; using BTCPayServer.Security.Bitpay; +using BTCPayServer.Client; namespace BTCPayServer.Security.Bitpay { @@ -54,7 +55,7 @@ namespace BTCPayServer.Security.Bitpay var anyoneCanInvoice = store.GetStoreBlob().AnyoneCanInvoice; switch (requirement.Policy) { - case Policies.CanCreateInvoice.Key: + case Permission.CanCreateInvoice: if (!isAnonymous || (isAnonymous && anyoneCanInvoice)) { context.Succeed(requirement); diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs index c844b2c46..9f5c5e603 100644 --- a/BTCPayServer/Security/CookieAuthorizationHandler.cs +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Primitives; +using BTCPayServer.Client; namespace BTCPayServer.Security { @@ -35,7 +36,7 @@ namespace BTCPayServer.Security var isAdmin = context.User.IsInRole(Roles.ServerAdmin); switch (requirement.Policy) { - case Policies.CanModifyServerSettings.Key: + case Permission.CanModifyServerSettings: if (isAdmin) context.Succeed(requirement); return; @@ -56,11 +57,11 @@ namespace BTCPayServer.Security bool success = false; switch (requirement.Policy) { - case Policies.CanModifyStoreSettings.Key: + case Permission.CanModifyStoreSettings: if (store.Role == StoreRoles.Owner || isAdmin) success = true; break; - case Policies.CanCreateInvoice.Key: + case Permission.CanCreateInvoice: if (store.Role == StoreRoles.Owner || store.Role == StoreRoles.Guest || isAdmin || diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index 5e48517c1..b4cfccc5a 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using BTCPayServer.Client; +using Microsoft.AspNetCore.Authorization; namespace BTCPayServer.Security { @@ -6,14 +7,11 @@ namespace BTCPayServer.Security { public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) { - options.AddPolicy(CanModifyStoreSettings.Key); - options.AddPolicy(CanListStoreSettings.Key); - options.AddPolicy(CanCreateInvoice.Key); + foreach (var p in Permission.AllPolicies) + { + options.AddPolicy(p); + } options.AddPolicy(CanGetRates.Key); - options.AddPolicy(CanModifyServerSettings.Key); - options.AddPolicy(CanModifyServerSettings.Key); - options.AddPolicy(CanModifyProfile.Key); - options.AddPolicy(CanCreateUser.Key); return options; } @@ -21,36 +19,9 @@ namespace BTCPayServer.Security { options.AddPolicy(policy, o => o.AddRequirements(new PolicyRequirement(policy))); } - - public class CanModifyServerSettings - { - public const string Key = "btcpay.store.canmodifyserversettings"; - } - public class CanModifyProfile - { - public const string Key = "btcpay.store.canmodifyprofile"; - } - public class CanModifyStoreSettings - { - public const string Key = "btcpay.store.canmodifystoresettings"; - } - public class CanListStoreSettings - { - public const string Key = "btcpay.store.canliststoresettings"; - } - public class CanCreateInvoice - { - public const string Key = "btcpay.store.cancreateinvoice"; - } - public class CanGetRates { public const string Key = "btcpay.store.cangetrates"; } - - public class CanCreateUser - { - public const string Key = "btcpay.store.cancreateuser"; - } } } diff --git a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs index 80126cd3a..7cccf57fa 100644 --- a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs +++ b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs @@ -20,14 +20,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using BTCPayServer.Filters; +using BTCPayServer.Client; namespace BTCPayServer.Services.Altcoins.Monero.UI { [Route("stores/{storeId}/monerolike")] [OnlyIfSupportAttribute("XMR")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public class MoneroLikeStoreController : Controller { private readonly MoneroLikeConfiguration _MoneroLikeConfiguration; diff --git a/BTCPayServer/Views/Manage/APIKeys.cshtml b/BTCPayServer/Views/Manage/APIKeys.cshtml index ac197f44d..2ff26577e 100644 --- a/BTCPayServer/Views/Manage/APIKeys.cshtml +++ b/BTCPayServer/Views/Manage/APIKeys.cshtml @@ -1,3 +1,4 @@ +@namespace BTCPayServer.Client @model BTCPayServer.Controllers.ManageController.ApiKeysViewModel @{ ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Manage your API Keys"); @@ -27,7 +28,7 @@ } else { - @string.Join(", ", keyData.GetPermissions()) + @string.Join(", ", Permission.ToPermissions(keyData.Permissions).Select(c => c.ToString()).Distinct().ToArray()) } diff --git a/BTCPayServer/Views/Manage/AddApiKey.cshtml b/BTCPayServer/Views/Manage/AddApiKey.cshtml index 13791963a..5471ae6e8 100644 --- a/BTCPayServer/Views/Manage/AddApiKey.cshtml +++ b/BTCPayServer/Views/Manage/AddApiKey.cshtml @@ -5,16 +5,6 @@ @{ ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Add API Key"); - - string GetDescription(string permission) - { - return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description; - } - - string GetTitle(string permission) - { - return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description; - } }

@ViewData["Title"]

@@ -26,53 +16,47 @@
- +
- +
- @if (Model.IsServerAdmin) - { -
- - - -

@GetDescription(Permissions.ServerManagement).

-
- } - + @for (int i = 0; i < Model.PermissionValues.Count; i++) { + @if (!Model.PermissionValues[i].Forbidden) + {
- - + + -

@GetDescription(Model.PermissionValues[i].Permission).

+

@Model.PermissionValues[i].Description

- } + } + } @if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores) { -
- @Html.CheckBoxFor(model => model.StoreManagementPermission, new Dictionary() {{"class", "form-check-inline"}}) +
+ - - -

@GetDescription(Permissions.StoreManagement).

- -
+ + +

@Model.StoreManagementPermission.Description

+ +
} else if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.Specific) {
  • -
    @GetTitle(Permissions.StoreManagement + ":")
    -

    @GetDescription(Permissions.StoreManagement + ":").

    +
    @Model.StoreManagementSelectivePermission.Title
    +

    @Model.StoreManagementSelectivePermission.Description

  • @if (!Model.Stores.Any()) diff --git a/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml b/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml index 8ba022d87..6a431637c 100644 --- a/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml +++ b/BTCPayServer/Views/Manage/AuthorizeAPIKey.cshtml @@ -6,24 +6,17 @@ @{ Layout = "_Layout"; ViewData["Title"] = $"Authorize {(Model.ApplicationName ?? "Application")}"; - - string GetDescription(string permission) - { - return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description; - } - - string GetTitle(string permission) - { - return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description; - } + var permissions = Permission.ToPermissions(Model.Permissions); + var hasStorePermission = permissions.Any(p => p.Policy == Permission.CanModifyStoreSettings); } - - - - + + + + +
    @@ -39,76 +32,60 @@
    - +
    - @if (!Model.PermissionsFormatted.Any()) + @if (!permissions.Any()) {
    -

    There are no associated permissions to the API key being requested here. The application cannot do anything with your BTCPay account other than validating your account exists.

    +

    There are no associated permissions to the API key being requested here. The application cannot do anything with your BTCPay account other than validating your account exists.

    } - @if (Model.PermissionsFormatted.Contains(Permissions.ServerManagement) && (Model.IsServerAdmin || Model.Strict)) - { -
    - @if (Model.Strict || !Model.IsServerAdmin) - { - - - } - else - { - - } - - @if (!Model.IsServerAdmin) - { - - The server management permission is being requested but your account is not an administrator - - } - - -

    @GetDescription(Permissions.ServerManagement).

    -
    - } @for (int i = 0; i < Model.PermissionValues.Count; i++) {
    - @if (Model.Strict || !Model.IsServerAdmin) + @if (Model.Strict) { - - + + } else { - + } - - -

    @GetDescription(Model.PermissionValues[i].Permission).

    + + + @if (Model.PermissionValues[i].Forbidden) + { +
    + + This permission is not available for your account. + + } +

    @Model.PermissionValues[i].Description

    } - @if (Model.PermissionsFormatted.Contains(Permissions.StoreManagement)) + @if (hasStorePermission) { @if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores) {
    @if (Model.Strict) { - - + + } else { - + } - + +
    -

    @GetDescription(Permissions.StoreManagement).

    +

    @Model.StoreManagementPermission.Description

    @if (Model.SelectiveStores) { @@ -119,8 +96,8 @@ {
  • -
    @GetTitle(Permissions.StoreManagement + ":")
    -

    @GetDescription(Permissions.StoreManagement + ":").

    +
    @Model.StoreManagementSelectivePermission.Title
    +

    @Model.StoreManagementSelectivePermission.Description

  • @if (!Model.Stores.Any())