Remove unused scope, assert policy on store listing

This commit is contained in:
nicolas.dorier 2019-10-18 23:42:06 +09:00
parent 8643c04a39
commit da2e8665a1
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
9 changed files with 76 additions and 194 deletions

View file

@ -133,50 +133,51 @@ namespace BTCPayServer.Tests
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]); results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await TestApiAgainstAccessToken(results["access_token"], tester, user); await TestApiAgainstAccessToken(results["access_token"], tester, user);
LogoutFlow(tester, id, s); var stores = await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/me/stores",
tester.PayTester.HttpClient);
Assert.NotEmpty(stores);
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient));
//we dont ask for consent after acquiring it the first time for the same scopes. //we dont ask for consent after acquiring it the first time for the same scopes.
LogoutFlow(tester, id, s);
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl); s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.AssertElementNotFound(By.Id("consent-yes"));
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
results = url.Split("#").Last().Split("&") // Let's asks without scopes
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]); LogoutFlow(tester, id, s);
await TestApiAgainstAccessToken(results["access_token"], tester, user); id = Guid.NewGuid().ToString();
openIdClient = await user.RegisterOpenIdClient(
//let's test out scopes! new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = { OpenIddictConstants.Permissions.GrantTypes.Implicit },
RedirectUris = { redirecturi },
});
implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri, implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid {RestAPIPolicies.BTCPayScopes.AppManagement} {RestAPIPolicies.BTCPayScopes.ViewStores} &nonce={Guid.NewGuid().ToString()}"); $"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl); s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
//authorize form should show now that we have asked for more scopes s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click(); s.Driver.FindElement(By.Id("consent-yes")).Click();
url = s.Driver.Url; results = s.Driver.Url.Split("#").Last().Split("&")
results = url.Split("#").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]); .ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/ScopeCanViewApps",
tester.PayTester.HttpClient));
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/ScopeCanManageApps",
tester.PayTester.HttpClient));
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/ScopeCanViewStores",
tester.PayTester.HttpClient));
await Assert.ThrowsAnyAsync<HttpRequestException>(async () => await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{ {
await TestApiAgainstAccessToken<bool>(results["access_token"], await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/ScopeCanManageStores", $"api/test/me/stores",
tester.PayTester.HttpClient); tester.PayTester.HttpClient);
}); });
await Assert.ThrowsAnyAsync<HttpRequestException>(async () => await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{ {
await TestApiAgainstAccessToken<bool>(results["access_token"], await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/ScopeCanViewProfile", $"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient); tester.PayTester.HttpClient);
}); });
} }
} }

View file

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using OpenIddict.Abstractions;
namespace BTCPayServer.Authentication
{
public static class RestAPIPolicies
{
public static class BTCPayScopes
{
public const string ViewStores = "view_stores";
//Create and manage stores
public const string StoreManagement = "store_management";
//create and manage invoices
public const string ViewInvoices = "view_invoices";
//create and manage invoices
public const string CreateInvoices = "create_invoice";
public const string InvoiceManagement = "manage_invoices";
//view apps
public const string ViewApps = "view_apps";
//create and manage apps
public const string AppManagement = "app_management";
public const string WalletManagement = "wallet_management";
public const string ServerManagement = "server_management";
}
public const string CanViewStores = nameof(CanViewStores);
public const string CanManageStores = nameof(CanManageStores);
public const string CanViewInvoices = nameof(CanViewInvoices);
public const string CanCreateInvoices = nameof(CanCreateInvoices);
public const string CanManageInvoices = nameof(CanManageInvoices);
public const string CanManageApps = nameof(CanManageApps);
public const string CanViewApps = nameof(CanViewApps);
public const string CanManageWallet = nameof(CanManageWallet);
public const string CanViewProfile = nameof(CanViewProfile);
}
}

View file

@ -48,6 +48,8 @@ namespace BTCPayServer.Controllers.RestApi
} }
[HttpGet("me/stores")] [HttpGet("me/stores")]
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public async Task<StoreData[]> GetCurrentUserStores() public async Task<StoreData[]> GetCurrentUserStores()
{ {
return await _storeRepository.GetStoresByUserId(_userManager.GetUserId(User)); return await _storeRepository.GetStoresByUserId(_userManager.GetUserId(User));
@ -61,56 +63,5 @@ namespace BTCPayServer.Controllers.RestApi
{ {
return true; return true;
} }
#region scopes tests
[Authorize(Policy = RestAPIPolicies.CanViewStores,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanViewStores))]
public bool ScopeCanViewStores() { return true; }
[Authorize(Policy = RestAPIPolicies.CanManageStores,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanManageStores))]
public bool ScopeCanManageStores() { return true; }
[Authorize(Policy = RestAPIPolicies.CanViewInvoices,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanViewInvoices))]
public bool ScopeCanViewInvoices() { return true; }
[Authorize(Policy = RestAPIPolicies.CanCreateInvoices,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanCreateInvoices))]
public bool ScopeCanCreateInvoices() { return true; }
[Authorize(Policy = RestAPIPolicies.CanManageInvoices,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanManageInvoices))]
public bool ScopeCanManageInvoices() { return true; }
[Authorize(Policy = RestAPIPolicies.CanManageApps,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanManageApps))]
public bool ScopeCanManageApps() { return true; }
[Authorize(Policy = RestAPIPolicies.CanViewApps,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanViewApps))]
public bool ScopeCanViewApps() { return true; }
[Authorize(Policy = RestAPIPolicies.CanManageWallet,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanManageWallet))]
public bool ScopeCanManageWallet() { return true; }
[Authorize(Policy = RestAPIPolicies.CanViewProfile,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
[HttpGet(nameof(ScopeCanViewProfile))]
public bool ScopeCanViewProfile() { return true; }
#endregion
} }
} }

View file

@ -184,13 +184,9 @@ namespace BTCPayServer.Hosting
options.RegisterScopes( options.RegisterScopes(
OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.OpenId,
RestAPIPolicies.BTCPayScopes.ViewStores, BTCPayScopes.StoreManagement,
RestAPIPolicies.BTCPayScopes.CreateInvoices, BTCPayScopes.ServerManagement
RestAPIPolicies.BTCPayScopes.StoreManagement, );
RestAPIPolicies.BTCPayScopes.ViewApps,
RestAPIPolicies.BTCPayScopes.ServerManagement,
RestAPIPolicies.BTCPayScopes.AppManagement
);
options.AddEventHandler(PasswordGrantTypeEventHandler.Descriptor); options.AddEventHandler(PasswordGrantTypeEventHandler.Descriptor);
options.AddEventHandler(OpenIdGrantHandlerCheckCanSignIn.Descriptor); options.AddEventHandler(OpenIdGrantHandlerCheckCanSignIn.Descriptor);
options.AddEventHandler(ClientCredentialsGrantTypeEventHandler.Descriptor); options.AddEventHandler(ClientCredentialsGrantTypeEventHandler.Descriptor);

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using OpenIddict.Abstractions;
namespace BTCPayServer.Security.OpenId
{
public static class BTCPayScopes
{
public const string StoreManagement = "store_management";
public const string ServerManagement = "server_management";
}
public static class RestAPIPolicies
{
}
}

View file

@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Identity;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using OpenIddict.Core; using OpenIddict.Core;
using OpenIddict.Server; using OpenIddict.Server;
using static BTCPayServer.Authentication.RestAPIPolicies;
namespace BTCPayServer.Security.OpenId namespace BTCPayServer.Security.OpenId
{ {
@ -93,7 +92,7 @@ namespace BTCPayServer.Security.OpenId
await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1)); await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1));
var authorizationsWithSufficientScopes = scopeTasks var authorizationsWithSufficientScopes = scopeTasks
.Select((tuple) => (tuple.Id, Scopes: tuple.Item1.Result)) .Select((tuple) => (Id: tuple.Id, Scopes: tuple.Item1.Result))
.Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any()); .Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any());
if (authorizationsWithSufficientScopes.Any()) if (authorizationsWithSufficientScopes.Any())

View file

@ -11,8 +11,9 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using BTCPayServer.Authentication; using BTCPayServer.Authentication;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using static BTCPayServer.Authentication.RestAPIPolicies; using static BTCPayServer.Security.OpenId.RestAPIPolicies;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using BTCPayServer.Security.OpenId;
namespace BTCPayServer.Security namespace BTCPayServer.Security
{ {
@ -38,33 +39,6 @@ namespace BTCPayServer.Security
bool success = false; bool success = false;
switch (requirement.Policy) switch (requirement.Policy)
{ {
case RestAPIPolicies.CanViewStores:
success = context.HasScopes(BTCPayScopes.StoreManagement) || context.HasScopes(BTCPayScopes.ViewStores);
break;
case RestAPIPolicies.CanManageStores:
success = context.HasScopes(BTCPayScopes.StoreManagement);
break;
case RestAPIPolicies.CanViewInvoices:
success = context.HasScopes(BTCPayScopes.ViewInvoices) || context.HasScopes(BTCPayScopes.InvoiceManagement);
break;
case RestAPIPolicies.CanCreateInvoices:
success = context.HasScopes(BTCPayScopes.CreateInvoices) || context.HasScopes(BTCPayScopes.InvoiceManagement);
break;
case RestAPIPolicies.CanViewApps:
success = context.HasScopes(BTCPayScopes.AppManagement) || context.HasScopes(BTCPayScopes.ViewApps);
break;
case RestAPIPolicies.CanManageInvoices:
success = context.HasScopes(BTCPayScopes.InvoiceManagement);
break;
case RestAPIPolicies.CanManageApps:
success = context.HasScopes(BTCPayScopes.AppManagement);
break;
case RestAPIPolicies.CanManageWallet:
success = context.HasScopes(BTCPayScopes.WalletManagement);
break;
case RestAPIPolicies.CanViewProfile:
success = context.HasScopes(OpenIddictConstants.Scopes.Profile);
break;
case Policies.CanModifyStoreSettings.Key: case Policies.CanModifyStoreSettings.Key:
if (!context.HasScopes(BTCPayScopes.StoreManagement)) if (!context.HasScopes(BTCPayScopes.StoreManagement))
break; break;
@ -73,15 +47,20 @@ namespace BTCPayServer.Security
// to the access_token // to the access_token
string storeId = _HttpContext.GetImplicitStoreId(); string storeId = _HttpContext.GetImplicitStoreId();
if (storeId == null) if (storeId == null)
break; {
var userid = _userManager.GetUserId(context.User); success = true;
if (string.IsNullOrEmpty(userid)) }
break; else
var store = await _storeRepository.FindStore((string)storeId, userid); {
if (store == null) var userid = _userManager.GetUserId(context.User);
break; if (string.IsNullOrEmpty(userid))
success = true; break;
_HttpContext.SetStoreData(store); var store = await _storeRepository.FindStore((string)storeId, userid);
if (store == null)
break;
success = true;
_HttpContext.SetStoreData(store);
}
break; break;
case Policies.CanModifyServerSettings.Key: case Policies.CanModifyServerSettings.Key:
if (!context.HasScopes(BTCPayScopes.ServerManagement)) if (!context.HasScopes(BTCPayScopes.ServerManagement))

View file

@ -15,16 +15,6 @@ namespace BTCPayServer.Security
options.AddPolicy(CanCreateInvoice.Key); options.AddPolicy(CanCreateInvoice.Key);
options.AddPolicy(CanGetRates.Key); options.AddPolicy(CanGetRates.Key);
options.AddPolicy(CanModifyServerSettings.Key); options.AddPolicy(CanModifyServerSettings.Key);
options.AddPolicy(RestAPIPolicies.CanCreateInvoices);
options.AddPolicy(RestAPIPolicies.CanManageApps);
options.AddPolicy(RestAPIPolicies.CanManageInvoices);
options.AddPolicy(RestAPIPolicies.CanManageStores);
options.AddPolicy(RestAPIPolicies.CanManageWallet);
options.AddPolicy(RestAPIPolicies.CanViewApps);
options.AddPolicy(RestAPIPolicies.CanViewInvoices);
options.AddPolicy(RestAPIPolicies.CanViewProfile);
options.AddPolicy(RestAPIPolicies.CanViewStores);
return options; return options;
} }

View file

@ -1,20 +1,13 @@
@using BTCPayServer.Authentication @using BTCPayServer.Security.OpenId
@using OpenIddict.Abstractions @using OpenIddict.Abstractions
@model BTCPayServer.Models.Authorization.AuthorizeViewModel @model BTCPayServer.Models.Authorization.AuthorizeViewModel
@{ @{
var scopeMappings = new Dictionary<string, (string Title, string Description)>() var scopeMappings = new Dictionary<string, (string Title, string Description)>()
{ {
{RestAPIPolicies.BTCPayScopes.AppManagement, ("Manage your apps", "The app will be able to create, modify and delete all your apps.")}, {BTCPayScopes.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")},
{RestAPIPolicies.BTCPayScopes.ViewApps, ("View your apps", "The app will be able to list and view all your apps.")}, {BTCPayScopes.ServerManagement, ("Manage your server", "The app will have total control on your server")},
{RestAPIPolicies.BTCPayScopes.CreateInvoices, ("Create invoices", "The app will be able to create new invoices.")}, };
{RestAPIPolicies.BTCPayScopes.InvoiceManagement, ("Manage invoices", "The app will be able to create new invoices and mark existing invoices as complete or invalid.")},
{RestAPIPolicies.BTCPayScopes.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")},
{RestAPIPolicies.BTCPayScopes.ViewStores, ("View your stores", "The app will be able to list and view all your stores.")},
{RestAPIPolicies.BTCPayScopes.WalletManagement, ("Manage your wallet", "The app will be able to manage your wallet associate to stores. This includes configuring it, transaction creation and signing.")},
{RestAPIPolicies.BTCPayScopes.ServerManagement, ("Manage your server", "The app will have total control on your server")},
{RestAPIPolicies.BTCPayScopes.ViewInvoices, ("View your invoices", "The app will be able to list and view all your apps.")},
};
} }
<form method="post"> <form method="post">
<input type="hidden" name="request_id" value="@Model.RequestId"/> <input type="hidden" name="request_id" value="@Model.RequestId"/>