mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +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
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
[BitpayAPIConstraint]
|
||||||
public class AccessTokenController : Controller
|
public class AccessTokenController : Controller
|
||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
|
|
|
@ -22,17 +22,14 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
private InvoiceController _InvoiceController;
|
private InvoiceController _InvoiceController;
|
||||||
private InvoiceRepository _InvoiceRepository;
|
private InvoiceRepository _InvoiceRepository;
|
||||||
private StoreRepository _StoreRepository;
|
|
||||||
private BTCPayNetworkProvider _NetworkProvider;
|
private BTCPayNetworkProvider _NetworkProvider;
|
||||||
|
|
||||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||||
InvoiceRepository invoceRepository,
|
InvoiceRepository invoceRepository,
|
||||||
StoreRepository storeRepository,
|
|
||||||
BTCPayNetworkProvider networkProvider)
|
BTCPayNetworkProvider networkProvider)
|
||||||
{
|
{
|
||||||
this._InvoiceController = invoiceController;
|
this._InvoiceController = invoiceController;
|
||||||
this._InvoiceRepository = invoceRepository;
|
this._InvoiceRepository = invoceRepository;
|
||||||
this._StoreRepository = storeRepository;
|
|
||||||
this._NetworkProvider = networkProvider;
|
this._NetworkProvider = networkProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,20 +38,14 @@ namespace BTCPayServer.Controllers
|
||||||
[MediaTypeConstraint("application/json")]
|
[MediaTypeConstraint("application/json")]
|
||||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
|
||||||
{
|
{
|
||||||
var store = await _StoreRepository.FindStore(this.User.GetStoreId());
|
return await _InvoiceController.CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot());
|
||||||
if (store == null)
|
|
||||||
throw new BitpayHttpException(401, "Can't access to store");
|
|
||||||
return await _InvoiceController.CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices/{id}")]
|
[Route("invoices/{id}")]
|
||||||
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
|
||||||
{
|
{
|
||||||
var store = await _StoreRepository.FindStore(this.User.GetStoreId());
|
var invoice = await _InvoiceRepository.GetInvoice(HttpContext.GetStoreData().Id, id);
|
||||||
if (store == null)
|
|
||||||
throw new BitpayHttpException(401, "Can't access to store");
|
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(store.Id, id);
|
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
throw new BitpayHttpException(404, "Object not found");
|
throw new BitpayHttpException(404, "Object not found");
|
||||||
var resp = invoice.EntityToDTO(_NetworkProvider);
|
var resp = invoice.EntityToDTO(_NetworkProvider);
|
||||||
|
@ -75,10 +66,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
if (dateEnd != null)
|
if (dateEnd != null)
|
||||||
dateEnd = dateEnd.Value + TimeSpan.FromDays(1); //Should include the end day
|
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()
|
var query = new InvoiceQuery()
|
||||||
{
|
{
|
||||||
Count = limit,
|
Count = limit,
|
||||||
|
@ -88,10 +76,9 @@ namespace BTCPayServer.Controllers
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
ItemCode = itemCode,
|
ItemCode = itemCode,
|
||||||
Status = status == null ? null : new[] { status },
|
Status = status == null ? null : new[] { status },
|
||||||
StoreId = new[] { store.Id }
|
StoreId = new[] { this.HttpContext.GetStoreData().Id }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||||
.Select((o) => o.EntityToDTO(_NetworkProvider)).ToArray();
|
.Select((o) => o.EntityToDTO(_NetworkProvider)).ToArray();
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ using BTCPayServer.Models;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
|
@ -153,6 +154,26 @@ namespace BTCPayServer
|
||||||
return principal.Claims.Where(c => c.Type == Claims.OwnStore).Select(c => c.Value).FirstOrDefault();
|
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() };
|
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||||
public static string ToJson(this object o)
|
public static string ToJson(this object o)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,11 +43,7 @@ namespace BTCPayServer.Filters
|
||||||
|
|
||||||
public bool Accept(ActionConstraintContext context)
|
public bool Accept(ActionConstraintContext context)
|
||||||
{
|
{
|
||||||
var hasVersion = context.RouteContext.HttpContext.Request.Headers["x-accept-version"].Where(h => h == "2.0.0").Any();
|
return context.RouteContext.HttpContext.GetIsBitpayAPI() == IsBitpayAPI;
|
||||||
var isBitpayAPI =
|
|
||||||
context.RouteContext.HttpContext.Items.TryGetValue("IsBitpayAPI", out object obj) &&
|
|
||||||
obj is bool b && b;
|
|
||||||
return (hasVersion || isBitpayAPI) == IsBitpayAPI;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,20 +27,24 @@ using System.Security.Claims;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using BTCPayServer.Services.Stores;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
public class BTCPayMiddleware
|
public class BTCPayMiddleware
|
||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
|
StoreRepository _StoreRepository;
|
||||||
RequestDelegate _Next;
|
RequestDelegate _Next;
|
||||||
BTCPayServerOptions _Options;
|
BTCPayServerOptions _Options;
|
||||||
|
|
||||||
public BTCPayMiddleware(RequestDelegate next,
|
public BTCPayMiddleware(RequestDelegate next,
|
||||||
TokenRepository tokenRepo,
|
TokenRepository tokenRepo,
|
||||||
|
StoreRepository storeRepo,
|
||||||
BTCPayServerOptions options)
|
BTCPayServerOptions options)
|
||||||
{
|
{
|
||||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||||
|
_StoreRepository = storeRepo;
|
||||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||||
_Options = options ?? throw new ArgumentNullException(nameof(options));
|
_Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
@ -49,27 +53,48 @@ namespace BTCPayServer.Hosting
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
RewriteHostIfNeeded(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
|
try
|
||||||
{
|
{
|
||||||
bool isBitId = false;
|
var bitpayAuth = GetBitpayAuth(httpContext, out bool isBitpayAuth);
|
||||||
if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
|
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);
|
await _Next(httpContext);
|
||||||
}
|
}
|
||||||
catch (WebSocketException)
|
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)
|
private void RewriteHostIfNeeded(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
string reverseProxyScheme = null;
|
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();
|
httpContext.Request.EnableRewind();
|
||||||
|
|
||||||
|
string storeId = null;
|
||||||
string body = string.Empty;
|
string body = string.Empty;
|
||||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||||
{
|
{
|
||||||
|
@ -227,23 +302,22 @@ namespace BTCPayServer.Hosting
|
||||||
var bitToken = await GetTokenPermissionAsync(sin, token);
|
var bitToken = await GetTokenPermissionAsync(sin, token);
|
||||||
if (bitToken == null)
|
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) { }
|
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);
|
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
throw new BitpayHttpException(401, $"Invalid Authorization header");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
string apiKey = null;
|
string apiKey = null;
|
||||||
|
@ -253,16 +327,9 @@ namespace BTCPayServer.Hosting
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw new BitpayHttpException(401, $"Invalid Authorization header");
|
return null;
|
||||||
}
|
}
|
||||||
var storeId = await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
return 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
||||||
|
|
Loading…
Add table
Reference in a new issue