Remove TokenRepository dependency from InvoiceControllerAPI

This commit is contained in:
nicolas.dorier
2018-04-28 02:51:20 +09:00
parent 8ff81f1648
commit 07c2f6b810
5 changed files with 113 additions and 83 deletions

View File

@@ -22,19 +22,16 @@ namespace BTCPayServer.Controllers
{ {
private InvoiceController _InvoiceController; private InvoiceController _InvoiceController;
private InvoiceRepository _InvoiceRepository; private InvoiceRepository _InvoiceRepository;
private TokenRepository _TokenRepository;
private StoreRepository _StoreRepository; private StoreRepository _StoreRepository;
private BTCPayNetworkProvider _NetworkProvider; private BTCPayNetworkProvider _NetworkProvider;
public InvoiceControllerAPI(InvoiceController invoiceController, public InvoiceControllerAPI(InvoiceController invoiceController,
InvoiceRepository invoceRepository, InvoiceRepository invoceRepository,
TokenRepository tokenRepository,
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider networkProvider) BTCPayNetworkProvider networkProvider)
{ {
this._InvoiceController = invoiceController; this._InvoiceController = invoiceController;
this._InvoiceRepository = invoceRepository; this._InvoiceRepository = invoceRepository;
this._TokenRepository = tokenRepository;
this._StoreRepository = storeRepository; this._StoreRepository = storeRepository;
this._NetworkProvider = networkProvider; this._NetworkProvider = networkProvider;
} }
@@ -44,8 +41,9 @@ 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 bitToken = await CheckTokenPermissionAsync(Facade.Merchant, invoice.Token); var store = await _StoreRepository.FindStore(this.User.GetStoreId());
var store = await FindStore(bitToken); 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, store, HttpContext.Request.GetAbsoluteRoot());
} }
@@ -53,12 +51,12 @@ namespace BTCPayServer.Controllers
[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 bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token); var store = await _StoreRepository.FindStore(this.User.GetStoreId());
var store = await FindStore(bitToken); 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(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);
return new DataWrapper<InvoiceResponse>(resp); return new DataWrapper<InvoiceResponse>(resp);
} }
@@ -77,8 +75,10 @@ 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 bitToken = await CheckTokenPermissionAsync(Facade.Merchant, token);
var store = await FindStore(bitToken); 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,
@@ -97,45 +97,5 @@ namespace BTCPayServer.Controllers
return DataWrapper.Create(entities); return DataWrapper.Create(entities);
} }
private async Task<BitTokenEntity> CheckTokenPermissionAsync(Facade facade, string expectedToken)
{
if (facade == null)
throw new ArgumentNullException(nameof(facade));
var actualTokens = (await _TokenRepository.GetTokens(this.User.GetSIN())).ToArray();
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
if (expectedToken == null || actualToken == null)
{
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.User.GetSIN()}");
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
}
return actualToken;
}
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
{
if (token.Facade == Facade.Merchant.ToString())
{
yield return token.Clone(Facade.User);
yield return token.Clone(Facade.PointOfSale);
}
if (token.Facade == Facade.PointOfSale.ToString())
{
yield return token.Clone(Facade.User);
}
yield return token;
}
private async Task<StoreData> FindStore(BitTokenEntity bitToken)
{
var store = await _StoreRepository.FindStore(bitToken.StoreId);
if (store == null)
throw new BitpayHttpException(401, "Unknown store");
return store;
}
} }
} }

View File

@@ -148,6 +148,11 @@ namespace BTCPayServer
return principal.Claims.Where(c => c.Type == Claims.SIN).Select(c => c.Value).FirstOrDefault(); return principal.Claims.Where(c => c.Type == Claims.SIN).Select(c => c.Value).FirstOrDefault();
} }
public static string GetStoreId(this ClaimsPrincipal principal)
{
return principal.Claims.Where(c => c.Type == Claims.OwnStore).Select(c => c.Value).FirstOrDefault();
}
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)
{ {

View File

@@ -25,6 +25,8 @@ using BTCPayServer.Controllers;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Claims; using System.Security.Claims;
using BTCPayServer.Services; using BTCPayServer.Services;
using NBitpayClient;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Hosting namespace BTCPayServer.Hosting
{ {
@@ -51,39 +53,12 @@ namespace BTCPayServer.Hosting
var sig = values.FirstOrDefault(); var sig = values.FirstOrDefault();
httpContext.Request.Headers.TryGetValue("x-identity", out values); httpContext.Request.Headers.TryGetValue("x-identity", out values);
var id = values.FirstOrDefault(); var id = values.FirstOrDefault();
try
{
if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id)) if (!string.IsNullOrEmpty(sig) && !string.IsNullOrEmpty(id))
{ {
httpContext.Request.EnableRewind(); await HandleBitId(httpContext, sig, id);
string body = string.Empty;
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
{
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
{
body = reader.ReadToEnd();
} }
httpContext.Request.Body.Position = 0;
}
var url = httpContext.Request.GetEncodedUrl();
try
{
var key = new PubKey(id);
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
{
var sin = key.GetBitIDSIN();
var identity = ((ClaimsIdentity)httpContext.User.Identity);
identity.AddClaim(new Claim(Claims.SIN, sin));
Logs.PayServer.LogDebug($"BitId signature check success for SIN {sin}");
}
}
catch (FormatException) { }
if (!httpContext.User.HasClaim(c=> c.Type == Claims.SIN))
Logs.PayServer.LogDebug("BitId signature check failed");
}
try
{
await _Next(httpContext); await _Next(httpContext);
} }
catch (WebSocketException) catch (WebSocketException)
@@ -195,5 +170,90 @@ namespace BTCPayServer.Hosting
await writer.FlushAsync(); await writer.FlushAsync();
} }
} }
private async Task HandleBitId(HttpContext httpContext, string sig, string id)
{
httpContext.Request.EnableRewind();
string body = string.Empty;
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
{
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
{
body = reader.ReadToEnd();
}
httpContext.Request.Body.Position = 0;
}
var url = httpContext.Request.GetEncodedUrl();
try
{
var key = new PubKey(id);
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
{
var sin = key.GetBitIDSIN();
var identity = ((ClaimsIdentity)httpContext.User.Identity);
identity.AddClaim(new Claim(Claims.SIN, sin));
string token = null;
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
{
token = tokenValues[0];
}
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
{
try
{
token = JObject.Parse(body)?.Property("token")?.Value?.Value<string>();
}
catch { }
}
if (token != null)
{
var bitToken = await GetTokenPermissionAsync(sin, token);
if (bitToken == null)
{
throw new BitpayHttpException(401, $"This endpoint does not support this facade");
}
identity.AddClaim(new Claim(Claims.OwnStore, bitToken.StoreId));
}
Logs.PayServer.LogDebug($"BitId signature check success for SIN {sin}");
}
}
catch (FormatException) { }
if (!httpContext.User.HasClaim(c => c.Type == Claims.SIN))
Logs.PayServer.LogDebug("BitId signature check failed");
}
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
{
var actualTokens = (await _TokenRepository.GetTokens(sin)).ToArray();
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
if (expectedToken == null || actualToken == null)
{
Logs.PayServer.LogDebug($"No token found for facade {Facade.Merchant} for SIN {sin}");
return null;
}
return actualToken;
}
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
{
if (token.Facade == Facade.Merchant.ToString())
{
yield return token.Clone(Facade.User);
yield return token.Clone(Facade.PointOfSale);
}
if (token.Facade == Facade.PointOfSale.ToString())
{
yield return token.Clone(Facade.User);
}
yield return token;
}
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -8,5 +9,7 @@ namespace BTCPayServer.Services
public class Claims public class Claims
{ {
public const string SIN = "BITID_SIN"; public const string SIN = "BITID_SIN";
public const string OwnStore = "BTCPAY_OWN_STORE";
} }
} }

View File

@@ -20,6 +20,8 @@ namespace BTCPayServer.Services.Stores
public async Task<StoreData> FindStore(string storeId) public async Task<StoreData> FindStore(string storeId)
{ {
if (storeId == null)
return null;
using (var ctx = _ContextFactory.CreateContext()) using (var ctx = _ContextFactory.CreateContext())
{ {
return await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false); return await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false);