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 04:55:21 +01:00
using BTCPayServer.Security.GreenField ;
2020-02-24 14:36:15 +01:00
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
2020-03-10 13:30:46 +01: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 11:11:15 +01:00
UserId = new [ ] { _userManager . GetUserId ( User ) }
2020-02-24 14:36:15 +01:00
} )
} ) ;
}
2020-03-19 11:11:15 +01: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 13:28:00 +01: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 13:28:00 +01: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" ) ;
}
2020-03-23 14:17:17 +01:00
2020-02-24 14:36:15 +01:00
permissions ? ? = Array . Empty < string > ( ) ;
2020-03-23 14:17:17 +01:00
var parsedPermissions = Permission . ToPermissions ( permissions ) . GroupBy ( permission = > permission . Policy ) ;
var vm = await SetViewModelValues ( new AuthorizeApiKeysViewModel ( )
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 ,
2020-03-23 14:17:17 +01:00
Permissions = string . Join ( ';' , parsedPermissions . SelectMany ( grouping = > grouping . Select ( permission = > permission . ToString ( ) ) ) )
2020-02-24 14:36:15 +01:00
} ) ;
2020-03-23 14:17:17 +01:00
AdjustVMForAuthorization ( vm ) ;
2020-02-24 14:36:15 +01:00
return View ( vm ) ;
}
2020-03-23 14:17:17 +01:00
private void AdjustVMForAuthorization ( AuthorizeApiKeysViewModel vm )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
var parsedPermissions = Permission . ToPermissions ( vm . Permissions . Split ( ';' ) ) . GroupBy ( permission = > permission . Policy ) ;
2020-02-24 14:36:15 +01:00
2020-03-23 14:17:17 +01:00
for ( var index = vm . PermissionValues . Count - 1 ; index > = 0 ; index - - )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
var permissionValue = vm . PermissionValues [ index ] ;
var wanted = parsedPermissions ? . SingleOrDefault ( permission = >
permission . Key . Equals ( permissionValue . Permission ,
StringComparison . InvariantCultureIgnoreCase ) ) ;
if ( vm . Strict & & ! ( wanted ? . Any ( ) ? ? false ) )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
vm . PermissionValues . RemoveAt ( index ) ;
continue ;
}
else if ( wanted ? . Any ( ) ? ? false )
{
if ( vm . SelectiveStores & & Policies . IsStorePolicy ( permissionValue . Permission ) & &
wanted . Any ( permission = > ! string . IsNullOrEmpty ( permission . StoreId ) ) )
{
permissionValue . StoreMode = AddApiKeyViewModel . ApiKeyStoreMode . Specific ;
permissionValue . SpecificStores = wanted . Select ( permission = > permission . StoreId ) . ToList ( ) ;
}
else
2020-03-19 11:11:15 +01:00
{
2020-03-23 14:17:17 +01:00
permissionValue . StoreMode = AddApiKeyViewModel . ApiKeyStoreMode . AllStores ;
permissionValue . SpecificStores = new List < string > ( ) ;
permissionValue . Value = true ;
2020-03-19 11:11:15 +01:00
}
2020-02-24 14:36:15 +01:00
}
}
2020-03-23 14:17:17 +01:00
}
2020-02-24 14:36:15 +01:00
2020-03-23 14:17:17 +01:00
[HttpPost("~/api-keys/authorize")]
public async Task < IActionResult > AuthorizeAPIKey ( [ FromForm ] AuthorizeApiKeysViewModel viewModel )
{
await SetViewModelValues ( viewModel ) ;
AdjustVMForAuthorization ( viewModel ) ;
var ar = HandleCommands ( viewModel ) ;
if ( ar ! = null )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
return ar ;
}
for ( int i = 0 ; i < viewModel . PermissionValues . Count ; i + + )
{
if ( viewModel . PermissionValues [ i ] . Forbidden & & viewModel . Strict )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
viewModel . PermissionValues [ i ] . Value = false ;
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-23 14:17:17 +01:00
if ( viewModel . PermissionValues [ i ] . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific & &
! viewModel . SelectiveStores )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
viewModel . PermissionValues [ i ] . StoreMode = AddApiKeyViewModel . ApiKeyStoreMode . AllStores ;
ModelState . AddModelError ( $"{viewModel.PermissionValues}[{i}].Value" ,
$"The permission '{viewModel.PermissionValues[i].Title}' cannot be store specific for this application." ) ;
2020-02-24 14:36:15 +01:00
}
}
2020-03-23 14:17:17 +01:00
2020-02-24 14:36:15 +01:00
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
2020-03-23 14:17:17 +01:00
2020-02-24 14:36:15 +01:00
switch ( viewModel . Command . ToLowerInvariant ( ) )
{
case "no" :
return RedirectToAction ( "APIKeys" ) ;
case "yes" :
var key = await CreateKey ( viewModel ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Success ,
2020-04-06 12:22:50 +02:00
Html = $"API key generated! <code class='alert-link'>{key.Id}</code>"
2020-02-24 14:36:15 +01:00
} ) ;
2020-03-19 11:11:15 +01: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 ,
2020-04-06 12:22:50 +02:00
Html = $"API key generated! <code class='alert-link'>{key.Id}</code>"
2020-02-24 14:36:15 +01:00
} ) ;
return RedirectToAction ( "APIKeys" ) ;
}
private IActionResult HandleCommands ( AddApiKeyViewModel viewModel )
{
2020-03-23 14:17:17 +01:00
if ( string . IsNullOrEmpty ( viewModel . Command ) )
{
return null ;
}
var parts = viewModel . Command . Split ( ':' , StringSplitOptions . RemoveEmptyEntries ) ;
var permission = parts [ 0 ] ;
if ( ! Policies . IsStorePolicy ( permission ) )
{
return null ;
}
var permissionValueItem = viewModel . PermissionValues . Single ( item = > item . Permission = = permission ) ;
var command = parts [ 1 ] ;
var storeIndex = parts . Length = = 3 ? parts [ 2 ] : null ;
ModelState . Clear ( ) ;
switch ( command )
2020-02-24 14:36:15 +01:00
{
case "change-store-mode" :
2020-03-23 14:17:17 +01:00
permissionValueItem . StoreMode = permissionValueItem . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific
2020-02-24 14:36:15 +01:00
? AddApiKeyViewModel . ApiKeyStoreMode . AllStores
: AddApiKeyViewModel . ApiKeyStoreMode . Specific ;
2020-03-23 14:17:17 +01:00
if ( permissionValueItem . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific & &
! permissionValueItem . SpecificStores . Any ( ) & & viewModel . Stores . Any ( ) )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
permissionValueItem . SpecificStores . Add ( null ) ;
2020-02-24 14:36:15 +01:00
}
return View ( viewModel ) ;
case "add-store" :
2020-03-23 14:17:17 +01:00
permissionValueItem . SpecificStores . Add ( null ) ;
2020-02-24 14:36:15 +01:00
return View ( viewModel ) ;
2020-03-23 14:17:17 +01:00
case "remove-store" :
{
if ( storeIndex ! = null )
permissionValueItem . SpecificStores . RemoveAt ( int . Parse ( storeIndex ,
CultureInfo . InvariantCulture ) ) ;
return View ( viewModel ) ;
}
2020-02-24 14:36:15 +01:00
}
2020-03-23 14:17:17 +01:00
2020-02-24 14:36:15 +01:00
return null ;
}
private async Task < APIKeyData > CreateKey ( AddApiKeyViewModel viewModel )
{
var key = new APIKeyData ( )
{
2020-03-10 13:30:46 +01: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-04-02 08:59:20 +02:00
key . SetBlob ( new APIKeyBlob ( )
{
Permissions = 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 11:11:15 +01:00
private IEnumerable < Permission > GetPermissionsFromViewModel ( AddApiKeyViewModel viewModel )
2020-02-24 14:36:15 +01:00
{
2020-03-19 11:11:15 +01:00
List < Permission > permissions = new List < Permission > ( ) ;
2020-03-23 14:17:17 +01:00
foreach ( var p in viewModel . PermissionValues . Where ( tuple = > ! tuple . Forbidden ) )
2020-02-24 14:36:15 +01:00
{
2020-03-23 14:17:17 +01:00
if ( Policies . IsStorePolicy ( p . Permission ) )
{
if ( p . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . AllStores & & p . Value )
{
permissions . Add ( Permission . Create ( p . Permission ) ) ;
}
else if ( p . StoreMode = = AddApiKeyViewModel . ApiKeyStoreMode . Specific )
{
permissions . AddRange ( p . SpecificStores . Select ( s = > Permission . Create ( p . Permission , s ) ) ) ;
}
}
else if ( p . Value & & Permission . TryCreatePermission ( p . Permission , null , out var pp ) )
2020-03-19 11:11:15 +01:00
permissions . Add ( pp ) ;
2020-02-24 14:36:15 +01:00
}
2020-03-23 14:17:17 +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-23 14:17:17 +01:00
var isAdmin = ( await _authorizationService . AuthorizeAsync ( User , Policies . CanModifyServerSettings ) )
. Succeeded ;
viewModel . PermissionValues ? ? = Policies . AllPolicies
. Select ( s = > new AddApiKeyViewModel . PermissionValueItem ( )
{
Permission = s ,
Value = false ,
Forbidden = Policies . IsServerPolicy ( s ) & & ! isAdmin
} ) . ToList ( ) ;
2020-03-19 11:11:15 +01:00
if ( ! isAdmin )
{
2020-03-23 14:17:17 +01:00
foreach ( var p in viewModel . PermissionValues . Where ( item = > Policies . IsServerPolicy ( item . Permission ) ) )
2020-03-19 11:11:15 +01:00
{
2020-03-23 14:17:17 +01:00
p . Forbidden = true ;
2020-03-19 11:11:15 +01:00
}
}
2020-03-23 14:17:17 +01:00
2020-02-24 14:36:15 +01:00
return viewModel ;
}
public class AddApiKeyViewModel
{
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 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 11:11:15 +01:00
public static readonly Dictionary < string , ( string Title , string Description ) > PermissionDescriptions = new Dictionary < string , ( string Title , string Description ) > ( )
{
2020-03-20 05:58:07 +01: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." ) } ,
2020-05-19 19:59:23 +02:00
{ BTCPayServer . Client . Policies . CanModifyStoreSettings , ( "Modify your stores" , "The app will be able to view, modify, delete and create new invoices on all your stores." ) } ,
2020-03-20 05:58:07 +01: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-23 14:17:17 +01:00
{ BTCPayServer . Client . Policies . CanViewStoreSettings , ( "View your stores" , "The app will be able to view stores settings." ) } ,
{ $"{BTCPayServer.Client.Policies.CanViewStoreSettings}:" , ( "View your stores" , "The app will be able to view the selected stores' settings." ) } ,
2020-03-20 06:33:11 +01:00
{ BTCPayServer . Client . Policies . CanModifyServerSettings , ( "Manage your server" , "The app will have total control on the server settings of your server" ) } ,
2020-03-20 05:58:07 +01: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." ) } ,
2020-03-23 14:17:17 +01:00
{ BTCPayServer . Client . Policies . CanCreateInvoice , ( "Create an invoice" , "The app will be able to create new invoices." ) } ,
{ $"{BTCPayServer.Client.Policies.CanCreateInvoice}:" , ( "Create an invoice" , "The app will be able to create new invoices on the selected stores." ) } ,
2020-05-19 19:59:23 +02:00
{ BTCPayServer . Client . Policies . CanModifyPaymentRequests , ( "Modify your payment requests" , "The app will be able to view, modify, delete and create new payment requests on all your stores." ) } ,
{ $"{BTCPayServer.Client.Policies.CanModifyPaymentRequests}:" , ( "Manage selected stores' payment requests" , "The app will be able to view, modify, delete and create new payment requests on the selected stores." ) } ,
{ BTCPayServer . Client . Policies . CanViewPaymentRequests , ( "View your payment requests" , "The app will be able to view payment requests." ) } ,
{ $"{BTCPayServer.Client.Policies.CanViewPaymentRequests}:" , ( "View your payment requests" , "The app will be able to view the selected stores' payment requests." ) } ,
2020-05-29 02:00:13 +02:00
{ BTCPayServer . Client . Policies . CanUseInternalLightningNode , ( "Use the internal lightning node" , "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices." ) } ,
{ BTCPayServer . Client . Policies . CanCreateLightningInvoiceInternalNode , ( "Create invoices with internal lightning node" , "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices." ) } ,
{ BTCPayServer . Client . Policies . CanUseLightningNodeInStore , ( "Use the lightning nodes associated with your stores" , "The app will be able to use the lightning nodes connected to all your stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices." ) } ,
{ BTCPayServer . Client . Policies . CanCreateLightningInvoiceInStore , ( "Create invoices the lightning nodes associated with your stores" , "The app will be able to use the lightning nodes connected to all your stores to create BOLT11 invoices." ) } ,
{ $"{BTCPayServer.Client.Policies.CanUseLightningNodeInStore}:" , ( "Use the lightning nodes associated with your stores" , "The app will be able to use the lightning nodes connected to the selected stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices." ) } ,
{ $"{BTCPayServer.Client.Policies.CanCreateLightningInvoiceInStore}:" , ( "Create invoices the lightning nodes associated with your stores" , "The app will be able to use the lightning nodes connected to the selected stores to create BOLT11 invoices." ) } ,
2020-03-19 11:11:15 +01:00
} ;
public string Title
{
get
{
2020-03-23 14:17:17 +01:00
return PermissionDescriptions [ $"{Permission}{(StoreMode == ApiKeyStoreMode.Specific? " : ": " ")}" ] . Title ;
2020-03-19 11:11:15 +01:00
}
}
public string Description
{
get
{
2020-03-23 14:17:17 +01:00
return PermissionDescriptions [ $"{Permission}{(StoreMode == ApiKeyStoreMode.Specific? " : ": " ")}" ] . Description ;
2020-03-19 11:11:15 +01:00
}
}
2020-03-12 14:59:24 +01:00
public string Permission { get ; set ; }
public bool Value { get ; set ; }
2020-03-19 11:11:15 +01:00
public bool Forbidden { get ; set ; }
2020-03-23 14:17:17 +01:00
public ApiKeyStoreMode StoreMode { get ; set ; } = ApiKeyStoreMode . AllStores ;
public List < string > SpecificStores { get ; set ; } = new List < string > ( ) ;
2020-03-12 14:59:24 +01:00
}
2020-02-24 14:36:15 +01:00
}
public class AuthorizeApiKeysViewModel : AddApiKeyViewModel
{
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 ; }
}
}
}