mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 22:25:28 +01:00
Merge pull request #1857 from Kukks/fix-greenfield-roles
Set roles when authenticating via greenfield
This commit is contained in:
commit
0a8fb1b835
6 changed files with 104 additions and 93 deletions
|
@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
|
@ -873,6 +874,96 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = 60 * 2 * 1000)]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
[Trait("Lightning", "Lightning")]
|
||||||
|
public async Task CanUseLightningAPI()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
tester.ActivateLightning();
|
||||||
|
await tester.StartAsync();
|
||||||
|
await tester.EnsureChannelsSetup();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
user.GrantAccess(true);
|
||||||
|
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
|
||||||
|
|
||||||
|
var merchant = tester.NewAccount();
|
||||||
|
merchant.GrantAccess(true);
|
||||||
|
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||||
|
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
|
||||||
|
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||||
|
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
|
||||||
|
// The default client is using charge, so we should not be able to query channels
|
||||||
|
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||||
|
|
||||||
|
var info = await client.GetLightningNodeInfo("BTC");
|
||||||
|
Assert.Single(info.NodeURIs);
|
||||||
|
Assert.NotEqual(0, info.BlockHeight);
|
||||||
|
|
||||||
|
var err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||||
|
Assert.Contains("503", err.Message);
|
||||||
|
// Not permission for the store!
|
||||||
|
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||||
|
Assert.Contains("403", err.Message);
|
||||||
|
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||||
|
{
|
||||||
|
Amount = LightMoney.Satoshis(1000),
|
||||||
|
Description = "lol",
|
||||||
|
Expiry = TimeSpan.FromSeconds(400),
|
||||||
|
PrivateRouteHints = false
|
||||||
|
});
|
||||||
|
var chargeInvoice = invoiceData;
|
||||||
|
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
||||||
|
|
||||||
|
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
|
||||||
|
// Not permission for the server
|
||||||
|
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||||
|
Assert.Contains("403", err.Message);
|
||||||
|
|
||||||
|
var data = await client.GetLightningNodeChannels(user.StoreId, "BTC");
|
||||||
|
Assert.Equal(2, data.Count());
|
||||||
|
BitcoinAddress.Create(await client.GetLightningDepositAddress(user.StoreId, "BTC"), Network.RegTest);
|
||||||
|
|
||||||
|
invoiceData = await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||||
|
{
|
||||||
|
Amount = LightMoney.Satoshis(1000),
|
||||||
|
Description = "lol",
|
||||||
|
Expiry = TimeSpan.FromSeconds(400),
|
||||||
|
PrivateRouteHints = false
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
|
||||||
|
|
||||||
|
await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||||
|
{
|
||||||
|
BOLT11 = merchantInvoice.BOLT11
|
||||||
|
});
|
||||||
|
await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||||
|
{
|
||||||
|
BOLT11 = "lol"
|
||||||
|
}));
|
||||||
|
|
||||||
|
var validationErr = await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||||
|
{
|
||||||
|
Amount = -1,
|
||||||
|
Expiry = TimeSpan.FromSeconds(-1),
|
||||||
|
Description = null
|
||||||
|
}));
|
||||||
|
Assert.Equal(2, validationErr.ValidationErrors.Length);
|
||||||
|
|
||||||
|
var invoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
||||||
|
Assert.NotNull(invoice.PaidAt);
|
||||||
|
Assert.Equal(LightMoney.Satoshis(1000), invoice.Amount);
|
||||||
|
// Amount received might be bigger because of internal implementation shit from lightning
|
||||||
|
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
||||||
|
|
||||||
|
info = await client.GetLightningNodeInfo(user.StoreId, "BTC");
|
||||||
|
Assert.Single(info.NodeURIs);
|
||||||
|
Assert.NotEqual(0, info.BlockHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Fact(Timeout = TestTimeout)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
|
|
@ -836,92 +836,6 @@ namespace BTCPayServer.Tests
|
||||||
.ToArray());
|
.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = 60 * 2 * 1000)]
|
|
||||||
[Trait("Integration", "Integration")]
|
|
||||||
[Trait("Lightning", "Lightning")]
|
|
||||||
public async Task CanUseLightningAPI()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.ActivateLightning();
|
|
||||||
await tester.StartAsync();
|
|
||||||
await tester.EnsureChannelsSetup();
|
|
||||||
var user = tester.NewAccount();
|
|
||||||
user.GrantAccess(true);
|
|
||||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
|
|
||||||
|
|
||||||
var merchant = tester.NewAccount();
|
|
||||||
merchant.GrantAccess(true);
|
|
||||||
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
|
||||||
var merchantClient = await merchant.CreateClient($"btcpay.store.canuselightningnode:{merchant.StoreId}");
|
|
||||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
|
|
||||||
|
|
||||||
// The default client is using charge, so we should not be able to query channels
|
|
||||||
var client = await user.CreateClient("btcpay.server.canuseinternallightningnode");
|
|
||||||
var err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
|
||||||
Assert.Contains("503", err.Message);
|
|
||||||
// Not permission for the store!
|
|
||||||
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
|
||||||
Assert.Contains("403", err.Message);
|
|
||||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
|
||||||
{
|
|
||||||
Amount = LightMoney.Satoshis(1000),
|
|
||||||
Description = "lol",
|
|
||||||
Expiry = TimeSpan.FromSeconds(400),
|
|
||||||
PrivateRouteHints = false
|
|
||||||
});
|
|
||||||
var chargeInvoice = invoiceData;
|
|
||||||
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
|
||||||
|
|
||||||
client = await user.CreateClient($"btcpay.store.canuselightningnode:{user.StoreId}");
|
|
||||||
// Not permission for the server
|
|
||||||
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
|
||||||
Assert.Contains("403", err.Message);
|
|
||||||
|
|
||||||
var data = await client.GetLightningNodeChannels(user.StoreId, "BTC");
|
|
||||||
Assert.Equal(2, data.Count());
|
|
||||||
BitcoinAddress.Create(await client.GetLightningDepositAddress(user.StoreId, "BTC"), Network.RegTest);
|
|
||||||
|
|
||||||
invoiceData = await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
|
||||||
{
|
|
||||||
Amount = LightMoney.Satoshis(1000),
|
|
||||||
Description = "lol",
|
|
||||||
Expiry = TimeSpan.FromSeconds(400),
|
|
||||||
PrivateRouteHints = false
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
|
|
||||||
|
|
||||||
await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
|
||||||
{
|
|
||||||
BOLT11 = merchantInvoice.BOLT11
|
|
||||||
});
|
|
||||||
await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
|
||||||
{
|
|
||||||
BOLT11 = "lol"
|
|
||||||
}));
|
|
||||||
|
|
||||||
var validationErr = await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
|
||||||
{
|
|
||||||
Amount = -1,
|
|
||||||
Expiry = TimeSpan.FromSeconds(-1),
|
|
||||||
Description = null
|
|
||||||
}));
|
|
||||||
Assert.Equal(2, validationErr.ValidationErrors.Length);
|
|
||||||
|
|
||||||
var invoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
|
||||||
Assert.NotNull(invoice.PaidAt);
|
|
||||||
Assert.Equal(LightMoney.Satoshis(1000), invoice.Amount);
|
|
||||||
// Amount received might be bigger because of internal implementation shit from lightning
|
|
||||||
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
|
||||||
|
|
||||||
var info = await client.GetLightningNodeInfo(user.StoreId, "BTC");
|
|
||||||
Assert.Single(info.NodeURIs);
|
|
||||||
Assert.NotEqual(0, info.BlockHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task CanSendLightningPaymentCore(ServerTester tester, TestAccount user)
|
async Task CanSendLightningPaymentCore(ServerTester tester, TestAccount user)
|
||||||
{
|
{
|
||||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice()
|
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice()
|
||||||
|
|
|
@ -16,13 +16,14 @@ namespace BTCPayServer.Security.GreenField
|
||||||
_applicationDbContextFactory = applicationDbContextFactory;
|
_applicationDbContextFactory = applicationDbContextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<APIKeyData> GetKey(string apiKey)
|
public async Task<APIKeyData> GetKey(string apiKey, bool includeUser = false)
|
||||||
{
|
{
|
||||||
using (var context = _applicationDbContextFactory.CreateContext())
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
|
if (includeUser)
|
||||||
{
|
{
|
||||||
return await EntityFrameworkQueryableExtensions.SingleOrDefaultAsync(context.ApiKeys,
|
return await context.ApiKeys.Include(data => data.User).SingleOrDefaultAsync(data => data.Id == apiKey && data.Type != APIKeyType.Legacy);
|
||||||
data => data.Id == apiKey && data.Type != APIKeyType.Legacy);
|
|
||||||
}
|
}
|
||||||
|
return await context.ApiKeys.SingleOrDefaultAsync(data => data.Id == apiKey && data.Type != APIKeyType.Legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<APIKeyData>> GetKeys(APIKeyQuery query)
|
public async Task<List<APIKeyData>> GetKeys(APIKeyQuery query)
|
||||||
|
|
|
@ -40,15 +40,16 @@ namespace BTCPayServer.Security.GreenField
|
||||||
if (!Context.Request.HttpContext.GetAPIKey(out var apiKey) || string.IsNullOrEmpty(apiKey))
|
if (!Context.Request.HttpContext.GetAPIKey(out var apiKey) || string.IsNullOrEmpty(apiKey))
|
||||||
return AuthenticateResult.NoResult();
|
return AuthenticateResult.NoResult();
|
||||||
|
|
||||||
var key = await _apiKeyRepository.GetKey(apiKey);
|
var key = await _apiKeyRepository.GetKey(apiKey, true);
|
||||||
|
|
||||||
if (key == null)
|
if (key == null)
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Fail("ApiKey authentication failed");
|
return AuthenticateResult.Fail("ApiKey authentication failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
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((await _userManager.GetRolesAsync(key.User)).Select(s => new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s)));
|
||||||
claims.AddRange(Permission.ToPermissions(key.GetBlob().Permissions).Select(permission =>
|
claims.AddRange(Permission.ToPermissions(key.GetBlob().Permissions).Select(permission =>
|
||||||
new Claim(GreenFieldConstants.ClaimTypes.Permission, permission.ToString())));
|
new Claim(GreenFieldConstants.ClaimTypes.Permission, permission.ToString())));
|
||||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
@ -67,6 +68,7 @@ namespace BTCPayServer.Security.GreenField
|
||||||
new Claim(GreenFieldConstants.ClaimTypes.Permission,
|
new Claim(GreenFieldConstants.ClaimTypes.Permission,
|
||||||
Permission.Create(Policies.Unrestricted).ToString())
|
Permission.Create(Policies.Unrestricted).ToString())
|
||||||
};
|
};
|
||||||
|
claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s => new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s)));
|
||||||
|
|
||||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
new ClaimsPrincipal(new ClaimsIdentity(claims, GreenFieldConstants.AuthenticationType)),
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return NetworkType == NetworkType.Regtest && Environment.IsDevelopment();
|
return DevelopmentOverride?? NetworkType == NetworkType.Regtest && Environment.IsDevelopment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,5 +87,7 @@ namespace BTCPayServer.Services
|
||||||
}
|
}
|
||||||
return txt.ToString();
|
return txt.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool? DevelopmentOverride;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue