btcpayserver/BTCPayServer.Client/Permissions.cs

315 lines
13 KiB
C#
Raw Normal View History

2020-06-29 04:44:35 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
namespace BTCPayServer.Client
{
public class Policies
{
public const string CanViewLightningInvoiceInternalNode = "btcpay.server.canviewlightninginvoiceinternalnode";
public const string CanCreateLightningInvoiceInternalNode = "btcpay.server.cancreatelightninginvoiceinternalnode";
public const string CanViewLightningInvoiceInStore = "btcpay.store.canviewlightninginvoice";
public const string CanCreateLightningInvoiceInStore = "btcpay.store.cancreatelightninginvoice";
public const string CanUseInternalLightningNode = "btcpay.server.canuseinternallightningnode";
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
2020-03-19 11:11:15 +01:00
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
2020-11-13 06:01:51 +01:00
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
2020-03-19 11:11:15 +01:00
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
2020-07-22 13:58:41 +02:00
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
2020-03-19 11:11:15 +01:00
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices";
public const string CanViewPaymentRequests = "btcpay.store.canviewpaymentrequests";
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
2020-03-19 11:11:15 +01:00
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
public const string CanViewProfile = "btcpay.user.canviewprofile";
public const string CanManageNotificationsForUser = "btcpay.user.canmanagenotificationsforuser";
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
public const string CanViewUsers = "btcpay.server.canviewusers";
2020-03-19 11:11:15 +01:00
public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanManageUsers = "btcpay.server.canmanageusers";
2021-04-08 05:40:57 +02:00
public const string CanDeleteUser = "btcpay.user.candeleteuser";
2020-06-24 03:34:09 +02:00
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
Exchange api no kraken (#3679) * WIP New APIs for dealing with custodians/exchanges * Simplified things * More API refinements + index.html file for quick viewing * Finishing touches on spec * Switched cryptoCode to paymentMethod as this allows us to differentiate between onchain and lightning * Moved draft API docs to "/docs-draft" * WIP baby steps * Added DB migration for CustodianAccountData * Rough but working POST /v1/api/custodian-account + GET /v1/api/custodian * WIP + early Kraken API client * Moved service registration to proper location * Working create + list custodian accounts + permissions + WIP Kraken client * Kraken API Balances call is working * Added asset balances to response * List Custodian Accounts call does not load assetBalances by default, because it can fail. Can be requested when needed. * Call to get the details of 1 specific custodian account * Added permissions to swagger * Added "tradableAssetPairs" to Kraken custodian response + cache the tradable pairs in memory for 24 hours * Removed unused file * WIP + Moved files to better locations * Updated docs * Working API endpoint to get info on a trade (same response as creating a new trade) * Working API endpoints for Deposit + Trade + untested Withdraw * Delete custodian account * Trading works, better error handling, cleanup * Working withdrawals + New endpoint for getting bid/ask prices * Completed withdrawals + new endpoint for getting info on a past withdrawal to simplify testing, Enums are output as strings, * Better error handling when withdrawing to a wrong destination * WithdrawalAddressName in config is now a string per currency (dictionary) * Added TODOs * Only show the custodian account "config" to users who are allowed * Added the new permissions to the API Keys UI * Renamed KrakenClient to KrakenExchange * WIP Kraken Config Form * Removed files for UI again, will make separate PR later * Fixed docs + Refactored to use PaymentMethod more + Added "name" to custodian account + Using cancelationToken everywhere * Updated withdrawal info docs * First unit test * Complete tests for /api/v1/custodians and /api/v1/custodian-accounts endpoints + Various improvements and fixes * Mock custodian and more exceptions * Many more tests + cleanup, moved files to better locations * More tests * WIP more tests * Greenfield API tests complete * Added missing "Name" column * Cleanup, TODOs and beginning of Kraken Tests * Added Kraken tests using public endpoints + handling of "SATS" currency * Added 1st mocked Kraken API call: GetAssetBalancesAsync * Added assert for bad config * Mocked more Kraken API responses + added CreationDate to withdrawal response * pr review club changes * Make Kraken Custodian a plugin * Re-added User-Agent header as it is required * Fixed bug in market trade on Kraken using a percentage as qty * A short delay so Kraken has the time to execute the market order and we don't fetch the details too quickly. * Merged the draft swagger into the main swagger since it didn't work anymore * Fixed API permissions test * Removed 2 TODOs * Fixed unit test * Remove Kraken Api as it should be separate opt-in plugin * Flatten namespace hierarchy and use InnerExeption instead of OriginalException * Remove useless line * Make sure account is from a specific store * Proper error if custodian code not found * Remove various warnings * Remove various warnings * Handle CustodianApiException through an exception filter * Store custodian-account blob directly * Remove duplications, transform methods into property * Improve docs tags * Make sure the custodianCode saved is canonical * Fix test Co-authored-by: Wouter Samaey <wouter.samaey@storefront.be> Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-05-18 07:59:56 +02:00
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
public const string CanDepositToCustodianAccounts = "btcpay.store.candeposittocustodianaccount";
public const string CanWithdrawFromCustodianAccounts = "btcpay.store.canwithdrawfromcustodianaccount";
public const string CanTradeCustodianAccount = "btcpay.store.cantradecustodianaccount";
2020-03-19 11:11:15 +01:00
public const string Unrestricted = "unrestricted";
public static IEnumerable<string> AllPolicies
{
2020-03-19 11:11:15 +01:00
get
{
2020-07-22 13:58:41 +02:00
yield return CanViewInvoices;
2020-03-19 11:11:15 +01:00
yield return CanCreateInvoice;
yield return CanModifyInvoices;
2020-11-13 06:01:51 +01:00
yield return CanModifyStoreWebhooks;
2020-03-19 11:11:15 +01:00
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
yield return CanViewStoreSettings;
yield return CanViewPaymentRequests;
yield return CanModifyPaymentRequests;
2020-03-19 11:11:15 +01:00
yield return CanModifyProfile;
yield return CanViewProfile;
yield return CanViewUsers;
2020-03-19 11:11:15 +01:00
yield return CanCreateUser;
2021-03-10 03:21:33 +01:00
yield return CanDeleteUser;
yield return CanManageNotificationsForUser;
yield return CanViewNotificationsForUser;
2020-03-19 11:11:15 +01:00
yield return Unrestricted;
yield return CanUseInternalLightningNode;
yield return CanViewLightningInvoiceInternalNode;
yield return CanCreateLightningInvoiceInternalNode;
yield return CanUseLightningNodeInStore;
yield return CanViewLightningInvoiceInStore;
yield return CanCreateLightningInvoiceInStore;
2020-06-24 03:34:09 +02:00
yield return CanManagePullPayments;
yield return CanCreatePullPayments;
yield return CanCreateNonApprovedPullPayments;
Exchange api no kraken (#3679) * WIP New APIs for dealing with custodians/exchanges * Simplified things * More API refinements + index.html file for quick viewing * Finishing touches on spec * Switched cryptoCode to paymentMethod as this allows us to differentiate between onchain and lightning * Moved draft API docs to "/docs-draft" * WIP baby steps * Added DB migration for CustodianAccountData * Rough but working POST /v1/api/custodian-account + GET /v1/api/custodian * WIP + early Kraken API client * Moved service registration to proper location * Working create + list custodian accounts + permissions + WIP Kraken client * Kraken API Balances call is working * Added asset balances to response * List Custodian Accounts call does not load assetBalances by default, because it can fail. Can be requested when needed. * Call to get the details of 1 specific custodian account * Added permissions to swagger * Added "tradableAssetPairs" to Kraken custodian response + cache the tradable pairs in memory for 24 hours * Removed unused file * WIP + Moved files to better locations * Updated docs * Working API endpoint to get info on a trade (same response as creating a new trade) * Working API endpoints for Deposit + Trade + untested Withdraw * Delete custodian account * Trading works, better error handling, cleanup * Working withdrawals + New endpoint for getting bid/ask prices * Completed withdrawals + new endpoint for getting info on a past withdrawal to simplify testing, Enums are output as strings, * Better error handling when withdrawing to a wrong destination * WithdrawalAddressName in config is now a string per currency (dictionary) * Added TODOs * Only show the custodian account "config" to users who are allowed * Added the new permissions to the API Keys UI * Renamed KrakenClient to KrakenExchange * WIP Kraken Config Form * Removed files for UI again, will make separate PR later * Fixed docs + Refactored to use PaymentMethod more + Added "name" to custodian account + Using cancelationToken everywhere * Updated withdrawal info docs * First unit test * Complete tests for /api/v1/custodians and /api/v1/custodian-accounts endpoints + Various improvements and fixes * Mock custodian and more exceptions * Many more tests + cleanup, moved files to better locations * More tests * WIP more tests * Greenfield API tests complete * Added missing "Name" column * Cleanup, TODOs and beginning of Kraken Tests * Added Kraken tests using public endpoints + handling of "SATS" currency * Added 1st mocked Kraken API call: GetAssetBalancesAsync * Added assert for bad config * Mocked more Kraken API responses + added CreationDate to withdrawal response * pr review club changes * Make Kraken Custodian a plugin * Re-added User-Agent header as it is required * Fixed bug in market trade on Kraken using a percentage as qty * A short delay so Kraken has the time to execute the market order and we don't fetch the details too quickly. * Merged the draft swagger into the main swagger since it didn't work anymore * Fixed API permissions test * Removed 2 TODOs * Fixed unit test * Remove Kraken Api as it should be separate opt-in plugin * Flatten namespace hierarchy and use InnerExeption instead of OriginalException * Remove useless line * Make sure account is from a specific store * Proper error if custodian code not found * Remove various warnings * Remove various warnings * Handle CustodianApiException through an exception filter * Store custodian-account blob directly * Remove duplications, transform methods into property * Improve docs tags * Make sure the custodianCode saved is canonical * Fix test Co-authored-by: Wouter Samaey <wouter.samaey@storefront.be> Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-05-18 07:59:56 +02:00
yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts;
yield return CanDepositToCustodianAccounts;
yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount;
yield return CanManageUsers;
2020-03-19 11:11:15 +01:00
}
}
public static bool IsValidPolicy(string policy)
{
return AllPolicies.Any(p => p.Equals(policy, StringComparison.OrdinalIgnoreCase));
}
public static bool IsStorePolicy(string policy)
{
return policy.StartsWith("btcpay.store", StringComparison.OrdinalIgnoreCase);
}
public static bool IsStoreModifyPolicy(string policy)
{
return policy.StartsWith("btcpay.store.canmodify", StringComparison.OrdinalIgnoreCase);
}
2020-03-23 14:17:17 +01:00
public static bool IsServerPolicy(string policy)
{
return policy.StartsWith("btcpay.server", StringComparison.OrdinalIgnoreCase);
}
public static bool IsPluginPolicy(string policy)
{
return policy.StartsWith("btcpay.plugin", StringComparison.OrdinalIgnoreCase);
}
public static bool IsUserPolicy(string policy)
{
return policy.StartsWith("btcpay.user", StringComparison.OrdinalIgnoreCase);
}
}
public class PermissionSet
{
public PermissionSet() : this(Array.Empty<Permission>())
{
}
public PermissionSet(Permission[] permissions)
{
Permissions = permissions;
}
public Permission[] Permissions { get; }
public bool Contains(Permission requestedPermission)
{
return Permissions.Any(p => p.Contains(requestedPermission));
}
public bool Contains(string permission, string store)
{
if (permission is null)
throw new ArgumentNullException(nameof(permission));
if (store is null)
throw new ArgumentNullException(nameof(store));
return Contains(Permission.Create(permission, store));
}
}
public class Permission
{
static Permission()
{
Init();
}
public static Permission Create(string policy, string scope = null)
2020-03-19 11:11:15 +01:00
{
if (TryCreatePermission(policy, scope, out var r))
2020-03-19 11:11:15 +01:00
return r;
throw new ArgumentException("Invalid Permission");
}
public static bool TryCreatePermission(string policy, string scope, out Permission permission)
2020-03-19 11:11:15 +01:00
{
permission = null;
if (policy == null)
throw new ArgumentNullException(nameof(policy));
policy = policy.Trim().ToLowerInvariant();
if (!Policies.IsValidPolicy(policy))
2020-03-19 11:11:15 +01:00
return false;
if (!string.IsNullOrEmpty(scope) && !Policies.IsStorePolicy(policy))
2020-03-19 11:11:15 +01:00
return false;
permission = new Permission(policy, scope);
2020-03-19 11:11:15 +01:00
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 (!Policies.IsValidPolicy(str))
2020-03-19 11:11:15 +01:00
return false;
permission = new Permission(str, null);
return true;
}
else
{
var policy = str.Substring(0, separator).ToLowerInvariant();
if (!Policies.IsValidPolicy(policy))
2020-03-19 11:11:15 +01:00
return false;
if (!Policies.IsStorePolicy(policy))
2020-03-19 11:11:15 +01:00
return false;
var storeId = str.Substring(separator + 1);
if (storeId.Length == 0)
return false;
permission = new Permission(policy, storeId);
return true;
}
}
internal Permission(string policy, string scope)
2020-03-19 11:11:15 +01:00
{
Policy = policy;
Scope = scope;
2020-03-19 11:11:15 +01:00
}
public bool Contains(Permission subpermission)
{
if (subpermission is null)
throw new ArgumentNullException(nameof(subpermission));
if (!ContainsPolicy(subpermission.Policy))
{
return false;
}
if (!Policies.IsStorePolicy(subpermission.Policy))
2020-03-19 11:11:15 +01:00
return true;
return Scope == null || subpermission.Scope == Scope;
2020-03-19 11:11:15 +01:00
}
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;
}
}
private bool ContainsPolicy(string subpolicy)
{
return ContainsPolicy(Policy, subpolicy);
}
private static bool ContainsPolicy(string policy, string subpolicy)
{
if (policy == Policies.Unrestricted)
2020-03-19 11:11:15 +01:00
return true;
if (policy == subpolicy)
2020-03-19 11:11:15 +01:00
return true;
if (!PolicyMap.TryGetValue(policy, out var subPolicies))
return false;
return subPolicies.Contains(subpolicy) || subPolicies.Any(s => ContainsPolicy(s, subpolicy));
}
private static Dictionary<string, HashSet<string>> PolicyMap = new();
private static void Init()
{
PolicyHasChild(Policies.CanModifyStoreSettings,
Policies.CanManageCustodianAccounts,
Policies.CanManagePullPayments,
Policies.CanModifyInvoices,
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanUseLightningNodeInStore);
PolicyHasChild(Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments);
PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile);
PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser);
PolicyHasChild(Policies.CanModifyServerSettings,
Policies.CanUseInternalLightningNode,
Policies.CanManageUsers);
PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
}
private static void PolicyHasChild(string policy, params string[] subPolicies)
{
if (PolicyMap.TryGetValue(policy, out var existingSubPolicies))
2020-06-28 10:55:27 +02:00
{
foreach (string subPolicy in subPolicies)
{
existingSubPolicies.Add(subPolicy);
}
}
else
{
PolicyMap.Add(policy, subPolicies.ToHashSet());
}
2020-03-19 11:11:15 +01:00
}
public string Scope { get; }
2020-03-19 11:11:15 +01:00
public string Policy { get; }
public override string ToString()
{
return Scope != null ? $"{Policy}:{Scope}" : Policy;
2020-03-19 11:11:15 +01:00
}
public override bool Equals(object obj)
{
Permission item = obj as Permission;
return item != null && ToString().Equals(item.ToString());
2020-03-19 11:11:15 +01:00
}
public static bool operator ==(Permission a, Permission b)
{
if (ReferenceEquals(a, b))
2020-03-19 11:11:15 +01:00
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();
}
}
}