mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Merge pull request #1387 from Kukks/api/users-get-current
Greenfield API: Get current User
This commit is contained in:
commit
b5664dac81
22 changed files with 490 additions and 163 deletions
11
BTCPayServer.Client/BTCPayServer.Client.csproj
Normal file
11
BTCPayServer.Client/BTCPayServer.Client.csproj
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Json" Version="4.7.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
22
BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
Normal file
22
BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public partial class BTCPayServerClient
|
||||||
|
{
|
||||||
|
public virtual async Task<ApiKeyData> GetCurrentAPIKeyInfo(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token);
|
||||||
|
return await HandleResponse<ApiKeyData>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
|
||||||
|
HandleResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
BTCPayServer.Client/BTCPayServerClient.Authorization.cs
Normal file
24
BTCPayServer.Client/BTCPayServerClient.Authorization.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public partial class BTCPayServerClient
|
||||||
|
{
|
||||||
|
|
||||||
|
public static Uri GenerateAuthorizeUri(Uri btcpayHost, string[] permissions, bool strict = true,
|
||||||
|
bool selectiveStores = false)
|
||||||
|
{
|
||||||
|
var result = new UriBuilder(btcpayHost);
|
||||||
|
result.Path = "api-keys/authorize";
|
||||||
|
|
||||||
|
AppendPayloadToQuery(result,
|
||||||
|
new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{"strict", strict}, {"selectiveStores", selectiveStores}, {"permissions", permissions}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.Uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
BTCPayServer.Client/BTCPayServerClient.Users.cs
Normal file
15
BTCPayServer.Client/BTCPayServerClient.Users.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public partial class BTCPayServerClient
|
||||||
|
{
|
||||||
|
public virtual async Task<ApplicationUserData> GetCurrentUser(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
|
||||||
|
return await HandleResponse<ApplicationUserData>(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
BTCPayServer.Client/BTCPayServerClient.cs
Normal file
96
BTCPayServer.Client/BTCPayServerClient.cs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public partial class BTCPayServerClient
|
||||||
|
{
|
||||||
|
private readonly string _apiKey;
|
||||||
|
private readonly Uri _btcpayHost;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
private readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
public BTCPayServerClient(Uri btcpayHost, string APIKey, HttpClient httpClient = null)
|
||||||
|
{
|
||||||
|
_apiKey = APIKey;
|
||||||
|
_btcpayHost = btcpayHost;
|
||||||
|
_httpClient = httpClient ?? new HttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void HandleResponse(HttpResponseMessage message)
|
||||||
|
{
|
||||||
|
message.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> HandleResponse<T>(HttpResponseMessage message)
|
||||||
|
{
|
||||||
|
HandleResponse(message);
|
||||||
|
return JsonSerializer.Deserialize<T>(await message.Content.ReadAsStringAsync(), _serializerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||||
|
Dictionary<string, object> queryPayload = null,
|
||||||
|
HttpMethod method = null)
|
||||||
|
{
|
||||||
|
UriBuilder uriBuilder = new UriBuilder(_btcpayHost) {Path = path};
|
||||||
|
if (queryPayload != null && queryPayload.Any())
|
||||||
|
{
|
||||||
|
AppendPayloadToQuery(uriBuilder, queryPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpRequest = new HttpRequestMessage(method ?? HttpMethod.Get, uriBuilder.Uri);
|
||||||
|
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("token", _apiKey);
|
||||||
|
|
||||||
|
|
||||||
|
return httpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual HttpRequestMessage CreateHttpRequest<T>(string path,
|
||||||
|
Dictionary<string, object> queryPayload = null,
|
||||||
|
T bodyPayload = default, HttpMethod method = null)
|
||||||
|
{
|
||||||
|
var request = CreateHttpRequest(path, queryPayload, method);
|
||||||
|
if (typeof(T).IsPrimitive || !EqualityComparer<T>.Default.Equals(bodyPayload, default(T)))
|
||||||
|
{
|
||||||
|
request.Content = new StringContent(JsonSerializer.Serialize(bodyPayload, _serializerOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendPayloadToQuery(UriBuilder uri, Dictionary<string, object> payload)
|
||||||
|
{
|
||||||
|
if (uri.Query.Length > 1)
|
||||||
|
uri.Query += "&";
|
||||||
|
foreach (KeyValuePair<string, object> keyValuePair in payload)
|
||||||
|
{
|
||||||
|
UriBuilder uriBuilder = uri;
|
||||||
|
if (keyValuePair.Value.GetType().GetInterfaces().Contains((typeof(IEnumerable))))
|
||||||
|
{
|
||||||
|
foreach (var item in (IEnumerable)keyValuePair.Value)
|
||||||
|
{
|
||||||
|
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
|
||||||
|
Uri.EscapeDataString(item.ToString()) + "&";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
|
||||||
|
Uri.EscapeDataString(keyValuePair.Value.ToString()) + "&";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uri.Query = uri.Query.Trim('&');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
BTCPayServer.Client/Models/ApiKeyData.cs
Normal file
10
BTCPayServer.Client/Models/ApiKeyData.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace BTCPayServer.Client.Models
|
||||||
|
{
|
||||||
|
public class ApiKeyData
|
||||||
|
{
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string[] Permissions { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
BTCPayServer.Client/Models/ApplicationUserData.cs
Normal file
8
BTCPayServer.Client/Models/ApplicationUserData.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace BTCPayServer.Client.Models
|
||||||
|
{
|
||||||
|
public class ApplicationUserData
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
29
BTCPayServer.Client/Permissions.cs
Normal file
29
BTCPayServer.Client/Permissions.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public static class Permissions
|
||||||
|
{
|
||||||
|
public const string ServerManagement = nameof(ServerManagement);
|
||||||
|
public const string StoreManagement = nameof(StoreManagement);
|
||||||
|
public const string ProfileManagement = nameof(ProfileManagement);
|
||||||
|
|
||||||
|
public static string[] GetAllPermissionKeys()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
ServerManagement,
|
||||||
|
StoreManagement,
|
||||||
|
ProfileManagement
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static string GetStorePermission(string storeId) => $"{nameof(StoreManagement)}:{storeId}";
|
||||||
|
|
||||||
|
public static IEnumerable<string> ExtractStorePermissionsIds(IEnumerable<string> permissions) => permissions
|
||||||
|
.Where(s => s.StartsWith($"{nameof(StoreManagement)}:", StringComparison.InvariantCulture))
|
||||||
|
.Select(s => s.Split(":")[1]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,11 @@ using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security.APIKeys;
|
using BTCPayServer.Security.APIKeys;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using BTCPayServer.Views.Manage;
|
using BTCPayServer.Views.Manage;
|
||||||
using ExchangeSharp;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -67,8 +67,8 @@ namespace BTCPayServer.Tests
|
||||||
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||||
|
|
||||||
//this api key has access to everything
|
//this api key has access to everything
|
||||||
await TestApiAgainstAccessToken(superApiKey, tester, user, APIKeyConstants.Permissions.ServerManagement,
|
await TestApiAgainstAccessToken(superApiKey, tester, user, Permissions.ServerManagement,
|
||||||
APIKeyConstants.Permissions.StoreManagement);
|
Permissions.StoreManagement);
|
||||||
|
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||||
|
@ -76,7 +76,7 @@ namespace BTCPayServer.Tests
|
||||||
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,
|
||||||
APIKeyConstants.Permissions.ServerManagement);
|
Permissions.ServerManagement);
|
||||||
|
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||||
|
@ -84,7 +84,7 @@ namespace BTCPayServer.Tests
|
||||||
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,
|
||||||
APIKeyConstants.Permissions.StoreManagement);
|
Permissions.StoreManagement);
|
||||||
|
|
||||||
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,7 +96,7 @@ 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,
|
||||||
APIKeyConstants.Permissions.GetStorePermission(storeId));
|
Permissions.GetStorePermission(storeId));
|
||||||
|
|
||||||
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();
|
||||||
|
@ -117,37 +117,14 @@ namespace BTCPayServer.Tests
|
||||||
//permissions
|
//permissions
|
||||||
//strict
|
//strict
|
||||||
//selectiveStores
|
//selectiveStores
|
||||||
UriBuilder authorize = new UriBuilder(tester.PayTester.ServerUri);
|
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||||
authorize.Path = "api-keys/authorize";
|
new[] {Permissions.StoreManagement, Permissions.ServerManagement}).ToString();
|
||||||
|
|
||||||
authorize.AppendPayloadToQuery(new Dictionary<string, object>()
|
|
||||||
{
|
|
||||||
{"redirect", "https://local.local/callback"},
|
|
||||||
{"applicationName", "kukksappname"},
|
|
||||||
{"strict", true},
|
|
||||||
{"selectiveStores", false},
|
|
||||||
{
|
|
||||||
"permissions",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
APIKeyConstants.Permissions.StoreManagement,
|
|
||||||
APIKeyConstants.Permissions.ServerManagement
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
var authUrl = authorize.ToString();
|
|
||||||
var perms = new[]
|
|
||||||
{
|
|
||||||
APIKeyConstants.Permissions.StoreManagement, APIKeyConstants.Permissions.ServerManagement
|
|
||||||
};
|
|
||||||
authUrl = authUrl.Replace("permissions=System.String%5B%5D",
|
|
||||||
string.Join("&", perms.Select(s1 => $"permissions={s1}")));
|
|
||||||
s.Driver.Navigate().GoToUrl(authUrl);
|
s.Driver.Navigate().GoToUrl(authUrl);
|
||||||
s.Driver.PageSource.Contains("kukksappname");
|
s.Driver.PageSource.Contains("kukksappname");
|
||||||
Assert.NotNull(s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("readonly"));
|
Assert.Equal("hidden", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.True(s.Driver.FindElement(By.Id("StoreManagementPermission")).Selected);
|
Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
||||||
Assert.NotNull(s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("readonly"));
|
Assert.Equal("hidden", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.True(s.Driver.FindElement(By.Id("ServerManagementPermission")).Selected);
|
Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).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;
|
||||||
|
@ -159,35 +136,16 @@ namespace BTCPayServer.Tests
|
||||||
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)).GetPermissions());
|
||||||
|
|
||||||
authorize = new UriBuilder(tester.PayTester.ServerUri);
|
authUrl = BTCPayServerClient.GenerateAuthorizeUri(tester.PayTester.ServerUri,
|
||||||
authorize.Path = "api-keys/authorize";
|
new[] {Permissions.StoreManagement, Permissions.ServerManagement}, false, true).ToString();
|
||||||
authorize.AppendPayloadToQuery(new Dictionary<string, object>()
|
|
||||||
{
|
|
||||||
{"strict", false},
|
|
||||||
{"selectiveStores", true},
|
|
||||||
{
|
|
||||||
"permissions",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
APIKeyConstants.Permissions.StoreManagement,
|
|
||||||
APIKeyConstants.Permissions.ServerManagement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
authUrl = authorize.ToString();
|
|
||||||
perms = new[]
|
|
||||||
{
|
|
||||||
APIKeyConstants.Permissions.StoreManagement, APIKeyConstants.Permissions.ServerManagement
|
|
||||||
};
|
|
||||||
authUrl = authUrl.Replace("permissions=System.String%5B%5D",
|
|
||||||
string.Join("&", perms.Select(s1 => $"permissions={s1}")));
|
|
||||||
s.Driver.Navigate().GoToUrl(authUrl);
|
s.Driver.Navigate().GoToUrl(authUrl);
|
||||||
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
|
||||||
|
|
||||||
Assert.Null(s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("readonly"));
|
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.True(s.Driver.FindElement(By.Id("StoreManagementPermission")).Selected);
|
Assert.Equal("true", s.Driver.FindElement(By.Id("StoreManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
||||||
Assert.Null(s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("readonly"));
|
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("type").ToLowerInvariant());
|
||||||
Assert.True(s.Driver.FindElement(By.Id("ServerManagementPermission")).Selected);
|
Assert.Equal("true",s.Driver.FindElement(By.Id("ServerManagementPermission")).GetAttribute("value").ToLowerInvariant());
|
||||||
|
|
||||||
s.SetCheckbox(s, "ServerManagementPermission", false);
|
s.SetCheckbox(s, "ServerManagementPermission", false);
|
||||||
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
||||||
|
@ -214,8 +172,8 @@ namespace BTCPayServer.Tests
|
||||||
var secondUser = tester.NewAccount();
|
var secondUser = tester.NewAccount();
|
||||||
secondUser.GrantAccess();
|
secondUser.GrantAccess();
|
||||||
|
|
||||||
var selectiveStorePermissions = APIKeyConstants.Permissions.ExtractStorePermissionsIds(permissions);
|
var selectiveStorePermissions = Permissions.ExtractStorePermissionsIds(permissions);
|
||||||
if (permissions.Contains(APIKeyConstants.Permissions.StoreManagement) || selectiveStorePermissions.Any())
|
if (permissions.Contains(Permissions.StoreManagement) || selectiveStorePermissions.Any())
|
||||||
{
|
{
|
||||||
var resultStores =
|
var resultStores =
|
||||||
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
||||||
|
@ -231,7 +189,7 @@ namespace BTCPayServer.Tests
|
||||||
data => data.Id.Equals(selectiveStorePermission, StringComparison.InvariantCultureIgnoreCase));
|
data => data.Id.Equals(selectiveStorePermission, StringComparison.InvariantCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permissions.Contains(APIKeyConstants.Permissions.StoreManagement))
|
if (permissions.Contains(Permissions.StoreManagement))
|
||||||
{
|
{
|
||||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/stores/actions",
|
$"{TestApiPath}/me/stores/actions",
|
||||||
|
@ -272,7 +230,7 @@ namespace BTCPayServer.Tests
|
||||||
tester.PayTester.HttpClient);
|
tester.PayTester.HttpClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (permissions.Contains(APIKeyConstants.Permissions.ServerManagement))
|
if (permissions.Contains(Permissions.ServerManagement))
|
||||||
{
|
{
|
||||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||||
$"{TestApiPath}/me/is-admin",
|
$"{TestApiPath}/me/is-admin",
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
using System;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Controllers.RestApi.ApiKeys;
|
|
||||||
using BTCPayServer.Data;
|
|
||||||
using BTCPayServer.Security.APIKeys;
|
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
|
using Microsoft.AspNet.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
@ -26,7 +23,7 @@ namespace BTCPayServer.Tests
|
||||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Timeout = TestTimeout)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public async Task ApiKeysControllerTests()
|
public async Task ApiKeysControllerTests()
|
||||||
{
|
{
|
||||||
|
@ -36,29 +33,59 @@ namespace BTCPayServer.Tests
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
await user.MakeAdmin();
|
await user.MakeAdmin();
|
||||||
string apiKey = await GenerateAPIKey(tester, user);
|
string apiKey = await GenerateAPIKey(tester, user, Permissions.ServerManagement, Permissions.StoreManagement);
|
||||||
|
var client = new BTCPayServerClient(tester.PayTester.ServerUri, apiKey);
|
||||||
//Get current api key
|
//Get current api key
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/api-keys/current");
|
var apiKeyData = await client.GetCurrentAPIKeyInfo();
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("token", apiKey);
|
|
||||||
var result = await tester.PayTester.HttpClient.SendAsync(request);
|
|
||||||
Assert.True(result.IsSuccessStatusCode);
|
|
||||||
var apiKeyData = JObject.Parse(await result.Content.ReadAsStringAsync()).ToObject<ApiKeyData>();
|
|
||||||
Assert.NotNull(apiKeyData);
|
Assert.NotNull(apiKeyData);
|
||||||
Assert.Equal(apiKey, apiKeyData.ApiKey);
|
Assert.Equal(apiKey, apiKeyData.ApiKey);
|
||||||
Assert.Equal(user.UserId, apiKeyData.UserId);
|
Assert.Equal(user.UserId, apiKeyData.UserId);
|
||||||
Assert.Equal(2, apiKeyData.Permissions.Length);
|
Assert.Equal(2, apiKeyData.Permissions.Length);
|
||||||
|
|
||||||
|
//revoke current api key
|
||||||
|
await client.RevokeCurrentAPIKeyInfo();
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||||
|
{
|
||||||
|
await client.GetCurrentAPIKeyInfo();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> GenerateAPIKey(ServerTester tester, TestAccount user)
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
public async Task UsersControllerTests()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
await tester.StartAsync();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
user.GrantAccess();
|
||||||
|
await user.MakeAdmin();
|
||||||
|
string apiKeyProfile = await GenerateAPIKey(tester, user, Permissions.ProfileManagement);
|
||||||
|
string apiKeyInsufficient = await GenerateAPIKey(tester, user, Permissions.StoreManagement);
|
||||||
|
var clientProfile = new BTCPayServerClient(tester.PayTester.ServerUri, apiKeyProfile);
|
||||||
|
var clientInsufficient= new BTCPayServerClient(tester.PayTester.ServerUri, apiKeyInsufficient);
|
||||||
|
|
||||||
|
var apiKeyProfileUserData = await clientProfile.GetCurrentUser();
|
||||||
|
Assert.NotNull(apiKeyProfileUserData);
|
||||||
|
Assert.Equal(apiKeyProfileUserData.Id, user.UserId);
|
||||||
|
Assert.Equal(apiKeyProfileUserData.Email, user.RegisterDetails.Email);
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> GenerateAPIKey(ServerTester tester, TestAccount user, params string[] permissions)
|
||||||
{
|
{
|
||||||
var manageController = tester.PayTester.GetController<ManageController>(user.UserId, user.StoreId, user.IsAdmin);
|
var manageController = tester.PayTester.GetController<ManageController>(user.UserId, user.StoreId, user.IsAdmin);
|
||||||
var x = Assert.IsType<RedirectToActionResult>(await manageController.AddApiKey(
|
var x = Assert.IsType<RedirectToActionResult>(await manageController.AddApiKey(
|
||||||
new ManageController.AddApiKeyViewModel()
|
new ManageController.AddApiKeyViewModel()
|
||||||
{
|
{
|
||||||
ServerManagementPermission = true,
|
PermissionValues = permissions.Select(s => new ManageController.AddApiKeyViewModel.PermissionValueItem()
|
||||||
StoreManagementPermission = true,
|
{
|
||||||
|
Permission = s,
|
||||||
|
Value = true
|
||||||
|
}).ToList(),
|
||||||
StoreMode = ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores
|
StoreMode = ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores
|
||||||
}));
|
}));
|
||||||
var statusMessage = manageController.TempData.GetStatusMessageModel();
|
var statusMessage = manageController.TempData.GetStatusMessageModel();
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Hosting.OpenApi;
|
using BTCPayServer.Hosting.OpenApi;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
|
@ -13,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
using NSwag.Annotations;
|
using NSwag.Annotations;
|
||||||
|
using YamlDotNet.Core.Tokens;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
@ -30,8 +32,6 @@ namespace BTCPayServer.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("api-keys/{id}/delete")]
|
[HttpGet("api-keys/{id}/delete")]
|
||||||
public async Task<IActionResult> RemoveAPIKey(string id)
|
public async Task<IActionResult> RemoveAPIKey(string id)
|
||||||
{
|
{
|
||||||
|
@ -109,9 +109,13 @@ namespace BTCPayServer.Controllers
|
||||||
var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel()
|
var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel()
|
||||||
{
|
{
|
||||||
Label = applicationName,
|
Label = applicationName,
|
||||||
ServerManagementPermission = permissions.Contains(APIKeyConstants.Permissions.ServerManagement),
|
ServerManagementPermission = permissions.Contains(Permissions.ServerManagement),
|
||||||
StoreManagementPermission = permissions.Contains(APIKeyConstants.Permissions.StoreManagement),
|
StoreManagementPermission = permissions.Contains(Permissions.StoreManagement),
|
||||||
PermissionsFormatted = permissions,
|
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,
|
||||||
|
@ -133,7 +137,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (viewModel.PermissionsFormatted.Contains(APIKeyConstants.Permissions.ServerManagement))
|
if (viewModel.PermissionsFormatted.Contains(Permissions.ServerManagement))
|
||||||
{
|
{
|
||||||
if (!viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
if (!viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +151,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.PermissionsFormatted.Contains(APIKeyConstants.Permissions.StoreManagement))
|
if (viewModel.PermissionsFormatted.Contains(Permissions.StoreManagement))
|
||||||
{
|
{
|
||||||
if (!viewModel.SelectiveStores &&
|
if (!viewModel.SelectiveStores &&
|
||||||
viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
||||||
|
@ -261,29 +265,34 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
private IEnumerable<string> GetPermissionsFromViewModel(AddApiKeyViewModel viewModel)
|
private IEnumerable<string> GetPermissionsFromViewModel(AddApiKeyViewModel viewModel)
|
||||||
{
|
{
|
||||||
var permissions = new List<string>();
|
var permissions = viewModel.PermissionValues.Where(tuple => tuple.Value).Select(tuple => tuple.Permission).ToList();
|
||||||
|
|
||||||
if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
if (viewModel.StoreMode == AddApiKeyViewModel.ApiKeyStoreMode.Specific)
|
||||||
{
|
{
|
||||||
permissions.AddRange(viewModel.SpecificStores.Select(APIKeyConstants.Permissions.GetStorePermission));
|
permissions.AddRange(viewModel.SpecificStores.Select(Permissions.GetStorePermission));
|
||||||
}
|
}
|
||||||
else if (viewModel.StoreManagementPermission)
|
else if (viewModel.StoreManagementPermission)
|
||||||
{
|
{
|
||||||
permissions.Add(APIKeyConstants.Permissions.StoreManagement);
|
permissions.Add(Permissions.StoreManagement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
if (viewModel.IsServerAdmin && viewModel.ServerManagementPermission)
|
||||||
{
|
{
|
||||||
permissions.Add(APIKeyConstants.Permissions.ServerManagement);
|
permissions.Add(Permissions.ServerManagement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissions;
|
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 = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
viewModel.IsServerAdmin =
|
||||||
|
(await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
||||||
|
viewModel.PermissionValues ??= Permissions.GetAllPermissionKeys().Where(s =>
|
||||||
|
!s.Contains(Permissions.StoreManagement, StringComparison.InvariantCultureIgnoreCase) &&
|
||||||
|
s != Permissions.ServerManagement)
|
||||||
|
.Select(s => new AddApiKeyViewModel.PermissionValueItem() {Permission = s, Value = true}).ToList();
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,12 +306,19 @@ namespace BTCPayServer.Controllers
|
||||||
public bool ServerManagementPermission { get; set; }
|
public bool ServerManagementPermission { get; set; }
|
||||||
public bool StoreManagementPermission { get; set; }
|
public bool StoreManagementPermission { get; set; }
|
||||||
public string Command { get; set; }
|
public string Command { get; set; }
|
||||||
|
public List<PermissionValueItem> PermissionValues { get; set; }
|
||||||
|
|
||||||
public enum ApiKeyStoreMode
|
public enum ApiKeyStoreMode
|
||||||
{
|
{
|
||||||
AllStores,
|
AllStores,
|
||||||
Specific
|
Specific
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PermissionValueItem
|
||||||
|
{
|
||||||
|
public string Permission { get; set; }
|
||||||
|
public bool Value { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
|
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
using BTCPayServer.Data;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi.ApiKeys
|
|
||||||
{
|
|
||||||
public class ApiKeyData
|
|
||||||
{
|
|
||||||
public string ApiKey { get; set; }
|
|
||||||
public string Label { get; set; }
|
|
||||||
public string UserId { get; set; }
|
|
||||||
public string[] Permissions { get; set; }
|
|
||||||
|
|
||||||
public static ApiKeyData FromModel(APIKeyData data)
|
|
||||||
{
|
|
||||||
return new ApiKeyData()
|
|
||||||
{
|
|
||||||
Permissions = data.GetPermissions(),
|
|
||||||
ApiKey = data.Id,
|
|
||||||
UserId = data.UserId,
|
|
||||||
Label = data.Label
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,12 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Hosting.OpenApi;
|
using BTCPayServer.Hosting.OpenApi;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Security.APIKeys;
|
using BTCPayServer.Security.APIKeys;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NSwag.Annotations;
|
using NSwag.Annotations;
|
||||||
|
|
||||||
|
@ -16,10 +19,12 @@ namespace BTCPayServer.Controllers.RestApi.ApiKeys
|
||||||
public class ApiKeysController : ControllerBase
|
public class ApiKeysController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly APIKeyRepository _apiKeyRepository;
|
private readonly APIKeyRepository _apiKeyRepository;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
public ApiKeysController(APIKeyRepository apiKeyRepository)
|
public ApiKeysController(APIKeyRepository apiKeyRepository, UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_apiKeyRepository = apiKeyRepository;
|
_apiKeyRepository = apiKeyRepository;
|
||||||
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[OpenApiOperation("Get current API Key information", "View information about the current API key")]
|
[OpenApiOperation("Get current API Key information", "View information about the current API key")]
|
||||||
|
@ -31,7 +36,30 @@ namespace BTCPayServer.Controllers.RestApi.ApiKeys
|
||||||
{
|
{
|
||||||
ControllerContext.HttpContext.GetAPIKey(out var apiKey);
|
ControllerContext.HttpContext.GetAPIKey(out var apiKey);
|
||||||
var data = await _apiKeyRepository.GetKey(apiKey);
|
var data = await _apiKeyRepository.GetKey(apiKey);
|
||||||
return Ok(ApiKeyData.FromModel(data));
|
return Ok(FromModel(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
[OpenApiOperation("Revoke the current API Key", "Revoke the current API key so that it cannot be used anymore")]
|
||||||
|
[SwaggerResponse(StatusCodes.Status200OK, typeof(ApiKeyData),
|
||||||
|
Description = "The key was revoked and is no longer usable")]
|
||||||
|
[HttpDelete("~/api/v1/api-keys/current")]
|
||||||
|
[HttpDelete("~/api/v1/users/me/api-keys/current")]
|
||||||
|
public async Task<ActionResult<ApiKeyData>> RevokeKey()
|
||||||
|
{
|
||||||
|
ControllerContext.HttpContext.GetAPIKey(out var apiKey);
|
||||||
|
await _apiKeyRepository.Remove(apiKey, _userManager.GetUserId(User));
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApiKeyData FromModel(APIKeyData data)
|
||||||
|
{
|
||||||
|
return new ApiKeyData()
|
||||||
|
{
|
||||||
|
Permissions = data.GetPermissions(),
|
||||||
|
ApiKey = data.Id,
|
||||||
|
UserId = data.UserId,
|
||||||
|
Label = data.Label
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
BTCPayServer/Controllers/RestApi/Users/UsersController.cs
Normal file
47
BTCPayServer/Controllers/RestApi/Users/UsersController.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Hosting.OpenApi;
|
||||||
|
using BTCPayServer.Security;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NSwag.Annotations;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Controllers.RestApi.Users
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[IncludeInOpenApiDocs]
|
||||||
|
[OpenApiTags("Users")]
|
||||||
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
|
public class UsersController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
|
public UsersController(UserManager<ApplicationUser> userManager)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[OpenApiOperation("Get current user information", "View information about the current user")]
|
||||||
|
[SwaggerResponse(StatusCodes.Status200OK, typeof(ApiKeyData),
|
||||||
|
Description = "Information about the current user")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyProfile.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
|
[HttpGet("~/api/v1/users/me")]
|
||||||
|
public async Task<ActionResult<ApplicationUserData>> GetCurrentUser()
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
return FromModel(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApplicationUserData FromModel(ApplicationUser data)
|
||||||
|
{
|
||||||
|
return new ApplicationUserData()
|
||||||
|
{
|
||||||
|
Id = data.Id,
|
||||||
|
Email = data.Email
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -33,16 +34,19 @@ namespace BTCPayServer.Security.APIKeys
|
||||||
bool success = false;
|
bool success = false;
|
||||||
switch (requirement.Policy)
|
switch (requirement.Policy)
|
||||||
{
|
{
|
||||||
|
case Policies.CanModifyProfile.Key:
|
||||||
|
success = context.HasPermissions(Permissions.ProfileManagement);
|
||||||
|
break;
|
||||||
case Policies.CanListStoreSettings.Key:
|
case Policies.CanListStoreSettings.Key:
|
||||||
var selectiveStorePermissions =
|
var selectiveStorePermissions =
|
||||||
APIKeyConstants.Permissions.ExtractStorePermissionsIds(context.GetPermissions());
|
Permissions.ExtractStorePermissionsIds(context.GetPermissions());
|
||||||
success = context.HasPermissions(APIKeyConstants.Permissions.StoreManagement) ||
|
success = context.HasPermissions(Permissions.StoreManagement) ||
|
||||||
selectiveStorePermissions.Any();
|
selectiveStorePermissions.Any();
|
||||||
break;
|
break;
|
||||||
case Policies.CanModifyStoreSettings.Key:
|
case Policies.CanModifyStoreSettings.Key:
|
||||||
string storeId = _HttpContext.GetImplicitStoreId();
|
string storeId = _HttpContext.GetImplicitStoreId();
|
||||||
if (!context.HasPermissions(APIKeyConstants.Permissions.StoreManagement) &&
|
if (!context.HasPermissions(Permissions.StoreManagement) &&
|
||||||
!context.HasPermissions(APIKeyConstants.Permissions.GetStorePermission(storeId)))
|
!context.HasPermissions(Permissions.GetStorePermission(storeId)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (storeId == null)
|
if (storeId == null)
|
||||||
|
@ -63,7 +67,7 @@ namespace BTCPayServer.Security.APIKeys
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Policies.CanModifyServerSettings.Key:
|
case Policies.CanModifyServerSettings.Key:
|
||||||
if (!context.HasPermissions(APIKeyConstants.Permissions.ServerManagement))
|
if (!context.HasPermissions(Permissions.ServerManagement))
|
||||||
break;
|
break;
|
||||||
// For this authorization, we stil check in database because it is super sensitive.
|
// For this authorization, we stil check in database because it is super sensitive.
|
||||||
var user = await _userManager.GetUserAsync(context.User);
|
var user = await _userManager.GetUserAsync(context.User);
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Security.APIKeys
|
namespace BTCPayServer.Security.APIKeys
|
||||||
{
|
{
|
||||||
|
@ -16,21 +13,14 @@ namespace BTCPayServer.Security.APIKeys
|
||||||
|
|
||||||
public static class Permissions
|
public static class Permissions
|
||||||
{
|
{
|
||||||
public const string ServerManagement = nameof(ServerManagement);
|
|
||||||
public const string StoreManagement = nameof(StoreManagement);
|
|
||||||
|
|
||||||
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
|
public static readonly Dictionary<string, (string Title, string Description)> PermissionDescriptions = new Dictionary<string, (string Title, string Description)>()
|
||||||
{
|
{
|
||||||
{StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")},
|
{Client.Permissions.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")},
|
||||||
{$"{nameof(StoreManagement)}:", ("Manage selected stores", "The app will be able to modify and delete selected stores.")},
|
{$"{nameof(Client.Permissions.StoreManagement)}:", ("Manage selected stores", "The app will be able to modify and delete selected stores.")},
|
||||||
{ServerManagement, ("Manage your server", "The app will have total control on your server")},
|
{Client.Permissions.ServerManagement, ("Manage your server", "The app will have total control on your server")},
|
||||||
|
{Client.Permissions.ProfileManagement, ("Manage your profile", "The app will be able to view and modify your user profile.")},
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string GetStorePermission(string storeId) => $"{nameof(StoreManagement)}:{storeId}";
|
|
||||||
|
|
||||||
public static IEnumerable<string> ExtractStorePermissionsIds(IEnumerable<string> permissions) => permissions
|
|
||||||
.Where(s => s.StartsWith($"{nameof(StoreManagement)}:", StringComparison.InvariantCulture))
|
|
||||||
.Select(s => s.Split(":")[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
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;
|
||||||
|
@ -35,12 +36,12 @@ namespace BTCPayServer.Security.APIKeys
|
||||||
claimsPrincipal.Claims.Where(claim => claim.Type == APIKeyConstants.ClaimTypes.Permissions)
|
claimsPrincipal.Claims.Where(claim => claim.Type == APIKeyConstants.ClaimTypes.Permissions)
|
||||||
.Select(claim => claim.Value).ToList();
|
.Select(claim => claim.Value).ToList();
|
||||||
|
|
||||||
if (permissions.Contains(APIKeyConstants.Permissions.StoreManagement))
|
if (permissions.Contains(Permissions.StoreManagement))
|
||||||
{
|
{
|
||||||
return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal));
|
return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal));
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeIds = APIKeyConstants.Permissions.ExtractStorePermissionsIds(permissions);
|
var storeIds = Permissions.ExtractStorePermissionsIds(permissions);
|
||||||
return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal), storeIds);
|
return storeRepository.GetStoresByUserId(userManager.GetUserId(claimsPrincipal), storeIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
|
@ -15,6 +11,8 @@ namespace BTCPayServer.Security
|
||||||
options.AddPolicy(CanCreateInvoice.Key);
|
options.AddPolicy(CanCreateInvoice.Key);
|
||||||
options.AddPolicy(CanGetRates.Key);
|
options.AddPolicy(CanGetRates.Key);
|
||||||
options.AddPolicy(CanModifyServerSettings.Key);
|
options.AddPolicy(CanModifyServerSettings.Key);
|
||||||
|
options.AddPolicy(CanModifyServerSettings.Key);
|
||||||
|
options.AddPolicy(CanModifyProfile.Key);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +25,10 @@ namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
public const string Key = "btcpay.store.canmodifyserversettings";
|
public const string Key = "btcpay.store.canmodifyserversettings";
|
||||||
}
|
}
|
||||||
|
public class CanModifyProfile
|
||||||
|
{
|
||||||
|
public const string Key = "btcpay.store.canmodifyprofile";
|
||||||
|
}
|
||||||
public class CanModifyStoreSettings
|
public class CanModifyStoreSettings
|
||||||
{
|
{
|
||||||
public const string Key = "btcpay.store.canmodifystoresettings";
|
public const string Key = "btcpay.store.canmodifystoresettings";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
@using BTCPayServer.Client
|
||||||
@using BTCPayServer.Controllers
|
@using BTCPayServer.Controllers
|
||||||
@using BTCPayServer.Security.APIKeys
|
@using BTCPayServer.Security.APIKeys
|
||||||
@model BTCPayServer.Controllers.ManageController.AddApiKeyViewModel
|
@model ManageController.AddApiKeyViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Add API Key");
|
ViewData.SetActivePageAndTitle(ManageNavPages.APIKeys, "Add API Key");
|
||||||
|
@ -39,9 +40,20 @@
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
<input asp-for="ServerManagementPermission" class="form-check-inline"/>
|
<input asp-for="ServerManagementPermission" class="form-check-inline"/>
|
||||||
<label asp-for="ServerManagementPermission" class="h5">@GetTitle(APIKeyConstants.Permissions.ServerManagement)</label>
|
<label asp-for="ServerManagementPermission" class="h5">@GetTitle(Permissions.ServerManagement)</label>
|
||||||
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
||||||
<p>@GetDescription(APIKeyConstants.Permissions.ServerManagement).</p>
|
<p>@GetDescription(Permissions.ServerManagement).</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
||||||
|
{
|
||||||
|
<div class="list-group-item form-group">
|
||||||
|
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
||||||
|
<input 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>
|
||||||
|
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
|
||||||
|
<p>@GetDescription(Model.PermissionValues[i].Permission).</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
@if (Model.StoreMode == ManageController.AddApiKeyViewModel.ApiKeyStoreMode.AllStores)
|
||||||
|
@ -49,9 +61,9 @@
|
||||||
<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"}})
|
@Html.CheckBoxFor(model => model.StoreManagementPermission, new Dictionary<string, string>() {{"class", "form-check-inline"}})
|
||||||
|
|
||||||
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(APIKeyConstants.Permissions.StoreManagement)</label>
|
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(Permissions.StoreManagement)</label>
|
||||||
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
||||||
<p class="mb-0">@GetDescription(APIKeyConstants.Permissions.StoreManagement).</p>
|
<p class="mb-0">@GetDescription(Permissions.StoreManagement).</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>
|
||||||
}
|
}
|
||||||
|
@ -59,8 +71,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(APIKeyConstants.Permissions.StoreManagement + ":")</h5>
|
<h5 class="mb-1">@GetTitle(Permissions.StoreManagement + ":")</h5>
|
||||||
<p class="mb-0">@GetDescription(APIKeyConstants.Permissions.StoreManagement + ":").</p>
|
<p class="mb-0">@GetDescription(Permissions.StoreManagement + ":").</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())
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@using BTCPayServer.Client
|
||||||
@using BTCPayServer.Controllers
|
@using BTCPayServer.Controllers
|
||||||
@using BTCPayServer.Security.APIKeys
|
@using BTCPayServer.Security.APIKeys
|
||||||
@model BTCPayServer.Controllers.ManageController.AuthorizeApiKeysViewModel
|
@model BTCPayServer.Controllers.ManageController.AuthorizeApiKeysViewModel
|
||||||
|
@ -48,11 +49,20 @@
|
||||||
<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(APIKeyConstants.Permissions.ServerManagement) && (Model.IsServerAdmin || Model.Strict))
|
@if (Model.PermissionsFormatted.Contains(Permissions.ServerManagement) && (Model.IsServerAdmin || Model.Strict))
|
||||||
{
|
{
|
||||||
<div class="list-group-item form-group">
|
<div class="list-group-item form-group">
|
||||||
<input asp-for="ServerManagementPermission" class="form-check-inline" readonly="@(Model.Strict || !Model.IsServerAdmin)"/>
|
@if (Model.Strict || !Model.IsServerAdmin)
|
||||||
<label asp-for="ServerManagementPermission" class="h5">@GetTitle(APIKeyConstants.Permissions.ServerManagement)</label>
|
{
|
||||||
|
<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)
|
@if (!Model.IsServerAdmin)
|
||||||
{
|
{
|
||||||
<span class="text-danger">
|
<span class="text-danger">
|
||||||
|
@ -61,19 +71,44 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="ServerManagementPermission" class="text-danger"></span>
|
||||||
<p>@GetDescription(APIKeyConstants.Permissions.ServerManagement).</p>
|
<p>@GetDescription(Permissions.ServerManagement).</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@for (int i = 0; i < Model.PermissionValues.Count; i++)
|
||||||
@if (Model.PermissionsFormatted.Contains(APIKeyConstants.Permissions.StoreManagement))
|
{
|
||||||
|
<div class="list-group-item form-group">
|
||||||
|
<input type="hidden" asp-for="PermissionValues[i].Permission">
|
||||||
|
@if (Model.Strict || !Model.IsServerAdmin)
|
||||||
|
{
|
||||||
|
<input type="hidden" asp-for="PermissionValues[i].Value"/>
|
||||||
|
<input type="checkbox" class="form-check-inline" checked="@Model.PermissionValues[i].Value" disabled/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input 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>
|
||||||
|
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
|
||||||
|
<p>@GetDescription(Model.PermissionValues[i].Permission).</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.PermissionsFormatted.Contains(Permissions.StoreManagement))
|
||||||
{
|
{
|
||||||
@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">
|
||||||
<input type="checkbox" asp-for="StoreManagementPermission" class="form-check-inline" readonly="@Model.Strict"/>
|
@if (Model.Strict)
|
||||||
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(APIKeyConstants.Permissions.StoreManagement)</label>
|
{
|
||||||
|
<input type="hidden" asp-for="StoreManagementPermission"/>
|
||||||
|
<input type="checkbox" class="form-check-inline" checked="@Model.StoreManagementPermission" disabled/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" asp-for="StoreManagementPermission" class="form-check-inline"/>
|
||||||
|
}
|
||||||
|
<label asp-for="StoreManagementPermission" class="h5">@GetTitle(Permissions.StoreManagement)</label>
|
||||||
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
<span asp-validation-for="StoreManagementPermission" class="text-danger"></span>
|
||||||
<p class="mb-0">@GetDescription(APIKeyConstants.Permissions.StoreManagement).</p>
|
<p class="mb-0">@GetDescription(Permissions.StoreManagement).</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>
|
||||||
|
@ -84,8 +119,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(APIKeyConstants.Permissions.StoreManagement + ":")</h5>
|
<h5 class="mb-1">@GetTitle(Permissions.StoreManagement + ":")</h5>
|
||||||
<p class="mb-0">@GetDescription(APIKeyConstants.Permissions.StoreManagement + ":").</p>
|
<p class="mb-0">@GetDescription(Permissions.StoreManagement + ":").</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())
|
||||||
|
|
|
@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Common", "BTCP
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Data", "BTCPayServer.Data\BTCPayServer.Data.csproj", "{4D7A865D-3945-4C70-9CC8-B09A274A697E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Data", "BTCPayServer.Data\BTCPayServer.Data.csproj", "{4D7A865D-3945-4C70-9CC8-B09A274A697E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Client", "BTCPayServer.Client\BTCPayServer.Client.csproj", "{21A13304-7168-49A0-86C2-0A1A9453E9C7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -95,6 +97,18 @@ Global
|
||||||
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x64.Build.0 = Release|Any CPU
|
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x86.ActiveCfg = Release|Any CPU
|
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x86.Build.0 = Release|Any CPU
|
{4D7A865D-3945-4C70-9CC8-B09A274A697E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
Loading…
Add table
Reference in a new issue