mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 14:04:12 +01:00
Make sure that we don't authenticate call with bitpay auth methods on non bitpay calls
This commit is contained in:
parent
2848caff2e
commit
f0145142a4
5 changed files with 127 additions and 55 deletions
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[BitpayAPIConstraint]
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
|
|
|
@ -22,17 +22,14 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
private InvoiceController _InvoiceController;
|
||||
private InvoiceRepository _InvoiceRepository;
|
||||
private StoreRepository _StoreRepository;
|
||||
private BTCPayNetworkProvider _NetworkProvider;
|
||||
|
||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||
InvoiceRepository invoceRepository,
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
this._InvoiceController = invoiceController;
|
||||
this._InvoiceRepository = invoceRepository;
|
||||
this._StoreRepository = storeRepository;
|
||||
this._NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
|
@ -41,20 +38,14 @@ namespace BTCPayServer.Controllers
|
|||
[MediaTypeConstraint("application/json")]
|
||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore(this.User.GetStoreId());
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Can't access to store");
|
||||
return await _InvoiceController.CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
return await _InvoiceController.CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices/{id}")]
|
||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore(this.User.GetStoreId());
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Can't access to store");
|
||||
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
||||
var invoice = await _InvoiceRepository.GetInvoice(HttpContext.GetStoreData().Id, id);
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
var resp = invoice.EntityToDTO(_NetworkProvider);
|
||||
|
@ -75,10 +66,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
if (dateEnd != null)
|
||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
||||
|
||||
var store = await _StoreRepository.FindStore(this.User.GetStoreId());
|
||||
if (store == null)
|
||||
throw new BitpayHttpException(401, "Can't access to store");
|
||||
|
||||
var query = new InvoiceQuery()
|
||||
{
|
||||
Count = limit,
|
||||
|
@ -88,10 +76,9 @@ namespace BTCPayServer.Controllers
|
|||
OrderId = orderId,
|
||||
ItemCode = itemCode,
|
||||
Status = status == null ? null : new[] { status },
|
||||
StoreId = new[] { store.Id }
|
||||
StoreId = new[] { this.HttpContext.GetStoreData().Id }
|
||||
};
|
||||
|
||||
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO(_NetworkProvider)).ToArray();
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ using BTCPayServer.Models;
|
|||
using System.Security.Claims;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
@ -153,6 +154,26 @@ namespace BTCPayServer
|
|||
return principal.Claims.Where(c => c.Type == Claims.OwnStore).Select(c => c.Value).FirstOrDefault();
|
||||
}
|
||||
|
||||
public static void SetIsBitpayAPI(this HttpContext ctx, bool value)
|
||||
{
|
||||
NBitcoin.Extensions.TryAdd(ctx.Items, "IsBitpayAPI", value);
|
||||
}
|
||||
|
||||
public static bool GetIsBitpayAPI(this HttpContext ctx)
|
||||
{
|
||||
return ctx.Items.TryGetValue("IsBitpayAPI", out object obj) &&
|
||||
obj is bool b && b;
|
||||
}
|
||||
|
||||
public static StoreData GetStoreData(this HttpContext ctx)
|
||||
{
|
||||
return ctx.Items.TryGet("BTCPAY.STOREDATA") as StoreData;
|
||||
}
|
||||
public static void SetStoreData(this HttpContext ctx, StoreData storeData)
|
||||
{
|
||||
ctx.Items["BTCPAY.STOREDATA"] = storeData;
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
public static string ToJson(this object o)
|
||||
{
|
||||
|
|
|
@ -43,11 +43,7 @@ namespace BTCPayServer.Filters
|
|||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var hasVersion = context.RouteContext.HttpContext.Request.Headers["x-accept-version"].Where(h => h == "2.0.0").Any();
|
||||
var isBitpayAPI =
|
||||
context.RouteContext.HttpContext.Items.TryGetValue("IsBitpayAPI", out object obj) &&
|
||||
obj is bool b && b;
|
||||
return (hasVersion || isBitpayAPI) == IsBitpayAPI;
|
||||
return context.RouteContext.HttpContext.GetIsBitpayAPI() == IsBitpayAPI;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,20 +27,24 @@ using System.Security.Claims;
|
|||
using BTCPayServer.Services;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public class BTCPayMiddleware
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
StoreRepository _StoreRepository;
|
||||
RequestDelegate _Next;
|
||||
BTCPayServerOptions _Options;
|
||||
|
||||
public BTCPayMiddleware(RequestDelegate next,
|
||||
TokenRepository tokenRepo,
|
||||
StoreRepository storeRepo,
|
||||
BTCPayServerOptions options)
|
||||
{
|
||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||
_StoreRepository = storeRepo;
|
||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
_Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
@ -49,27 +53,48 @@ namespace BTCPayServer.Hosting
|
|||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
RewriteHostIfNeeded(httpContext);
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
var id = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("Authorization", out values);
|
||||
var auth = values.FirstOrDefault();
|
||||
|
||||
try
|
||||
{
|
||||
bool isBitId = false;
|
||||
if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
||||
var bitpayAuth = GetBitpayAuth(httpContext, out bool isBitpayAuth);
|
||||
var isBitpayAPI = IsBitpayAPI(httpContext, isBitpayAuth);
|
||||
httpContext.SetIsBitpayAPI(isBitpayAPI);
|
||||
if (isBitpayAPI)
|
||||
{
|
||||
await HandleBitId(httpContext, sig, id);
|
||||
isBitId = httpContext.User.HasClaim(c => c.Type == Claims.SIN);
|
||||
if (!isBitId)
|
||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||
}
|
||||
if (!isBitId && !string.IsNullOrEmpty(auth))
|
||||
{
|
||||
await HandleLegacyAPIKey(httpContext, auth);
|
||||
}
|
||||
|
||||
string storeId = null;
|
||||
var failedAuth = false;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
storeId = await CheckBitId(httpContext, bitpayAuth.Signature, bitpayAuth.Id);
|
||||
if (!httpContext.User.Claims.Any(c => c.Type == Claims.SIN))
|
||||
{
|
||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(httpContext, bitpayAuth.Authorization);
|
||||
if (storeId == null)
|
||||
{
|
||||
Logs.PayServer.LogDebug("API key check failed");
|
||||
failedAuth = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (storeId != null)
|
||||
{
|
||||
var identity = ((ClaimsIdentity)httpContext.User.Identity);
|
||||
identity.AddClaim(new Claim(Claims.OwnStore, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
httpContext.SetStoreData(store);
|
||||
}
|
||||
else if (failedAuth)
|
||||
{
|
||||
throw new BitpayHttpException(401, "Can't access to store");
|
||||
}
|
||||
}
|
||||
await _Next(httpContext);
|
||||
}
|
||||
catch (WebSocketException)
|
||||
|
@ -89,6 +114,55 @@ namespace BTCPayServer.Hosting
|
|||
}
|
||||
}
|
||||
|
||||
private static (string Signature, String Id, String Authorization) GetBitpayAuth(HttpContext httpContext, out bool hasBitpayAuth)
|
||||
{
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
var id = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("Authorization", out values);
|
||||
var auth = values.FirstOrDefault();
|
||||
hasBitpayAuth = auth != null || (sig != null && id != null);
|
||||
return (sig, id, auth);
|
||||
}
|
||||
|
||||
private bool IsBitpayAPI(HttpContext httpContext, bool bitpayAuth)
|
||||
{
|
||||
if (!httpContext.Request.Path.HasValue)
|
||||
return false;
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
if (
|
||||
bitpayAuth &&
|
||||
path == "/invoices" &&
|
||||
httpContext.Request.Method == "POST" &&
|
||||
(httpContext.Request.ContentType ?? string.Empty).StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (
|
||||
bitpayAuth &&
|
||||
path == "/invoices" &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
if (
|
||||
bitpayAuth &&
|
||||
path.StartsWith("/invoices/", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
if (path.Equals("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
if (
|
||||
path.Equals("/tokens", StringComparison.Ordinal) &&
|
||||
( httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RewriteHostIfNeeded(HttpContext httpContext)
|
||||
{
|
||||
string reverseProxyScheme = null;
|
||||
|
@ -183,10 +257,11 @@ namespace BTCPayServer.Hosting
|
|||
}
|
||||
|
||||
|
||||
private async Task HandleBitId(HttpContext httpContext, string sig, string id)
|
||||
private async Task<string> CheckBitId(HttpContext httpContext, string sig, string id)
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
string storeId = null;
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
|
@ -227,23 +302,22 @@ namespace BTCPayServer.Hosting
|
|||
var bitToken = await GetTokenPermissionAsync(sin, token);
|
||||
if (bitToken == null)
|
||||
{
|
||||
throw new BitpayHttpException(401, $"This endpoint does not support this facade");
|
||||
return null;
|
||||
}
|
||||
identity.AddClaim(new Claim(Claims.OwnStore, bitToken.StoreId));
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
Logs.PayServer.LogDebug($"BitId signature check success for SIN {sin}");
|
||||
NBitcoin.Extensions.TryAdd(httpContext.Items, "IsBitpayAPI", true);
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return storeId;
|
||||
}
|
||||
|
||||
private async Task HandleLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
{
|
||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new BitpayHttpException(401, $"Invalid Authorization header");
|
||||
return null;
|
||||
}
|
||||
|
||||
string apiKey = null;
|
||||
|
@ -253,16 +327,9 @@ namespace BTCPayServer.Hosting
|
|||
}
|
||||
catch
|
||||
{
|
||||
throw new BitpayHttpException(401, $"Invalid Authorization header");
|
||||
return null;
|
||||
}
|
||||
var storeId = await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
if (storeId == null)
|
||||
{
|
||||
throw new BitpayHttpException(401, $"Invalid Authorization header");
|
||||
}
|
||||
var identity = ((ClaimsIdentity)httpContext.User.Identity);
|
||||
identity.AddClaim(new Claim(Claims.OwnStore, storeId));
|
||||
NBitcoin.Extensions.TryAdd(httpContext.Items, "IsBitpayAPI", true);
|
||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
}
|
||||
|
||||
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
||||
|
|
Loading…
Add table
Reference in a new issue