mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Refactor permissions of GreenField
This commit is contained in:
@@ -5,25 +5,183 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace BTCPayServer.Client
|
namespace BTCPayServer.Client
|
||||||
{
|
{
|
||||||
public static class Permissions
|
public class Permission
|
||||||
{
|
{
|
||||||
public const string ServerManagement = nameof(ServerManagement);
|
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
|
||||||
public const string StoreManagement = nameof(StoreManagement);
|
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
|
||||||
public const string ProfileManagement = nameof(ProfileManagement);
|
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<string> AllPolicies
|
||||||
{
|
{
|
||||||
return new[]
|
get
|
||||||
{
|
{
|
||||||
ServerManagement,
|
yield return CanCreateInvoice;
|
||||||
StoreManagement,
|
yield return CanModifyServerSettings;
|
||||||
ProfileManagement
|
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<string> ExtractStorePermissionsIds(IEnumerable<string> permissions) => permissions
|
public static Permission Create(string policy, string storeId = null)
|
||||||
.Where(s => s.StartsWith($"{nameof(StoreManagement)}:", StringComparison.InvariantCulture))
|
{
|
||||||
.Select(s => s.Split(":")[1]);
|
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<Permission> 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<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)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,6 @@ namespace BTCPayServer.Data
|
|||||||
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 string[] GetPermissions() { return Permissions?.Split(';') ?? new string[0]; }
|
|
||||||
|
|
||||||
public void SetPermissions(IEnumerable<string> permissions)
|
|
||||||
{
|
|
||||||
Permissions = string.Join(';',
|
|
||||||
permissions?.Select(s => s.Replace(";", string.Empty)) ?? new string[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum APIKeyType
|
public enum APIKeyType
|
||||||
|
|||||||
@@ -42,49 +42,46 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
|
await user.MakeAdmin(false);
|
||||||
await user.CreateStoreAsync();
|
|
||||||
s.GoToLogin();
|
s.GoToLogin();
|
||||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||||
s.GoToProfile(ManageNavPages.APIKeys);
|
s.GoToProfile(ManageNavPages.APIKeys);
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||||
if (!user.IsAdmin)
|
|
||||||
{
|
//not an admin, so this permission should not show
|
||||||
//not an admin, so this permission should not show
|
Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
|
||||||
Assert.DoesNotContain("ServerManagementPermission", s.Driver.PageSource);
|
await user.MakeAdmin();
|
||||||
await user.MakeAdmin();
|
s.Logout();
|
||||||
s.Logout();
|
s.GoToLogin();
|
||||||
s.GoToLogin();
|
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
s.GoToProfile(ManageNavPages.APIKeys);
|
||||||
s.GoToProfile(ManageNavPages.APIKeys);
|
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
|
||||||
}
|
|
||||||
|
|
||||||
//server management should show now
|
//server management should show now
|
||||||
s.SetCheckbox(s, "ServerManagementPermission", true);
|
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
|
||||||
s.SetCheckbox(s, "StoreManagementPermission", true);
|
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
|
||||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||||
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, Permissions.ServerManagement,
|
await TestApiAgainstAccessToken(superApiKey, tester, user, $"{Permission.CanModifyServerSettings};{Permission.CanModifyStoreSettings}");
|
||||||
Permissions.StoreManagement);
|
|
||||||
|
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
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();
|
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||||
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||||
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
|
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
|
||||||
Permissions.ServerManagement);
|
Permission.CanModifyServerSettings);
|
||||||
|
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
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();
|
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||||
var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||||
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
|
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
|
||||||
Permissions.StoreManagement);
|
Permission.CanModifyStoreSettings);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||||
s.Driver.FindElement(By.CssSelector("button[value=change-store-mode]")).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();
|
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||||
var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||||
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
|
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("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);
|
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user, string.Empty);
|
||||||
|
|
||||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
{
|
{
|
||||||
@@ -118,13 +115,13 @@ namespace BTCPayServer.Tests
|
|||||||
//strict
|
//strict
|
||||||
//selectiveStores
|
//selectiveStores
|
||||||
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
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.Navigate().GoToUrl(authUrl);
|
||||||
s.Driver.PageSource.Contains("kukksappname");
|
s.Driver.PageSource.Contains("kukksappname");
|
||||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
|
||||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
Assert.Equal("true",s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
|
||||||
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
|
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
|
||||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||||
var url = s.Driver.Url;
|
var url = s.Driver.Url;
|
||||||
@@ -134,20 +131,20 @@ 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)).GetPermissions());
|
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions);
|
||||||
|
|
||||||
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
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);
|
s.Driver.Navigate().GoToUrl(authUrl);
|
||||||
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
||||||
|
|
||||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
|
||||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("value").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);
|
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
||||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||||
url = s.Driver.Url;
|
url = s.Driver.Url;
|
||||||
@@ -155,14 +152,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)).GetPermissions());
|
(await apiKeyRepo.GetKey(results.Single(pair => pair.Key == "key").Value)).Permissions);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount,
|
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount,
|
||||||
params string[] permissions)
|
string permissionFormatted)
|
||||||
{
|
{
|
||||||
|
var permissions = Permission.ToPermissions(permissionFormatted);
|
||||||
var resultUser =
|
var resultUser =
|
||||||
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id",
|
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id",
|
||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
@@ -172,49 +170,68 @@ namespace BTCPayServer.Tests
|
|||||||
var secondUser = tester.NewAccount();
|
var secondUser = tester.NewAccount();
|
||||||
secondUser.GrantAccess();
|
secondUser.GrantAccess();
|
||||||
|
|
||||||
var selectiveStorePermissions = Permissions.ExtractStorePermissionsIds(permissions);
|
var canModifyAllStores = Permission.Create(Permission.CanModifyStoreSettings, null);
|
||||||
if (permissions.Contains(Permissions.StoreManagement) || selectiveStorePermissions.Any())
|
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 =
|
var resultStores =
|
||||||
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
|
|
||||||
foreach (string selectiveStorePermission in selectiveStorePermissions)
|
foreach (var selectiveStorePermission in selectiveStorePermissions)
|
||||||
{
|
{
|
||||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/stores/{selectiveStorePermission}/can-edit",
|
$"{TestApiPath}/me/stores/{selectiveStorePermission.StoreId}/can-edit",
|
||||||
tester.PayTester.HttpClient));
|
tester.PayTester.HttpClient));
|
||||||
|
|
||||||
Assert.Contains(resultStores,
|
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<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(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<bool>(accessToken,
|
||||||
|
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-view",
|
||||||
tester.PayTester.HttpClient));
|
tester.PayTester.HttpClient));
|
||||||
|
|
||||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||||
tester.PayTester.HttpClient));
|
tester.PayTester.HttpClient));
|
||||||
Assert.Contains(resultStores,
|
Assert.Contains(resultStores,
|
||||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
shouldBeAuthorized = true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!shouldBeAuthorized)
|
||||||
{
|
{
|
||||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
{
|
{
|
||||||
await TestApiAgainstAccessToken<bool>(accessToken,
|
await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/stores/actions",
|
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
});
|
});
|
||||||
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
|
{
|
||||||
|
await TestApiAgainstAccessToken<bool>(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<HttpRequestException>(async () =>
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
@@ -231,7 +248,7 @@ namespace BTCPayServer.Tests
|
|||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!permissions.Contains(Permissions.ServerManagement))
|
if (!permissions.Contains(unrestricted))
|
||||||
{
|
{
|
||||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||||
{
|
{
|
||||||
@@ -245,7 +262,7 @@ namespace BTCPayServer.Tests
|
|||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permissions.Contains(Permissions.ServerManagement))
|
if (permissions.Contains(canModifyServer))
|
||||||
{
|
{
|
||||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/is-admin",
|
$"{TestApiPath}/me/is-admin",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace BTCPayServer.Tests
|
|||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
await user.MakeAdmin();
|
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
|
//Get current api key
|
||||||
var apiKeyData = await client.GetCurrentAPIKeyInfo();
|
var apiKeyData = await client.GetCurrentAPIKeyInfo();
|
||||||
Assert.NotNull(apiKeyData);
|
Assert.NotNull(apiKeyData);
|
||||||
@@ -97,14 +97,14 @@ namespace BTCPayServer.Tests
|
|||||||
var adminAcc = tester.NewAccount();
|
var adminAcc = tester.NewAccount();
|
||||||
adminAcc.UserId = admin.Id;
|
adminAcc.UserId = admin.Id;
|
||||||
adminAcc.IsAdmin = true;
|
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
|
// 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" }));
|
||||||
await AssertHttpError(403, async () => await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true }));
|
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
|
// 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" });
|
await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou" });
|
||||||
// Even creating new admin should be ok
|
// Even creating new admin should be ok
|
||||||
await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "admin4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true });
|
await adminClient.CreateUser(new CreateApplicationUserRequest() { Email = "admin4@gmail.com", Password = "afewfoiewiou", IsAdministrator = true });
|
||||||
@@ -112,7 +112,7 @@ namespace BTCPayServer.Tests
|
|||||||
var user1Acc = tester.NewAccount();
|
var user1Acc = tester.NewAccount();
|
||||||
user1Acc.UserId = user1.Id;
|
user1Acc.UserId = user1.Id;
|
||||||
user1Acc.IsAdmin = false;
|
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
|
// 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" }));
|
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();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
await user.MakeAdmin();
|
await user.MakeAdmin();
|
||||||
var clientProfile = await user.CreateClient(Permissions.ProfileManagement);
|
var clientProfile = await user.CreateClient(Permission.CanModifyProfile);
|
||||||
var clientServer = await user.CreateClient(Permissions.ServerManagement);
|
var clientServer = await user.CreateClient(Permission.CanModifyServerSettings, Permission.CanViewProfile);
|
||||||
var clientInsufficient = await user.CreateClient(Permissions.StoreManagement);
|
var clientInsufficient = await user.CreateClient(Permission.CanModifyStoreSettings);
|
||||||
|
|
||||||
|
|
||||||
var apiKeyProfileUserData = await clientProfile.GetCurrentUser();
|
var apiKeyProfileUserData = await clientProfile.GetCurrentUser();
|
||||||
@@ -153,6 +153,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
||||||
await clientServer.GetCurrentUser();
|
await clientServer.GetCurrentUser();
|
||||||
|
await clientProfile.GetCurrentUser();
|
||||||
|
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.CreateUser(new CreateApplicationUserRequest()
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.CreateUser(new CreateApplicationUserRequest()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ namespace BTCPayServer.Tests
|
|||||||
public IWebDriver Driver { get; set; }
|
public IWebDriver Driver { get; set; }
|
||||||
public ServerTester Server { 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()
|
return new SeleniumTester()
|
||||||
{
|
{
|
||||||
Server = server
|
Server = server
|
||||||
@@ -259,7 +259,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
public void SetCheckbox(SeleniumTester s, string inputName, bool value)
|
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)
|
public void ScrollToElement(IWebElement element)
|
||||||
|
|||||||
@@ -38,11 +38,14 @@ namespace BTCPayServer.Tests
|
|||||||
GrantAccessAsync().GetAwaiter().GetResult();
|
GrantAccessAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MakeAdmin()
|
public async Task MakeAdmin(bool isAdmin = true)
|
||||||
{
|
{
|
||||||
var userManager = parent.PayTester.GetService<UserManager<ApplicationUser>>();
|
var userManager = parent.PayTester.GetService<UserManager<ApplicationUser>>();
|
||||||
var u = await userManager.FindByIdAsync(UserId);
|
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;
|
IsAdmin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ using BTCPayServer.U2F.Models;
|
|||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
||||||
using Newtonsoft.Json.Schema;
|
using Newtonsoft.Json.Schema;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -3000,6 +3001,22 @@ noninventoryitem:
|
|||||||
await new ApplicationDbContext(builder.Options).Database.MigrateAsync();
|
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)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
[Trait("Fast", "Fast")]
|
[Trait("Fast", "Fast")]
|
||||||
public void CheckRatesProvider()
|
public void CheckRatesProvider()
|
||||||
|
|||||||
@@ -223,5 +223,5 @@
|
|||||||
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
@@ -12,7 +13,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[BitpayAPIConstraint]
|
[BitpayAPIConstraint]
|
||||||
[Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Bitpay)]
|
[Authorize(Permission.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Bitpay)]
|
||||||
public class InvoiceControllerAPI : Controller
|
public class InvoiceControllerAPI : Controller
|
||||||
{
|
{
|
||||||
private InvoiceController _InvoiceController;
|
private InvoiceController _InvoiceController;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Net.Mime;
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
@@ -510,7 +511,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices/create")]
|
[Route("invoices/create")]
|
||||||
[Authorize(Policy = Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
|
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
ApiKeyDatas = await _apiKeyRepository.GetKeys(new APIKeyRepository.APIKeyQuery()
|
ApiKeyDatas = await _apiKeyRepository.GetKeys(new APIKeyRepository.APIKeyQuery()
|
||||||
{
|
{
|
||||||
UserId = new[] {_userManager.GetUserId(User)}
|
UserId = new[] { _userManager.GetUserId(User) }
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("api-keys/{id}/delete")]
|
[HttpGet("api-keys/{id}/delete")]
|
||||||
public async Task<IActionResult> RemoveAPIKey(string id)
|
public async Task<IActionResult> RemoveAPIKey(string id)
|
||||||
{
|
{
|
||||||
@@ -96,22 +96,13 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
permissions ??= Array.Empty<string>();
|
permissions ??= Array.Empty<string>();
|
||||||
|
|
||||||
var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel()
|
var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel(Permission.ToPermissions(permissions))
|
||||||
{
|
{
|
||||||
Label = applicationName,
|
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,
|
ApplicationName = applicationName,
|
||||||
SelectiveStores = selectiveStores,
|
SelectiveStores = selectiveStores,
|
||||||
Strict = strict,
|
Strict = strict,
|
||||||
});
|
});
|
||||||
|
|
||||||
vm.ServerManagementPermission = vm.ServerManagementPermission && vm.IsServerAdmin;
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,22 +117,20 @@ namespace BTCPayServer.Controllers
|
|||||||
return ar;
|
return ar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewModel.Strict)
|
||||||
if (viewModel.PermissionsFormatted.Contains(Permissions.ServerManagement))
|
|
||||||
{
|
{
|
||||||
if (!viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
for (int i = 0; i < viewModel.PermissionValues.Count; i++)
|
||||||
{
|
{
|
||||||
viewModel.ServerManagementPermission = false;
|
if (viewModel.PermissionValues[i].Forbidden)
|
||||||
}
|
{
|
||||||
|
ModelState.AddModelError($"{viewModel.PermissionValues}[{i}].Value",
|
||||||
if (!viewModel.ServerManagementPermission && viewModel.Strict)
|
$"The permission '{viewModel.PermissionValues[i].Title}' is required for this application.");
|
||||||
{
|
}
|
||||||
ModelState.AddModelError(nameof(viewModel.ServerManagementPermission),
|
|
||||||
"This permission 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 &&
|
if (!viewModel.SelectiveStores &&
|
||||||
viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
||||||
@@ -151,10 +140,10 @@ namespace BTCPayServer.Controllers
|
|||||||
"This application does not allow selective store permissions.");
|
"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),
|
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,
|
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||||
Html = $"API key generated! <code>{key.Id}</code>"
|
Html = $"API key generated! <code>{key.Id}</code>"
|
||||||
});
|
});
|
||||||
return RedirectToAction("APIKeys", new { key = key.Id});
|
return RedirectToAction("APIKeys", new { key = key.Id });
|
||||||
default: return View(viewModel);
|
default:
|
||||||
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,15 +215,15 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(viewModel);
|
return View(viewModel);
|
||||||
|
|
||||||
case string x when x.StartsWith("remove-store", StringComparison.InvariantCultureIgnoreCase):
|
case string x when x.StartsWith("remove-store", StringComparison.InvariantCultureIgnoreCase):
|
||||||
{
|
{
|
||||||
ModelState.Clear();
|
ModelState.Clear();
|
||||||
var index = int.Parse(
|
var index = int.Parse(
|
||||||
viewModel.Command.Substring(
|
viewModel.Command.Substring(
|
||||||
viewModel.Command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1),
|
viewModel.Command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1),
|
||||||
CultureInfo.InvariantCulture);
|
CultureInfo.InvariantCulture);
|
||||||
viewModel.SpecificStores.RemoveAt(index);
|
viewModel.SpecificStores.RemoveAt(index);
|
||||||
return View(viewModel);
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -248,53 +238,104 @@ namespace BTCPayServer.Controllers
|
|||||||
UserId = _userManager.GetUserId(User),
|
UserId = _userManager.GetUserId(User),
|
||||||
Label = viewModel.Label
|
Label = viewModel.Label
|
||||||
};
|
};
|
||||||
key.SetPermissions(GetPermissionsFromViewModel(viewModel));
|
key.Permissions = string.Join(";", GetPermissionsFromViewModel(viewModel).Select(p => p.ToString()).Distinct().ToArray());
|
||||||
await _apiKeyRepository.CreateKey(key);
|
await _apiKeyRepository.CreateKey(key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetPermissionsFromViewModel(AddApiKeyViewModel viewModel)
|
private IEnumerable<Permission> GetPermissionsFromViewModel(AddApiKeyViewModel viewModel)
|
||||||
{
|
{
|
||||||
var permissions = viewModel.PermissionValues.Where(tuple => tuple.Value).Select(tuple => tuple.Permission).ToList();
|
List<Permission> permissions = new List<Permission>();
|
||||||
|
foreach (var p in viewModel.PermissionValues.Where(tuple => tuple.Value && !tuple.Forbidden))
|
||||||
if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
|
||||||
{
|
{
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
else if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
||||||
if (viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
|
||||||
{
|
{
|
||||||
permissions.Add(Permissions.ServerManagement);
|
permissions.AddRange(viewModel.SpecificStores.Select(s => Permission.Create(Permission.CanModifyStoreSettings, s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissions.Distinct();
|
return permissions.Distinct();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> SetViewModelValues<T>(T viewModel) where T : AddApiKeyViewModel
|
private async Task<T> SetViewModelValues<T>(T viewModel) where T : AddApiKeyViewModel
|
||||||
{
|
{
|
||||||
viewModel.Stores = await _StoreRepository.GetStoresByUserId(_userManager.GetUserId(User));
|
viewModel.Stores = await _StoreRepository.GetStoresByUserId(_userManager.GetUserId(User));
|
||||||
viewModel.IsServerAdmin =
|
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded;
|
||||||
(await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
viewModel.PermissionValues ??= Permission.AllPolicies.Where(p => p != Permission.CanModifyStoreSettings)
|
||||||
viewModel.PermissionValues ??= Permissions.GetAllPermissionKeys().Where(s =>
|
.Select(s => new AddApiKeyViewModel.PermissionValueItem() { Permission = s, Value = false }).ToList();
|
||||||
!s.Contains(Permissions.StoreManagement, StringComparison.InvariantCultureIgnoreCase) &&
|
if (!isAdmin)
|
||||||
s != Permissions.ServerManagement)
|
{
|
||||||
.Select(s => new AddApiKeyViewModel.PermissionValueItem() {Permission = s, Value = true}).ToList();
|
foreach (var p in viewModel.PermissionValues)
|
||||||
|
{
|
||||||
|
if (p.Permission == Permission.CanCreateUser ||
|
||||||
|
p.Permission == Permission.CanModifyServerSettings)
|
||||||
|
{
|
||||||
|
p.Forbidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AddApiKeyViewModel
|
public class AddApiKeyViewModel
|
||||||
{
|
{
|
||||||
|
public AddApiKeyViewModel()
|
||||||
|
{
|
||||||
|
StoreManagementPermission = new PermissionValueItem()
|
||||||
|
{
|
||||||
|
Permission = Permission.CanModifyStoreSettings,
|
||||||
|
Value = false
|
||||||
|
};
|
||||||
|
StoreManagementSelectivePermission = new PermissionValueItem()
|
||||||
|
{
|
||||||
|
Permission = $"{Permission.CanModifyStoreSettings}:",
|
||||||
|
Value = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public AddApiKeyViewModel(IEnumerable<Permission> 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<Permission> 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<string>)
|
||||||
|
{
|
||||||
|
foreach (var p in SpecificStores)
|
||||||
|
{
|
||||||
|
if (Permission.TryCreatePermission(Permission.CanModifyStoreSettings, p, out var pp))
|
||||||
|
yield return pp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
public StoreData[] Stores { get; set; }
|
public StoreData[] Stores { get; set; }
|
||||||
|
ApiKeyStoreMode _StoreMode;
|
||||||
public ApiKeyStoreMode StoreMode { get; set; }
|
public ApiKeyStoreMode StoreMode { get; set; }
|
||||||
public List<string> SpecificStores { get; set; } = new List<string>();
|
public List<string> SpecificStores { get; set; } = new List<string>();
|
||||||
public bool IsServerAdmin { get; set; }
|
public PermissionValueItem StoreManagementPermission { get; set; }
|
||||||
public bool ServerManagementPermission { get; set; }
|
public PermissionValueItem StoreManagementSelectivePermission { get; set; }
|
||||||
public bool StoreManagementPermission { get; set; }
|
|
||||||
public string Command { get; set; }
|
public string Command { get; set; }
|
||||||
public List<PermissionValueItem> PermissionValues { get; set; }
|
public List<PermissionValueItem> PermissionValues { get; set; }
|
||||||
|
|
||||||
@@ -306,29 +347,52 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
public class PermissionValueItem
|
public class PermissionValueItem
|
||||||
{
|
{
|
||||||
|
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
|
||||||
|
{
|
||||||
|
{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 string Permission { get; set; }
|
||||||
public bool Value { get; set; }
|
public bool Value { get; set; }
|
||||||
|
public bool Forbidden { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
|
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
|
||||||
{
|
{
|
||||||
|
public AuthorizeApiKeysViewModel()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public AuthorizeApiKeysViewModel(IEnumerable<Permission> permissions) : base(permissions)
|
||||||
|
{
|
||||||
|
Permissions = string.Join(';', permissions.Select(p => p.ToString()).ToArray());
|
||||||
|
}
|
||||||
public string ApplicationName { get; set; }
|
public string ApplicationName { get; set; }
|
||||||
public bool Strict { get; set; }
|
public bool Strict { get; set; }
|
||||||
public bool SelectiveStores { get; set; }
|
public bool SelectiveStores { get; set; }
|
||||||
public string Permissions { get; set; }
|
public string Permissions { get; set; }
|
||||||
|
|
||||||
public string[] PermissionsFormatted
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Permissions?.Split(";", StringSplitOptions.RemoveEmptyEntries)?? Array.Empty<string>();
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Permissions = string.Join(';', value ?? Array.Empty<string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
@@ -43,7 +45,7 @@ namespace BTCPayServer.Controllers.RestApi.ApiKeys
|
|||||||
{
|
{
|
||||||
return new ApiKeyData()
|
return new ApiKeyData()
|
||||||
{
|
{
|
||||||
Permissions = data.GetPermissions(),
|
Permissions = Permission.ToPermissions(data.Permissions).Select(c => c.ToString()).ToArray(),
|
||||||
ApiKey = data.Id,
|
ApiKey = data.Id,
|
||||||
UserId = data.UserId,
|
UserId = data.UserId,
|
||||||
Label = data.Label
|
Label = data.Label
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Security.APIKeys;
|
using BTCPayServer.Security.APIKeys;
|
||||||
@@ -27,45 +28,45 @@ namespace BTCPayServer.Controllers.RestApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/id")]
|
[HttpGet("me/id")]
|
||||||
|
[Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
public string GetCurrentUserId()
|
public string GetCurrentUserId()
|
||||||
{
|
{
|
||||||
return _userManager.GetUserId(User);
|
return _userManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me")]
|
[HttpGet("me")]
|
||||||
|
[Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
public async Task<ApplicationUser> GetCurrentUser()
|
public async Task<ApplicationUser> GetCurrentUser()
|
||||||
{
|
{
|
||||||
return await _userManager.GetUserAsync(User);
|
return await _userManager.GetUserAsync(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/is-admin")]
|
[HttpGet("me/is-admin")]
|
||||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
[Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
public bool AmIAnAdmin()
|
public bool AmIAnAdmin()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/stores")]
|
[HttpGet("me/stores")]
|
||||||
[Authorize(Policy = Policies.CanListStoreSettings.Key,
|
[Authorize(Policy = Permission.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
public StoreData[] GetCurrentUserStores()
|
||||||
public async Task<StoreData[]> GetCurrentUserStores()
|
|
||||||
{
|
{
|
||||||
return await User.GetStores(_userManager, _storeRepository);
|
return this.HttpContext.GetStoresData();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/stores/actions")]
|
[HttpGet("me/stores/{storeId}/can-view")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
|
[Authorize(Policy = Permission.CanViewStoreSettings,
|
||||||
AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
public bool CanDoNonImplicitStoreActions()
|
public bool CanViewStore(string storeId)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("me/stores/{storeId}/can-edit")]
|
[HttpGet("me/stores/{storeId}/can-edit")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
|
[Authorize(Policy = Permission.CanModifyStoreSettings,
|
||||||
AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
public bool CanEdit(string storeId)
|
public bool CanEditStore(string storeId)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi.Users
|
namespace BTCPayServer.Controllers.RestApi.Users
|
||||||
{
|
{
|
||||||
@@ -54,7 +55,7 @@ namespace BTCPayServer.Controllers.RestApi.Users
|
|||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyProfile.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
[Authorize(Policy = Permission.CanViewProfile, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
[HttpGet("~/api/v1/users/me")]
|
[HttpGet("~/api/v1/users/me")]
|
||||||
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
||||||
{
|
{
|
||||||
@@ -86,26 +87,21 @@ namespace BTCPayServer.Controllers.RestApi.Users
|
|||||||
if (anyAdmin && request.IsAdministrator is true && !isAuth)
|
if (anyAdmin && request.IsAdministrator is true && !isAuth)
|
||||||
return Forbid(AuthenticationSchemes.ApiKey);
|
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
|
// 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
|
&& isAuth
|
||||||
: true;
|
: true;
|
||||||
// You need to be admin to create an admin
|
// You need to be admin to create an admin
|
||||||
if (request.IsAdministrator is true && !isAdmin)
|
if (request.IsAdministrator is true && !isAdmin)
|
||||||
return Forbid(AuthenticationSchemes.ApiKey);
|
return Forbid(AuthenticationSchemes.ApiKey);
|
||||||
|
|
||||||
var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser.Key))).Succeeded;
|
|
||||||
if (!isAdmin && policies.LockSubscription)
|
if (!isAdmin && policies.LockSubscription)
|
||||||
{
|
{
|
||||||
// If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission
|
// 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)
|
if (!isAuth || !canCreateUser)
|
||||||
return Forbid(AuthenticationSchemes.ApiKey);
|
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
|
var user = new ApplicationUser
|
||||||
{
|
{
|
||||||
UserName = request.Email,
|
UserName = request.Email,
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ using BTCPayServer.Services.Apps;
|
|||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key,
|
[Authorize(Policy = Permission.CanModifyServerSettings,
|
||||||
AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes.Cookie)]
|
AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes.Cookie)]
|
||||||
public partial class ServerController : Controller
|
public partial class ServerController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
@@ -386,7 +387,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private async Task<bool> CanUseHotWallet()
|
private async Task<bool> CanUseHotWallet()
|
||||||
{
|
{
|
||||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, BTCPayServer.Security.Policies.CanModifyServerSettings.Key)).Succeeded;
|
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded;
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
return true;
|
return true;
|
||||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
@@ -33,7 +34,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
[Route("stores")]
|
[Route("stores")]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
public partial class StoresController : Controller
|
public partial class StoresController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Net.WebSockets;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Hwi;
|
using BTCPayServer.Hwi;
|
||||||
using BTCPayServer.ModelBinders;
|
using BTCPayServer.ModelBinders;
|
||||||
@@ -127,7 +128,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);
|
await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);
|
||||||
o = JObject.Parse(await websocketHelper.NextMessageAsync(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)
|
if (!authorization.Succeeded)
|
||||||
{
|
{
|
||||||
await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);
|
await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Net.WebSockets;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.ModelBinders;
|
using BTCPayServer.ModelBinders;
|
||||||
@@ -30,7 +31,7 @@ using Newtonsoft.Json;
|
|||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[Route("wallets")]
|
[Route("wallets")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[AutoValidateAntiforgeryToken]
|
[AutoValidateAntiforgeryToken]
|
||||||
public partial class WalletsController : Controller
|
public partial class WalletsController : Controller
|
||||||
{
|
{
|
||||||
@@ -366,7 +367,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private async Task<bool> CanUseHotWallet()
|
private async Task<bool> CanUseHotWallet()
|
||||||
{
|
{
|
||||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Permission.CanModifyServerSettings)).Succeeded;
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
return true;
|
return true;
|
||||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||||
@@ -839,7 +840,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var vm = new RescanWalletModel();
|
var vm = new RescanWalletModel();
|
||||||
vm.IsFullySync = _dashboard.IsFullySynched(walletId.CryptoCode, out var unused);
|
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;
|
vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true;
|
||||||
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
|
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
|
||||||
var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation);
|
var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation);
|
||||||
@@ -869,7 +870,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{walletId}/rescan")]
|
[Route("{walletId}/rescan")]
|
||||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> WalletRescan(
|
public async Task<IActionResult> WalletRescan(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId, RescanWalletModel vm)
|
WalletId walletId, RescanWalletModel vm)
|
||||||
|
|||||||
@@ -425,6 +425,15 @@ namespace BTCPayServer
|
|||||||
ctx.Items["BTCPAY.STOREDATA"] = storeData;
|
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() };
|
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||||
public static string ToJson(this object o)
|
public static string ToJson(this object o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
@@ -44,11 +45,8 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(key.GetPermissions()
|
claims.AddRange(Permission.ToPermissions(key.Permissions).Select(permission => new Claim(APIKeyConstants.ClaimTypes.Permission, permission.ToString())));
|
||||||
.Select(permission => new Claim(APIKeyConstants.ClaimTypes.Permissions, permission)));
|
|
||||||
|
|
||||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)), APIKeyConstants.AuthenticationType));
|
new ClaimsPrincipal(new ClaimsIdentity(claims, APIKeyConstants.AuthenticationType)), APIKeyConstants.AuthenticationType));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -35,52 +36,54 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
switch (requirement.Policy)
|
switch (requirement.Policy)
|
||||||
{
|
{
|
||||||
case Policies.CanModifyProfile.Key:
|
case Permission.CanModifyProfile:
|
||||||
success = context.HasPermissions(Permissions.ProfileManagement);
|
case Permission.CanViewProfile:
|
||||||
|
success = context.HasPermission(Permission.Create(requirement.Policy));
|
||||||
break;
|
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
|
else
|
||||||
{
|
{
|
||||||
var userid = _userManager.GetUserId(context.User);
|
var stores = await _storeRepository.GetStoresByUserId(userid);
|
||||||
if (string.IsNullOrEmpty(userid))
|
List<StoreData> permissionedStores = new List<StoreData>();
|
||||||
|
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;
|
break;
|
||||||
var store = await _storeRepository.FindStore((string)storeId, userid);
|
if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin))
|
||||||
if (store == null)
|
|
||||||
break;
|
break;
|
||||||
success = true;
|
success = true;
|
||||||
_HttpContext.SetStoreData(store);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
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)
|
if (success)
|
||||||
@@ -88,15 +91,5 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Security.APIKeys
|
namespace BTCPayServer.Security.APIKeys
|
||||||
{
|
{
|
||||||
@@ -8,19 +9,7 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
|
|
||||||
public static class ClaimTypes
|
public static class ClaimTypes
|
||||||
{
|
{
|
||||||
public const string Permissions = nameof(APIKeys) + "." + nameof(Permissions);
|
public const string Permission = "APIKey.Permission";
|
||||||
}
|
|
||||||
|
|
||||||
public static class Permissions
|
|
||||||
{
|
|
||||||
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
|
|
||||||
{
|
|
||||||
{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.")},
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,22 +29,6 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<StoreData[]> GetStores(this ClaimsPrincipal claimsPrincipal,
|
|
||||||
UserManager<ApplicationUser> 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)
|
public static AuthenticationBuilder AddAPIKeyAuthentication(this AuthenticationBuilder builder)
|
||||||
{
|
{
|
||||||
builder.AddScheme<APIKeyAuthenticationOptions, APIKeyAuthenticationHandler>(AuthenticationSchemes.ApiKey,
|
builder.AddScheme<APIKeyAuthenticationOptions, APIKeyAuthenticationHandler>(AuthenticationSchemes.ApiKey,
|
||||||
@@ -62,15 +46,24 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
public static string[] GetPermissions(this AuthorizationHandlerContext context)
|
public static string[] GetPermissions(this AuthorizationHandlerContext context)
|
||||||
{
|
{
|
||||||
return context.User.Claims.Where(c =>
|
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();
|
.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 =>
|
foreach (var claim in context.User.Claims.Where(c =>
|
||||||
c.Type.Equals(APIKeyConstants.ClaimTypes.Permissions, StringComparison.InvariantCultureIgnoreCase) &&
|
c.Type.Equals(APIKeyConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
c.Value.Split(' ').Contains(s)));
|
{
|
||||||
|
if (Permission.TryParse(claim.Value, out var claimPermission))
|
||||||
|
{
|
||||||
|
if (claimPermission.Contains(permission))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Routing;
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Security.Bitpay
|
namespace BTCPayServer.Security.Bitpay
|
||||||
{
|
{
|
||||||
@@ -54,7 +55,7 @@ namespace BTCPayServer.Security.Bitpay
|
|||||||
var anyoneCanInvoice = store.GetStoreBlob().AnyoneCanInvoice;
|
var anyoneCanInvoice = store.GetStoreBlob().AnyoneCanInvoice;
|
||||||
switch (requirement.Policy)
|
switch (requirement.Policy)
|
||||||
{
|
{
|
||||||
case Policies.CanCreateInvoice.Key:
|
case Permission.CanCreateInvoice:
|
||||||
if (!isAnonymous || (isAnonymous && anyoneCanInvoice))
|
if (!isAnonymous || (isAnonymous && anyoneCanInvoice))
|
||||||
{
|
{
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ namespace BTCPayServer.Security
|
|||||||
var isAdmin = context.User.IsInRole(Roles.ServerAdmin);
|
var isAdmin = context.User.IsInRole(Roles.ServerAdmin);
|
||||||
switch (requirement.Policy)
|
switch (requirement.Policy)
|
||||||
{
|
{
|
||||||
case Policies.CanModifyServerSettings.Key:
|
case Permission.CanModifyServerSettings:
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
return;
|
return;
|
||||||
@@ -56,11 +57,11 @@ namespace BTCPayServer.Security
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
switch (requirement.Policy)
|
switch (requirement.Policy)
|
||||||
{
|
{
|
||||||
case Policies.CanModifyStoreSettings.Key:
|
case Permission.CanModifyStoreSettings:
|
||||||
if (store.Role == StoreRoles.Owner || isAdmin)
|
if (store.Role == StoreRoles.Owner || isAdmin)
|
||||||
success = true;
|
success = true;
|
||||||
break;
|
break;
|
||||||
case Policies.CanCreateInvoice.Key:
|
case Permission.CanCreateInvoice:
|
||||||
if (store.Role == StoreRoles.Owner ||
|
if (store.Role == StoreRoles.Owner ||
|
||||||
store.Role == StoreRoles.Guest ||
|
store.Role == StoreRoles.Guest ||
|
||||||
isAdmin ||
|
isAdmin ||
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using BTCPayServer.Client;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
@@ -6,14 +7,11 @@ namespace BTCPayServer.Security
|
|||||||
{
|
{
|
||||||
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
||||||
{
|
{
|
||||||
options.AddPolicy(CanModifyStoreSettings.Key);
|
foreach (var p in Permission.AllPolicies)
|
||||||
options.AddPolicy(CanListStoreSettings.Key);
|
{
|
||||||
options.AddPolicy(CanCreateInvoice.Key);
|
options.AddPolicy(p);
|
||||||
|
}
|
||||||
options.AddPolicy(CanGetRates.Key);
|
options.AddPolicy(CanGetRates.Key);
|
||||||
options.AddPolicy(CanModifyServerSettings.Key);
|
|
||||||
options.AddPolicy(CanModifyServerSettings.Key);
|
|
||||||
options.AddPolicy(CanModifyProfile.Key);
|
|
||||||
options.AddPolicy(CanCreateUser.Key);
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,36 +19,9 @@ namespace BTCPayServer.Security
|
|||||||
{
|
{
|
||||||
options.AddPolicy(policy, o => o.AddRequirements(new PolicyRequirement(policy)));
|
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 class CanGetRates
|
||||||
{
|
{
|
||||||
public const string Key = "btcpay.store.cangetrates";
|
public const string Key = "btcpay.store.cangetrates";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CanCreateUser
|
|
||||||
{
|
|
||||||
public const string Key = "btcpay.store.cancreateuser";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,15 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Altcoins.Monero.UI
|
namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||||
{
|
{
|
||||||
[Route("stores/{storeId}/monerolike")]
|
[Route("stores/{storeId}/monerolike")]
|
||||||
[OnlyIfSupportAttribute("XMR")]
|
[OnlyIfSupportAttribute("XMR")]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Permission.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public class MoneroLikeStoreController : Controller
|
public class MoneroLikeStoreController : Controller
|
||||||
{
|
{
|
||||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@namespace BTCPayServer.Client
|
||||||
@model BTCPayServer.Controllers.ManageController.ApiKeysViewModel
|
@model BTCPayServer.Controllers.ManageController.ApiKeysViewModel
|
||||||
@{
|
@{
|
||||||
ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Manage your API Keys");
|
ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Manage your API Keys");
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span>@string.Join(", ", keyData.GetPermissions())</span>
|
<span>@string.Join(", ", Permission.ToPermissions(keyData.Permissions).Select(c => c.ToString()).Distinct().ToArray())</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
|||||||
@@ -5,16 +5,6 @@
|
|||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Add API Key");
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<h4>@ViewData["Title"]</h4>
|
<h4>@ViewData["Title"]</h4>
|
||||||
@@ -26,53 +16,47 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<form method="post" asp-action="AddApiKey" class="list-group">
|
<form method="post" asp-action="AddApiKey" class="list-group">
|
||||||
|
|
||||||
<input type="hidden" asp-for="StoreMode" value="@Model.StoreMode"/>
|
<input type="hidden" asp-for="StoreMode" value="@Model.StoreMode" />
|
||||||
<div asp-validation-summary="All" class="text-danger"></div>
|
<div asp-validation-summary="All" class="text-danger"></div>
|
||||||
|
|
||||||
<div class="list-group-item ">
|
<div class="list-group-item ">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Label"></label>
|
<label asp-for="Label"></label>
|
||||||
<input asp-for="Label" class="form-control"/>
|
<input asp-for="Label" class="form-control" />
|
||||||
<span asp-validation-for="Label" class="text-danger"></span>
|
<span asp-validation-for="Label" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.IsServerAdmin)
|
|
||||||
{
|
|
||||||
<div class="list-group-item form-group">
|
|
||||||
<input asp-for="ServerManagementPermission" class="form-check-inline"/>
|
|
||||||
<label asp-for="ServerManagementPermission" class="h5">@GetTitle(Permissions.ServerManagement)</label>
|
|
||||||
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
|
||||||
<p>@GetDescription(Permissions.ServerManagement).</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
||||||
{
|
{
|
||||||
|
@if (!Model.PermissionValues[i].Forbidden)
|
||||||
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
||||||
<input type="checkbox" asp-for="PermissionValues[i].Value" class="form-check-inline"/>
|
<input id="@Model.PermissionValues[i].Permission" type="checkbox" asp-for="PermissionValues[i].Value" class="form-check-inline" />
|
||||||
<label asp-for="PermissionValues[i].Value" class="h5">@GetTitle(Model.PermissionValues[i].Permission)</label>
|
<label asp-for="PermissionValues[i].Value" class="h5">@Model.PermissionValues[i].Title</label>
|
||||||
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
|
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
|
||||||
<p>@GetDescription(Model.PermissionValues[i].Permission).</p>
|
<p>@Model.PermissionValues[i].Description</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
@Html.CheckBoxFor(model => model.StoreManagementPermission, new Dictionary<string, string>() {{"class", "form-check-inline"}})
|
<input id="@Model.StoreManagementPermission.Permission" type="checkbox" asp-for="@Model.StoreManagementPermission.Value" class="form-check-inline" />
|
||||||
|
|
||||||
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(Permissions.StoreManagement)</label>
|
<label asp-for="StoreManagementPermission" class="h5">@Model.StoreManagementPermission.Title</label>
|
||||||
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
||||||
<p class="mb-0">@GetDescription(Permissions.StoreManagement).</p>
|
<p class="mb-0">@Model.StoreManagementPermission.Description</p>
|
||||||
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to specific stores instead</button>
|
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to specific stores instead</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
else if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
||||||
{
|
{
|
||||||
<div class="list-group-item p-0 border-0 mb-2">
|
<div class="list-group-item p-0 border-0 mb-2">
|
||||||
<li class="list-group-item ">
|
<li class="list-group-item ">
|
||||||
<h5 class="mb-1">@GetTitle(Permissions.StoreManagement + ":")</h5>
|
<h5 class="mb-1">@Model.StoreManagementSelectivePermission.Title</h5>
|
||||||
<p class="mb-0">@GetDescription(Permissions.StoreManagement + ":").</p>
|
<p class="mb-0">@Model.StoreManagementSelectivePermission.Description</p>
|
||||||
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to all stores instead</button>
|
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to all stores instead</button>
|
||||||
</li>
|
</li>
|
||||||
@if (!Model.Stores.Any())
|
@if (!Model.Stores.Any())
|
||||||
|
|||||||
@@ -6,24 +6,17 @@
|
|||||||
@{
|
@{
|
||||||
Layout = "_Layout";
|
Layout = "_Layout";
|
||||||
ViewData["Title"] = $"Authorize {(Model.ApplicationName ?? "Application")}";
|
ViewData["Title"] = $"Authorize {(Model.ApplicationName ?? "Application")}";
|
||||||
|
var permissions = Permission.ToPermissions(Model.Permissions);
|
||||||
string GetDescription(string permission)
|
var hasStorePermission = permissions.Any(p => p.Policy == Permission.CanModifyStoreSettings);
|
||||||
{
|
|
||||||
return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description;
|
|
||||||
}
|
|
||||||
|
|
||||||
string GetTitle(string permission)
|
|
||||||
{
|
|
||||||
return APIKeyConstants.Permissions.PermissionDescriptions[permission].Description;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<partial name="_StatusMessage"/>
|
<partial name="_StatusMessage"/>
|
||||||
<form method="post" asp-action="AuthorizeAPIKey">
|
<form method="post" asp-action="AuthorizeAPIKey">
|
||||||
<input type="hidden" asp-for="Permissions" value="@Model.Permissions"/>
|
<input type="hidden" asp-for="Permissions" value="@Model.Permissions" />
|
||||||
<input type="hidden" asp-for="Strict" value="@Model.Strict"/>
|
<input type="hidden" asp-for="Strict" value="@Model.Strict" />
|
||||||
<input type="hidden" asp-for="ApplicationName" value="@Model.ApplicationName"/>
|
<input type="hidden" asp-for="ApplicationName" value="@Model.ApplicationName" />
|
||||||
<input type="hidden" asp-for="SelectiveStores" value="@Model.SelectiveStores"/>
|
<input type="hidden" asp-for="SelectiveStores" value="@Model.SelectiveStores" />
|
||||||
|
<input type="hidden" asp-for="StoreMode" value="@Model.StoreMode" />
|
||||||
<section>
|
<section>
|
||||||
<div class="card container">
|
<div class="card container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -39,76 +32,60 @@
|
|||||||
<div class="list-group-item ">
|
<div class="list-group-item ">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Label"></label>
|
<label asp-for="Label"></label>
|
||||||
<input asp-for="Label" class="form-control"/>
|
<input asp-for="Label" class="form-control" />
|
||||||
<span asp-validation-for="Label" class="text-danger"></span>
|
<span asp-validation-for="Label" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!Model.PermissionsFormatted.Any())
|
@if (!permissions.Any())
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
<p >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.</p>
|
<p>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.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.PermissionsFormatted.Contains(Permissions.ServerManagement) && (Model.IsServerAdmin || Model.Strict))
|
|
||||||
{
|
|
||||||
<div class="list-group-item form-group">
|
|
||||||
@if (Model.Strict || !Model.IsServerAdmin)
|
|
||||||
{
|
|
||||||
<input type="hidden" asp-for="ServerManagementPermission"/>
|
|
||||||
<input type="checkbox" class="form-check-inline" checked="@Model.ServerManagementPermission" disabled/>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input type="checkbox" asp-for="ServerManagementPermission" class="form-check-inline"/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<label asp-for="ServerManagementPermission" class="h5">@GetTitle(Permissions.ServerManagement)</label>
|
|
||||||
@if (!Model.IsServerAdmin)
|
|
||||||
{
|
|
||||||
<span class="text-danger">
|
|
||||||
The server management permission is being requested but your account is not an administrator
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
|
||||||
<p>@GetDescription(Permissions.ServerManagement).</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
||||||
@if (Model.Strict || !Model.IsServerAdmin)
|
@if (Model.Strict)
|
||||||
{
|
{
|
||||||
<input type="hidden" asp-for="PermissionValues[i].Value"/>
|
<input id="@Model.PermissionValues[i].Permission" type="hidden" asp-for="PermissionValues[i].Value" />
|
||||||
<input type="checkbox" class="form-check-inline" checked="@Model.PermissionValues[i].Value" disabled/>
|
<input type="checkbox" class="form-check-inline" checked="@Model.PermissionValues[i].Value" disabled />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input type="checkbox" asp-for="PermissionValues[i].Value" class="form-check-inline"/>
|
<input id="@Model.PermissionValues[i].Permission" type="checkbox" asp-for="PermissionValues[i].Value" class="form-check-inline" />
|
||||||
}
|
}
|
||||||
<label asp-for="PermissionValues[i].Value" class="h5">@GetTitle(Model.PermissionValues[i].Permission)</label>
|
<label asp-for="PermissionValues[i].Value" class="h5">@Model.PermissionValues[i].Title</label>
|
||||||
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
|
|
||||||
<p>@GetDescription(Model.PermissionValues[i].Permission).</p>
|
@if (Model.PermissionValues[i].Forbidden)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<span class="text-danger">
|
||||||
|
This permission is not available for your account.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
<p>@Model.PermissionValues[i].Description</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.PermissionsFormatted.Contains(Permissions.StoreManagement))
|
@if (hasStorePermission)
|
||||||
{
|
{
|
||||||
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
@if (Model.Strict)
|
@if (Model.Strict)
|
||||||
{
|
{
|
||||||
<input type="hidden" asp-for="StoreManagementPermission"/>
|
<input id="@Model.StoreManagementPermission.Permission" type="hidden" asp-for="StoreManagementPermission.Value" />
|
||||||
<input type="checkbox" class="form-check-inline" checked="@Model.StoreManagementPermission" disabled/>
|
<input type="checkbox" class="form-check-inline" checked="@Model.StoreManagementPermission.Value" disabled />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input type="checkbox" asp-for="StoreManagementPermission" class="form-check-inline"/>
|
<input id="@Model.StoreManagementPermission.Permission" type="checkbox" asp-for="StoreManagementPermission.Value" class="form-check-inline" />
|
||||||
}
|
}
|
||||||
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(Permissions.StoreManagement)</label>
|
<label asp-for="StoreManagementPermission" class="h5">@Model.StoreManagementPermission.Title</label>
|
||||||
|
<br />
|
||||||
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
||||||
<p class="mb-0">@GetDescription(Permissions.StoreManagement).</p>
|
<p class="mb-0">@Model.StoreManagementPermission.Description</p>
|
||||||
@if (Model.SelectiveStores)
|
@if (Model.SelectiveStores)
|
||||||
{
|
{
|
||||||
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to specific stores instead</button>
|
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to specific stores instead</button>
|
||||||
@@ -119,8 +96,8 @@
|
|||||||
{
|
{
|
||||||
<div class="list-group-item p-0 border-0 mb-2">
|
<div class="list-group-item p-0 border-0 mb-2">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<h5 class="mb-1">@GetTitle(Permissions.StoreManagement + ":")</h5>
|
<h5 class="mb-1">@Model.StoreManagementSelectivePermission.Title</h5>
|
||||||
<p class="mb-0">@GetDescription(Permissions.StoreManagement + ":").</p>
|
<p class="mb-0">@Model.StoreManagementSelectivePermission.Description</p>
|
||||||
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to all stores instead</button>
|
<button type="submit" class="btn btn-link" name="command" value="change-store-mode">Give permission to all stores instead</button>
|
||||||
</li>
|
</li>
|
||||||
@if (!Model.Stores.Any())
|
@if (!Model.Stores.Any())
|
||||||
|
|||||||
Reference in New Issue
Block a user