2020-02-24 14:36:15 +01:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading.Tasks ;
2020-03-02 16:50:28 +01:00
using BTCPayServer.Client ;
2020-02-24 14:36:15 +01:00
using BTCPayServer.Data ;
using BTCPayServer.Models ;
using BTCPayServer.Security ;
2020-03-27 12:55:21 +09:00
using BTCPayServer.Security.GreenField ;
2020-02-24 14:36:15 +01:00
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
2020-03-10 21:30:46 +09:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
2020-03-12 14:59:24 +01:00
using YamlDotNet.Core.Tokens ;
2020-02-24 14:36:15 +01:00
namespace BTCPayServer.Controllers
{
public partial class ManageController
{
[HttpGet]
public async Task < IActionResult > APIKeys ( )
{
return View ( new ApiKeysViewModel ( )
{
ApiKeyDatas = await _apiKeyRepository . GetKeys ( new APIKeyRepository . APIKeyQuery ( )
{
2020-03-19 19:11:15 +09:00
UserId = new [ ] { _userManager . GetUserId ( User ) }
2020-02-24 14:36:15 +01:00
} )
} ) ;
}
2020-03-19 19:11:15 +09:00
2020-02-26 10:26:38 +01:00
[HttpGet("api-keys/{id}/delete")]
2020-02-24 14:36:15 +01:00
public async Task < IActionResult > RemoveAPIKey ( string id )
{
2020-02-26 10:26:38 +01:00
var key = await _apiKeyRepository . GetKey ( id ) ;
if ( key = = null | | key . UserId ! = _userManager . GetUserId ( User ) )
{
return NotFound ( ) ;
}
return View ( "Confirm" , new ConfirmModel ( )
{
2020-03-10 21:28:00 +09:00
Title = "Delete API Key " + ( string . IsNullOrEmpty ( key . Label ) ? string . Empty : key . Label ) + "(" + key . Id + ")" ,
2020-02-26 10:26:38 +01:00
Description = "Any application using this api key will immediately lose access" ,
Action = "Delete" ,
2020-03-10 21:28:00 +09:00
ActionUrl = this . Url . ActionLink ( nameof ( RemoveAPIKeyPost ) , values : new { id = id } )
2020-02-26 10:26:38 +01:00
} ) ;
}
[HttpPost("api-keys/{id}/delete")]
public async Task < IActionResult > RemoveAPIKeyPost ( string id )
{
var key = await _apiKeyRepository . GetKey ( id ) ;
if ( key = = null | | key . UserId ! = _userManager . GetUserId ( User ) )
{
return NotFound ( ) ;
}
2020-02-24 14:36:15 +01:00
await _apiKeyRepository . Remove ( id , _userManager . GetUserId ( User ) ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Success ,
Message = "API Key removed"
} ) ;
return RedirectToAction ( "APIKeys" ) ;
}
[HttpGet]
public async Task < IActionResult > AddApiKey ( )
{
if ( ! _btcPayServerEnvironment . IsSecure )
{
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Error ,
Message = "Cannot generate api keys while not on https or tor"
} ) ;
return RedirectToAction ( "APIKeys" ) ;
}
return View ( "AddApiKey" , await SetViewModelValues ( new AddApiKeyViewModel ( ) ) ) ;
}
[HttpGet("~/api-keys/authorize")]
2020-02-18 10:50:01 +01:00
public async Task < IActionResult > AuthorizeAPIKey ( string [ ] permissions , string applicationName = null ,
2020-02-24 14:36:15 +01:00
bool strict = true , bool selectiveStores = false )
{
if ( ! _btcPayServerEnvironment . IsSecure )
{
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Error ,
Message = "Cannot generate api keys while not on https or tor"
} ) ;
return RedirectToAction ( "APIKeys" ) ;
}
permissions ? ? = Array . Empty < string > ( ) ;
2020-03-19 19:11:15 +09:00
var vm = await SetViewModelValues ( new AuthorizeApiKeysViewModel ( Permission . ToPermissions ( permissions ) )
2020-02-24 14:36:15 +01:00
{
2020-02-25 14:43:53 +01:00
Label = applicationName ,
2020-02-24 14:36:15 +01:00
ApplicationName = applicationName ,
SelectiveStores = selectiveStores ,
Strict = strict ,
} ) ;
return View ( vm ) ;
}
[HttpPost("~/api-keys/authorize")]
public async Task < IActionResult > AuthorizeAPIKey ( [ FromForm ] AuthorizeApiKeysViewModel viewModel )
{
await SetViewModelValues ( viewModel ) ;
var ar = HandleCommands ( viewModel ) ;
if ( ar ! = null )
{
return ar ;
}
2020-03-19 19:11:15 +09:00
if ( viewModel . Strict )
2020-02-24 14:36:15 +01:00
{
2020-03-19 19:11:15 +09:00
for ( int i = 0 ; i < viewModel . PermissionValues . Count ; i + + )
2020-02-24 14:36:15 +01:00
{
2020-03-19 19:11:15 +09:00
if ( viewModel . PermissionValues [ i ] . Forbidden )
{
ModelState . AddModelError ( $"{viewModel.PermissionValues}[{i}].Value" ,
$"The permission '{viewModel.PermissionValues[i].Title}' is required for this application." ) ;
}
2020-02-24 14:36:15 +01:00
}
}
2020-03-19 19:11:15 +09:00
var permissions = Permission . ToPermissions ( viewModel . Permissions ) . ToHashSet ( ) ;
2020-03-20 13:58:07 +09:00
if ( permissions . Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) )
2020-02-24 14:36:15 +01:00
{
if ( ! viewModel . SelectiveStores & &
viewModel . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific )
{
viewModel . StoreMode = AddApiKeyViewModel . ApiKeyStoreMode . AllStores ;
ModelState . AddModelError ( nameof ( viewModel . StoreManagementPermission ) ,
"This application does not allow selective store permissions." ) ;
}
2020-03-19 19:11:15 +09:00
if ( ! viewModel . StoreManagementPermission . Value & & ! viewModel . SpecificStores . Any ( ) & & viewModel . Strict )
2020-02-24 14:36:15 +01:00
{
ModelState . AddModelError ( nameof ( viewModel . StoreManagementPermission ) ,
2020-03-19 19:11:15 +09:00
$"This permission '{viewModel.StoreManagementPermission.Title}' is required for this application." ) ;
2020-02-24 14:36:15 +01:00
}
}
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
switch ( viewModel . Command . ToLowerInvariant ( ) )
{
case "no" :
return RedirectToAction ( "APIKeys" ) ;
case "yes" :
var key = await CreateKey ( viewModel ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Success ,
Html = $"API key generated! <code>{key.Id}</code>"
} ) ;
2020-03-19 19:11:15 +09:00
return RedirectToAction ( "APIKeys" , new { key = key . Id } ) ;
default :
return View ( viewModel ) ;
2020-02-24 14:36:15 +01:00
}
}
[HttpPost]
public async Task < IActionResult > AddApiKey ( AddApiKeyViewModel viewModel )
{
await SetViewModelValues ( viewModel ) ;
var ar = HandleCommands ( viewModel ) ;
if ( ar ! = null )
{
return ar ;
}
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
var key = await CreateKey ( viewModel ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Success ,
Html = $"API key generated! <code>{key.Id}</code>"
} ) ;
return RedirectToAction ( "APIKeys" ) ;
}
private IActionResult HandleCommands ( AddApiKeyViewModel viewModel )
{
switch ( viewModel . Command )
{
case "change-store-mode" :
viewModel . StoreMode = viewModel . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific
? AddApiKeyViewModel . ApiKeyStoreMode . AllStores
: AddApiKeyViewModel . ApiKeyStoreMode . Specific ;
if ( viewModel . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific & &
! viewModel . SpecificStores . Any ( ) & & viewModel . Stores . Any ( ) )
{
viewModel . SpecificStores . Add ( null ) ;
}
return View ( viewModel ) ;
case "add-store" :
viewModel . SpecificStores . Add ( null ) ;
return View ( viewModel ) ;
case string x when x . StartsWith ( "remove-store" , StringComparison . InvariantCultureIgnoreCase ) :
2020-03-19 19:11:15 +09:00
{
ModelState . Clear ( ) ;
var index = int . Parse (
viewModel . Command . Substring (
viewModel . Command . IndexOf ( ":" , StringComparison . InvariantCultureIgnoreCase ) + 1 ) ,
CultureInfo . InvariantCulture ) ;
viewModel . SpecificStores . RemoveAt ( index ) ;
return View ( viewModel ) ;
}
2020-02-24 14:36:15 +01:00
}
return null ;
}
private async Task < APIKeyData > CreateKey ( AddApiKeyViewModel viewModel )
{
var key = new APIKeyData ( )
{
2020-03-10 21:30:46 +09:00
Id = Encoders . Hex . EncodeData ( RandomUtils . GetBytes ( 20 ) ) ,
2020-02-25 14:43:53 +01:00
Type = APIKeyType . Permanent ,
UserId = _userManager . GetUserId ( User ) ,
Label = viewModel . Label
2020-02-24 14:36:15 +01:00
} ;
2020-03-19 19:11:15 +09:00
key . Permissions = string . Join ( ";" , GetPermissionsFromViewModel ( viewModel ) . Select ( p = > p . ToString ( ) ) . Distinct ( ) . ToArray ( ) ) ;
2020-02-24 14:36:15 +01:00
await _apiKeyRepository . CreateKey ( key ) ;
return key ;
}
2020-03-19 19:11:15 +09:00
private IEnumerable < Permission > GetPermissionsFromViewModel ( AddApiKeyViewModel viewModel )
2020-02-24 14:36:15 +01:00
{
2020-03-19 19:11:15 +09:00
List < Permission > permissions = new List < Permission > ( ) ;
foreach ( var p in viewModel . PermissionValues . Where ( tuple = > tuple . Value & & ! tuple . Forbidden ) )
2020-02-24 14:36:15 +01:00
{
2020-03-19 19:11:15 +09:00
if ( Permission . TryCreatePermission ( p . Permission , null , out var pp ) )
permissions . Add ( pp ) ;
2020-02-24 14:36:15 +01:00
}
2020-03-19 19:11:15 +09:00
if ( viewModel . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . AllStores & & viewModel . StoreManagementPermission . Value )
2020-02-24 14:36:15 +01:00
{
2020-03-20 13:58:07 +09:00
permissions . Add ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ;
2020-02-24 14:36:15 +01:00
}
2020-03-19 19:11:15 +09:00
else if ( viewModel . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific )
2020-02-24 14:36:15 +01:00
{
2020-03-20 13:58:07 +09:00
permissions . AddRange ( viewModel . SpecificStores . Select ( s = > Permission . Create ( Policies . CanModifyStoreSettings , s ) ) ) ;
2020-02-24 14:36:15 +01:00
}
2020-03-12 14:59:24 +01:00
return permissions . Distinct ( ) ;
2020-02-24 14:36:15 +01:00
}
private async Task < T > SetViewModelValues < T > ( T viewModel ) where T : AddApiKeyViewModel
{
viewModel . Stores = await _StoreRepository . GetStoresByUserId ( _userManager . GetUserId ( User ) ) ;
2020-03-20 13:58:07 +09:00
var isAdmin = ( await _authorizationService . AuthorizeAsync ( User , Policies . CanModifyServerSettings ) ) . Succeeded ;
viewModel . PermissionValues ? ? = Policies . AllPolicies . Where ( p = > p ! = Policies . CanModifyStoreSettings )
2020-03-19 19:11:15 +09:00
. Select ( s = > new AddApiKeyViewModel . PermissionValueItem ( ) { Permission = s , Value = false } ) . ToList ( ) ;
if ( ! isAdmin )
{
foreach ( var p in viewModel . PermissionValues )
{
2020-03-20 13:58:07 +09:00
if ( p . Permission = = Policies . CanCreateUser | |
p . Permission = = Policies . CanModifyServerSettings )
2020-03-19 19:11:15 +09:00
{
p . Forbidden = true ;
}
}
}
2020-02-24 14:36:15 +01:00
return viewModel ;
}
public class AddApiKeyViewModel
{
2020-03-19 19:11:15 +09:00
public AddApiKeyViewModel ( )
{
StoreManagementPermission = new PermissionValueItem ( )
{
2020-03-20 13:58:07 +09:00
Permission = Policies . CanModifyStoreSettings ,
2020-03-19 19:11:15 +09:00
Value = false
} ;
StoreManagementSelectivePermission = new PermissionValueItem ( )
{
2020-03-20 13:58:07 +09:00
Permission = $"{Policies.CanModifyStoreSettings}:" ,
2020-03-19 19:11:15 +09:00
Value = true
} ;
}
public AddApiKeyViewModel ( IEnumerable < Permission > permissions ) : this ( )
{
2020-03-20 13:58:07 +09:00
StoreManagementPermission . Value = permissions . Any ( p = > p . Policy = = Policies . CanModifyStoreSettings & & p . StoreId = = null ) ;
PermissionValues = permissions . Where ( p = > p . Policy ! = Policies . CanModifyStoreSettings )
2020-03-19 19:11:15 +09:00
. Select ( p = > new PermissionValueItem ( ) { Permission = p . ToString ( ) , Value = true } )
. ToList ( ) ;
}
public IEnumerable < Permission > GetPermissions ( )
{
if ( ! ( PermissionValues is null ) )
{
foreach ( var p in PermissionValues . Where ( o = > o . Value ) )
{
if ( Permission . TryCreatePermission ( p . Permission , null , out var pp ) )
yield return pp ;
}
}
if ( this . StoreMode = = ApiKeyStoreMode . AllStores )
{
if ( StoreManagementPermission . Value )
2020-03-20 13:58:07 +09:00
yield return Permission . Create ( Policies . CanModifyStoreSettings ) ;
2020-03-19 19:11:15 +09:00
}
else if ( this . StoreMode = = ApiKeyStoreMode . Specific & & SpecificStores is List < string > )
{
foreach ( var p in SpecificStores )
{
2020-03-20 13:58:07 +09:00
if ( Permission . TryCreatePermission ( Policies . CanModifyStoreSettings , p , out var pp ) )
2020-03-19 19:11:15 +09:00
yield return pp ;
}
}
}
2020-02-25 14:43:53 +01:00
public string Label { get ; set ; }
2020-02-24 14:36:15 +01:00
public StoreData [ ] Stores { get ; set ; }
public ApiKeyStoreMode StoreMode { get ; set ; }
public List < string > SpecificStores { get ; set ; } = new List < string > ( ) ;
2020-03-19 19:11:15 +09:00
public PermissionValueItem StoreManagementPermission { get ; set ; }
public PermissionValueItem StoreManagementSelectivePermission { get ; set ; }
2020-02-24 14:36:15 +01:00
public string Command { get ; set ; }
2020-03-12 14:59:24 +01:00
public List < PermissionValueItem > PermissionValues { get ; set ; }
2020-02-24 14:36:15 +01:00
public enum ApiKeyStoreMode
{
AllStores ,
Specific
}
2020-03-12 14:59:24 +01:00
public class PermissionValueItem
{
2020-03-19 19:11:15 +09:00
public static readonly Dictionary < string , ( string Title , string Description ) > PermissionDescriptions = new Dictionary < string , ( string Title , string Description ) > ( )
{
2020-03-20 13:58:07 +09:00
{ BTCPayServer . Client . Policies . Unrestricted , ( "Unrestricted access" , "The app will have unrestricted access to your account." ) } ,
{ BTCPayServer . Client . Policies . CanCreateUser , ( "Create new users" , "The app will be able to create new users on this server." ) } ,
{ BTCPayServer . Client . Policies . CanModifyStoreSettings , ( "Modify your stores" , "The app will be able to create, view and modify, delete and create new invoices on the all your stores." ) } ,
2020-03-20 14:33:11 +09:00
{ BTCPayServer . Client . Policies . CanViewStoreSettings , ( "View your stores" , "The app will be able to view stores settings." ) } ,
2020-03-20 13:58:07 +09:00
{ $"{BTCPayServer.Client.Policies.CanModifyStoreSettings}:" , ( "Manage selected stores" , "The app will be able to view, modify, delete and create new invoices on the selected stores." ) } ,
2020-03-20 14:33:11 +09:00
{ BTCPayServer . Client . Policies . CanModifyServerSettings , ( "Manage your server" , "The app will have total control on the server settings of your server" ) } ,
2020-03-20 13:58:07 +09:00
{ BTCPayServer . Client . Policies . CanViewProfile , ( "View your profile" , "The app will be able to view your user profile." ) } ,
{ BTCPayServer . Client . Policies . CanModifyProfile , ( "Manage your profile" , "The app will be able to view and modify your user profile." ) } ,
{ BTCPayServer . Client . Policies . CanCreateInvoice , ( "Create an invoice" , "The app will be able to create new invoice." ) } ,
2020-03-19 19:11:15 +09:00
} ;
public string Title
{
get
{
return PermissionDescriptions [ Permission ] . Title ;
}
}
public string Description
{
get
{
return PermissionDescriptions [ Permission ] . Description ;
}
}
2020-03-12 14:59:24 +01:00
public string Permission { get ; set ; }
public bool Value { get ; set ; }
2020-03-19 19:11:15 +09:00
public bool Forbidden { get ; set ; }
2020-03-12 14:59:24 +01:00
}
2020-02-24 14:36:15 +01:00
}
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
{
2020-03-19 19:11:15 +09:00
public AuthorizeApiKeysViewModel ( )
{
}
public AuthorizeApiKeysViewModel ( IEnumerable < Permission > permissions ) : base ( permissions )
{
Permissions = string . Join ( ';' , permissions . Select ( p = > p . ToString ( ) ) . ToArray ( ) ) ;
}
2020-02-24 14:36:15 +01:00
public string ApplicationName { get ; set ; }
public bool Strict { get ; set ; }
public bool SelectiveStores { get ; set ; }
public string Permissions { get ; set ; }
}
public class ApiKeysViewModel
{
public List < APIKeyData > ApiKeyDatas { get ; set ; }
}
}
}