Merge pull request #1857 from Kukks/fix-greenfield-roles

Set roles when authenticating via greenfield
This commit is contained in:
Nicolas Dorier 2020-09-11 22:16:18 +09:00 committed by GitHub
commit 0a8fb1b835
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 93 deletions

View file

@ -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)]

View file

@ -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()

View file

@ -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)

View file

@ -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(

View file

@ -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)),

View file

@ -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;
} }
} }