Compare commits

...

6 Commits

Author SHA1 Message Date
2111b67e2c Update changelog 2024-01-25 21:03:27 +09:00
b96cfcd14d Apps: Allow authenticated, non-owner users permissioned access (#5702)
Fixes #5698. Before this, the app lookup was constrained by the user having at least `CanModifyStoreSettings` permissions. This changes it to require the user being associated with a store, leaving the fine-grained authorization checks up to the individual actions.
2024-01-25 21:00:33 +09:00
086f713752 Wizard UI: Constrain navigation width (#5697)
This way the back and close buttons stay within the regular container size on and don't stick to the left and right end on wide screens.

Closes #5693.
2024-01-25 16:38:05 +09:00
fd67e09cf0 In Wallet Send, label were not applied to transactions (#5700) 2024-01-25 16:37:49 +09:00
6f4ca47532 Add documentation for wallet export on sparrow 2024-01-25 16:37:15 +09:00
f97f23c8a5 Do not dispose connections created by EF 2024-01-25 10:45:02 +09:00
10 changed files with 89 additions and 45 deletions

View File

@ -24,7 +24,7 @@
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageReference Include="Selenium.Support" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="119.0.6045.10500" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="121.0.6167.8500" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>

View File

@ -2332,10 +2332,27 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning();
await s.StartAsync();
await s.Server.EnsureChannelsSetup();
// Create users
var user = s.RegisterNewUser();
var userAccount = s.AsTestAccount();
s.GoToHome();
s.Logout();
s.GoToRegister();
s.RegisterNewUser(true);
// Setup store and associate user
s.CreateNewStore();
s.GoToStore();
s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToStore(StoreNavPages.Users);
s.Driver.FindElement(By.Id("Email")).Clear();
s.Driver.FindElement(By.Id("Email")).SendKeys(user);
new SelectElement(s.Driver.FindElement(By.Id("Role"))).SelectByValue("Guest");
s.Driver.FindElement(By.Id("AddUser")).Click();
Assert.Contains("User added successfully", s.FindAlertMessage().Text);
// Setup POS
s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
s.Driver.FindElement(By.Id("Create")).Click();
@ -2360,6 +2377,8 @@ namespace BTCPayServer.Tests
s.Driver.WaitForElement(By.ClassName("keypad"));
// basic checks
var keypadUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("RecentTransactionsToggle"));
Assert.Contains("EUR", s.Driver.FindElement(By.Id("Currency")).Text);
Assert.Contains("0,00", s.Driver.FindElement(By.Id("Amount")).Text);
Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text);
@ -2405,6 +2424,19 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("DetailsToggle")).Click();
s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat"));
Assert.Contains("1 222,21 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text);
// Guest user can access recent transactions
s.GoToHome();
s.Logout();
s.LogIn(user, userAccount.RegisterDetails.Password);
s.GoToUrl(keypadUrl);
s.Driver.FindElement(By.Id("RecentTransactionsToggle"));
s.GoToHome();
s.Logout();
// Unauthenticated user can't access recent transactions
s.GoToUrl(keypadUrl);
s.Driver.ElementDoesNotExist(By.Id("RecentTransactionsToggle"));
}
[Fact]

View File

@ -51,17 +51,31 @@ namespace BTCPayServer.HostedServices
// find all wallet objects that fit this transaction
// that means see if there are any utxo objects that match in/outs and scripts/addresses that match outs
var matchedObjects = transactionEvent.NewTransactionEvent.TransactionData.Transaction.Inputs
.Select<TxIn, ObjectTypeId>(txIn => new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()))
.Concat(transactionEvent.NewTransactionEvent.Outputs.SelectMany<NBXplorer.Models.MatchedOutput, ObjectTypeId>(txOut =>
new[]{
new ObjectTypeId(WalletObjectData.Types.Address, GetAddress(derivation, txOut, network).ToString()),
new ObjectTypeId(WalletObjectData.Types.Utxo, new OutPoint(transactionEvent.NewTransactionEvent.TransactionData.TransactionHash, (uint)txOut.Index).ToString())
var matchedObjects = new List<ObjectTypeId>();
// Check if inputs match some UTXOs
foreach (var txIn in transactionEvent.NewTransactionEvent.TransactionData.Transaction.Inputs)
{
matchedObjects.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()));
}
})).Distinct().ToArray();
// Check if outputs match some UTXOs
var walletOutputsByIndex = transactionEvent.NewTransactionEvent.Outputs.ToDictionary(o => (uint)o.Index);
foreach (var txOut in transactionEvent.NewTransactionEvent.TransactionData.Transaction.Outputs.AsIndexedOutputs())
{
BitcoinAddress? address = null;
// Technically, walletTxOut.Address can be calculated.
// However in liquid for example, this returns the blinded address
// rather than the unblinded one.
if (walletOutputsByIndex.TryGetValue(txOut.N, out var walletTxOut))
address = walletTxOut.Address;
address ??= txOut.TxOut.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
if (address is not null)
matchedObjects.Add(new ObjectTypeId(WalletObjectData.Types.Address, address.ToString()));
matchedObjects.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, new OutPoint(transactionEvent.NewTransactionEvent.TransactionData.TransactionHash, txOut.N).ToString()));
}
var objs = await _walletRepository.GetWalletObjects(new GetWalletObjectsQuery() { TypesIds = matchedObjects });
var objs = await _walletRepository.GetWalletObjects(new GetWalletObjectsQuery() { TypesIds = matchedObjects.Distinct().ToArray() });
var links = new List<WalletObjectLinkData>();
foreach (var walletObjectDatas in objs.GroupBy(data => data.Key.WalletId))
{
@ -112,11 +126,5 @@ namespace BTCPayServer.HostedServices
}
}
}
private BitcoinAddress GetAddress(DerivationStrategyBase derivationStrategy, NBXplorer.Models.MatchedOutput txOut, BTCPayNetwork network)
{
// Old version of NBX doesn't give address in the event, so we need to guess
return (txOut.Address ?? network.NBXplorerNetwork.CreateAddress(derivationStrategy, txOut.KeyPath, txOut.ScriptPubKey));
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
@ -74,10 +73,10 @@ namespace BTCPayServer.Security
// resolve from app
if (routeData.Values.TryGetValue("appId", out var vAppId) && vAppId is string appId)
{
app = await _appService.GetAppDataIfOwner(userId, appId);
app = await _appService.GetAppData(userId, appId);
if (storeId == null)
{
storeId = app?.StoreDataId ?? String.Empty;
storeId = app?.StoreDataId ?? string.Empty;
}
else if (app?.StoreDataId != storeId)
{
@ -90,7 +89,7 @@ namespace BTCPayServer.Security
paymentRequest = await _paymentRequestRepository.FindPaymentRequest(payReqId, userId);
if (storeId == null)
{
storeId = paymentRequest?.StoreDataId ?? String.Empty;
storeId = paymentRequest?.StoreDataId ?? string.Empty;
}
else if (paymentRequest?.StoreDataId != storeId)
{
@ -103,7 +102,7 @@ namespace BTCPayServer.Security
invoice = await _invoiceRepository.GetInvoice(invoiceId);
if (storeId == null)
{
storeId = invoice?.StoreId ?? String.Empty;
storeId = invoice?.StoreId ?? string.Empty;
}
else if (invoice?.StoreId != storeId)
{

View File

@ -371,10 +371,26 @@ namespace BTCPayServer.Services.Apps
return null;
await using var ctx = _ContextFactory.CreateContext();
var app = await ctx.UserStore
.Include(store => store.StoreRole)
.Where(us => us.ApplicationUserId == userId && us.StoreRole.Permissions.Contains(Policies.CanModifyStoreSettings))
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.FirstOrDefaultAsync();
.Include(store => store.StoreRole)
.Where(us => us.ApplicationUserId == userId && us.StoreRole.Permissions.Contains(Policies.CanModifyStoreSettings))
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.FirstOrDefaultAsync();
if (app == null)
return null;
if (type != null && type != app.AppType)
return null;
return app;
}
public async Task<AppData?> GetAppData(string userId, string appId, string? type = null)
{
if (userId == null || appId == null)
return null;
await using var ctx = _ContextFactory.CreateContext();
var app = await ctx.UserStore
.Where(us => us.ApplicationUserId == userId && us.StoreData != null && us.StoreData.UserStores.Any(u => u.ApplicationUserId == userId))
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.FirstOrDefaultAsync();
if (app == null)
return null;
if (type != null && type != app.AppType)

View File

@ -393,8 +393,7 @@ namespace BTCPayServer.Services
}
else
{
var conn = ctx.Database.GetDbConnection();
await conn.ExecuteAsync("INSERT INTO \"WalletObjectLinks\" VALUES (@WalletId, @AType, @AId, @BType, @BId, @Data::JSONB) ON CONFLICT DO NOTHING", links);
await connection.ExecuteAsync("INSERT INTO \"WalletObjectLinks\" VALUES (@WalletId, @AType, @AId, @BType, @BId, @Data::JSONB) ON CONFLICT DO NOTHING", links);
}
}
@ -700,11 +699,9 @@ namespace BTCPayServer.Services
walletObjectLinks ??= new List<WalletObjectLinkData>();
var objs = walletObjects.Concat(ExtractObjectsFromLinks(walletObjectLinks).Except(walletObjects)).ToArray();
await using var ctx = _ContextFactory.CreateContext();
await using var connection = ctx.Database.GetDbConnection();
await connection.OpenAsync();
var connection = ctx.Database.GetDbConnection();
await EnsureWalletObjects(ctx,connection, objs);
await EnsureWalletObjectLinks(ctx,connection, walletObjectLinks);
await connection.CloseAsync();
}
#nullable restore
}

View File

@ -45,6 +45,10 @@
<td>Electrum</td>
<td>File Save backup (not encrypted with a password)</td>
</tr>
<tr>
<td>Sparrow</td>
<td>File Export wallet… Electrum: export file…</td>
</tr>
<tr>
<td>Wasabi</td>
<td>Tools Wallet Manager Open Wallets Folder</td>

View File

@ -33,7 +33,7 @@
</select>
</div>
<div class="ms-3">
<button type="submit" role="button" class="btn btn-primary">Add User</button>
<button type="submit" role="button" class="btn btn-primary" id="AddUser">Add User</button>
</div>
</div>
</form>

View File

@ -7,21 +7,7 @@ body {
display: flex;
align-items: center;
justify-content: space-between;
}
@media (max-width: 575px) {
#wizard-navbar {
margin-top: -35px;
}
}
@media (min-width: 576px) {
#wizard-navbar {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 24px;
}
margin-top: -2rem;
}
#wizard-navbar a,

View File

@ -15,6 +15,8 @@
* Fix: Test webhook for payment requests (#5680) @Kukks
* Fix: Sometimes importing a wallet file from Electrum would fail @NicolasDorier
* Fix: Creating a Store as a Guest generates a 403 error (#5688 #5689) @dennisreimann
* Fix: In Wallet Send, label were not applied to transactions (#5700) @NicolasDorier
* Fix: "View recent invoices" in Keypad PoS should be accessible for authenticated Guest users (#5702 #5698) @dennisreimann
### Improvements