mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 09:29:10 +01:00
Remove OpenIddict (#1244)
This commit is contained in:
parent
d16a4334cb
commit
276a9a95f9
30 changed files with 182 additions and 1690 deletions
|
@ -7,7 +7,6 @@
|
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.20058.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
@ -261,28 +260,6 @@ namespace BTCPayServer.Data
|
|||
.HasOne(o => o.WalletData)
|
||||
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
||||
|
||||
if (Database.IsSqlite() && !_designTime)
|
||||
{
|
||||
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
|
||||
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
|
||||
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
|
||||
// use the DateTimeOffsetToBinaryConverter
|
||||
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||
foreach (var entityType in builder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
|
||||
foreach (var property in properties)
|
||||
{
|
||||
builder
|
||||
.Entity(entityType.Name)
|
||||
.Property(property.Name)
|
||||
.HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,7 @@ namespace BTCPayServer.Data
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<BTCPayOpenIdClient> OpenIdClients { get; set; }
|
||||
|
||||
|
||||
public List<StoredFile> StoredFiles
|
||||
{
|
||||
get;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class BTCPayOpenIdAuthorization : OpenIddictAuthorization<string, BTCPayOpenIdClient, BTCPayOpenIdToken> { }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class BTCPayOpenIdClient: OpenIddictApplication<string, BTCPayOpenIdAuthorization, BTCPayOpenIdToken>
|
||||
{
|
||||
public string ApplicationUserId { get; set; }
|
||||
public ApplicationUser ApplicationUser { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class BTCPayOpenIdToken : OpenIddictToken<string, BTCPayOpenIdClient, BTCPayOpenIdAuthorization> { }
|
||||
}
|
169
BTCPayServer.Data/Migrations/20200224134444_Remove_OpenIddict.cs
Normal file
169
BTCPayServer.Data/Migrations/20200224134444_Remove_OpenIddict.cs
Normal file
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class Remove_OpenIddict : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictAuthorizations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictApplications");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OpenIddictApplications",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false, maxLength: maxLength),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxLength),
|
||||
ClientId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
ClientSecret = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ConcurrencyToken = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
ConsentType = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Permissions = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PostLogoutRedirectUris = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Properties = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RedirectUris = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Requirements = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OpenIddictApplications", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OpenIddictApplications_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OpenIddictScopes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false, maxLength: maxLength),
|
||||
ConcurrencyToken = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Properties = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Resources = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OpenIddictScopes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OpenIddictAuthorizations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false, maxLength: maxLength),
|
||||
ApplicationId = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxLength),
|
||||
ConcurrencyToken = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
Properties = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Scopes = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Status = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false),
|
||||
Subject = table.Column<string>(type: "TEXT", maxLength: 450, nullable: true),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId",
|
||||
column: x => x.ApplicationId,
|
||||
principalTable: "OpenIddictApplications",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OpenIddictTokens",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false, maxLength: maxLength),
|
||||
ApplicationId = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxLength),
|
||||
AuthorizationId = table.Column<string>(type: "TEXT", nullable: true, maxLength: maxLength),
|
||||
ConcurrencyToken = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
CreationDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||
ExpirationDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||
Payload = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Properties = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ReferenceId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
Status = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false),
|
||||
Subject = table.Column<string>(type: "TEXT", maxLength: 450, nullable: true),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OpenIddictTokens", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId",
|
||||
column: x => x.ApplicationId,
|
||||
principalTable: "OpenIddictApplications",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId",
|
||||
column: x => x.AuthorizationId,
|
||||
principalTable: "OpenIddictAuthorizations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictApplications_ApplicationUserId",
|
||||
table: "OpenIddictApplications",
|
||||
column: "ApplicationUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictApplications_ClientId",
|
||||
table: "OpenIddictApplications",
|
||||
column: "ClientId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type",
|
||||
table: "OpenIddictAuthorizations",
|
||||
columns: new[] { "ApplicationId", "Status", "Subject", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictScopes_Name",
|
||||
table: "OpenIddictScopes",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictTokens_AuthorizationId",
|
||||
table: "OpenIddictTokens",
|
||||
column: "AuthorizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictTokens_ReferenceId",
|
||||
table: "OpenIddictTokens",
|
||||
column: "ReferenceId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type",
|
||||
table: "OpenIddictTokens",
|
||||
columns: new[] { "ApplicationId", "Status", "Subject", "Type" });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -160,164 +160,6 @@ namespace BTCPayServer.Migrations
|
|||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Properties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictAuthorizations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("ClientSecret")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("ConsentType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PostLogoutRedirectUris")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Properties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RedirectUris")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Requirements")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictApplications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ApplicationId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AuthorizationId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<DateTimeOffset?>("CreationDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("ExpirationDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Payload")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Properties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ReferenceId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorizationId");
|
||||
|
||||
b.HasIndex("ReferenceId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId")
|
||||
|
@ -812,42 +654,6 @@ namespace BTCPayServer.Migrations
|
|||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictScope<string>", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(200);
|
||||
|
||||
b.Property<string>("Properties")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Resources")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
|
@ -877,31 +683,6 @@ namespace BTCPayServer.Migrations
|
|||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Authorizations")
|
||||
.HasForeignKey("ApplicationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("OpenIdClients")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("ApplicationId");
|
||||
|
||||
b.HasOne("BTCPayServer.Data.BTCPayOpenIdAuthorization", "Authorization")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("AuthorizationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
|
|
|
@ -1,428 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using BTCPayServer.Data;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenQA.Selenium;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class AuthenticationTests
|
||||
{
|
||||
public const string TestApiPath = "api/test/openid";
|
||||
public const int TestTimeout = TestUtils.TestTimeout;
|
||||
public AuthenticationTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task GetRedirectedToLoginPathOnChallenge()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var client = tester.PayTester.HttpClient;
|
||||
//Wallets endpoint is protected
|
||||
var response = await client.GetAsync("wallets");
|
||||
var urlPath = response.RequestMessage.RequestUri.ToString()
|
||||
.Replace(tester.PayTester.ServerUri.ToString(), "");
|
||||
//Cookie Challenge redirects you to login page
|
||||
Assert.StartsWith("Account/Login", urlPath, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
var queryString = response.RequestMessage.RequestUri.ParseQueryString();
|
||||
|
||||
Assert.NotNull(queryString["ReturnUrl"]);
|
||||
Assert.Equal("/wallets", queryString["ReturnUrl"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanGetOpenIdConfiguration()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
using (var response =
|
||||
await tester.PayTester.HttpClient.GetAsync("/.well-known/openid-configuration"))
|
||||
{
|
||||
using (var streamToReadFrom = new StreamReader(await response.Content.ReadAsStreamAsync()))
|
||||
{
|
||||
var json = await streamToReadFrom.ReadToEndAsync();
|
||||
Assert.NotNull(json);
|
||||
JObject.Parse(json); // Should do more tests but good enough
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseNonInteractiveFlows()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var token = await RegisterPasswordClientAndGetAccessToken(user, null, tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
token = await RegisterPasswordClientAndGetAccessToken(user, "secret", tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
token = await RegisterClientCredentialsFlowAndGetAccessToken(user, "secret", tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseImplicitFlow()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var tester = s.Server;
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Implicit},
|
||||
RedirectUris = {redirecturi},
|
||||
|
||||
});
|
||||
var implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid server_management store_management&nonce={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("#").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
//in Implicit mode, you renew your token by hitting the same endpoint but adding prompt=none. If you are still logged in on the site, you will receive a fresh token.
|
||||
var implicitAuthorizeUrlSilentModel = new Uri($"{implicitAuthorizeUrl.OriginalString}&prompt=none");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrlSilentModel);
|
||||
url = s.Driver.Url;
|
||||
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
|
||||
var stores = await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
|
||||
$"{TestApiPath}/me/stores",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.NotEmpty(stores);
|
||||
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
|
||||
$"{TestApiPath}/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.
|
||||
LogoutFlow(tester, id, s);
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.AssertElementNotFound(By.Id("consent-yes"));
|
||||
|
||||
// Let's asks without scopes
|
||||
LogoutFlow(tester, id, s);
|
||||
id = Guid.NewGuid().ToString();
|
||||
openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = { OpenIddictConstants.Permissions.GrantTypes.Implicit },
|
||||
RedirectUris = { redirecturi },
|
||||
});
|
||||
implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
results = s.Driver.Url.Split("#").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
|
||||
$"{TestApiPath}/me/stores",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(results["access_token"],
|
||||
$"{TestApiPath}/me/stores/{stores[0].Id}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void LogoutFlow(ServerTester tester, string clientId, SeleniumTester seleniumTester)
|
||||
{
|
||||
var logoutUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/logout?response_type=token&client_id={clientId}");
|
||||
seleniumTester.Driver.Navigate().GoToUrl(logoutUrl);
|
||||
seleniumTester.GoToHome();
|
||||
Assert.Throws<NoSuchElementException>(() => seleniumTester.Driver.FindElement(By.Id("Logout")));
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanUseCodeFlow()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var tester = s.Server;
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.MakeAdmin();
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
|
||||
var secret = "secret";
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions =
|
||||
{
|
||||
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
|
||||
OpenIddictConstants.Permissions.GrantTypes.RefreshToken
|
||||
},
|
||||
RedirectUris = {redirecturi}
|
||||
}, secret);
|
||||
var authorizeUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access server_management store_management&state={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(authorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("?").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.AuthorizationCode),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret),
|
||||
new KeyValuePair<string, string>("code", results["code"]),
|
||||
new KeyValuePair<string, string>("redirect_uri", redirecturi.AbsoluteUri)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
|
||||
await TestApiAgainstAccessToken(result.AccessToken, tester, user);
|
||||
|
||||
var refreshedAccessToken = await RefreshAnAccessToken(result.RefreshToken, httpClient, id, secret);
|
||||
|
||||
await TestApiAgainstAccessToken(refreshedAccessToken, tester, user);
|
||||
|
||||
LogoutFlow(tester, id, s);
|
||||
s.Driver.Navigate().GoToUrl(authorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
|
||||
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
|
||||
results = url.Split("?").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
Assert.True(results.ContainsKey("code"));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> RefreshAnAccessToken(string refreshToken, HttpClient client, string clientId,
|
||||
string clientSecret = null)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(client.BaseAddress, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.RefreshToken),
|
||||
new KeyValuePair<string, string>("client_id", clientId),
|
||||
new KeyValuePair<string, string>("client_secret", clientSecret),
|
||||
new KeyValuePair<string, string>("refresh_token", refreshToken)
|
||||
})
|
||||
};
|
||||
|
||||
var response = await client.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
private static async Task<string> RegisterClientCredentialsFlowAndGetAccessToken(TestAccount user,
|
||||
string secret,
|
||||
ServerTester tester)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.ClientCredentials}
|
||||
}, secret);
|
||||
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.ClientCredentials),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret),
|
||||
new KeyValuePair<string, string>("scope", "server_management store_management")
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
private static async Task<string> RegisterPasswordClientAndGetAccessToken(TestAccount user, string secret,
|
||||
ServerTester tester)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Password}
|
||||
}, secret);
|
||||
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type", OpenIddictConstants.GrantTypes.Password),
|
||||
new KeyValuePair<string, string>("username", user.RegisterDetails.Email),
|
||||
new KeyValuePair<string, string>("password", user.RegisterDetails.Password),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret),
|
||||
new KeyValuePair<string, string>("scope", "server_management store_management")
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount)
|
||||
{
|
||||
var resultUser =
|
||||
await TestApiAgainstAccessToken<string>(accessToken, $"{TestApiPath}/me/id",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.Equal(testAccount.UserId, resultUser);
|
||||
|
||||
var secondUser = tester.NewAccount();
|
||||
secondUser.GrantAccess();
|
||||
|
||||
var resultStores =
|
||||
await TestApiAgainstAccessToken<StoreData[]>(accessToken, $"{TestApiPath}/me/stores",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.Contains(resultStores,
|
||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.DoesNotContain(resultStores,
|
||||
data => data.Id.Equals(secondUser.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/stores/{testAccount.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"{TestApiPath}/me/is-admin",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken, $"{TestApiPath}/me/stores/{secondUser.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<T> TestApiAgainstAccessToken<T>(string accessToken, string url, HttpClient client)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
|
||||
new Uri(client.BaseAddress, url));
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||
var result = await client.SendAsync(httpRequest);
|
||||
result.EnsureSuccessStatusCode();
|
||||
|
||||
var rawJson = await result.Content.ReadAsStringAsync();
|
||||
if (typeof(T).IsPrimitive || typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)Convert.ChangeType(rawJson, typeof(T));
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(rawJson);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,7 +32,6 @@ using System.Security.Claims;
|
|||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using OpenIddict.Abstractions;
|
||||
using Xunit;
|
||||
using BTCPayServer.Services;
|
||||
using System.Net.Http;
|
||||
|
@ -298,7 +297,7 @@ namespace BTCPayServer.Tests
|
|||
if (userId != null)
|
||||
{
|
||||
List<Claim> claims = new List<Claim>();
|
||||
claims.Add(new Claim(OpenIddictConstants.Claims.Subject, userId));
|
||||
claims.Add(new Claim(ClaimTypes.NameIdentifier, userId));
|
||||
if (isAdmin)
|
||||
claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin));
|
||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie));
|
||||
|
|
|
@ -18,8 +18,6 @@ using BTCPayServer.Tests.Logging;
|
|||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Data;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NBXplorer.Models;
|
||||
|
||||
|
@ -194,14 +192,5 @@ namespace BTCPayServer.Tests
|
|||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
public async Task<BTCPayOpenIdClient> RegisterOpenIdClient(OpenIddictApplicationDescriptor descriptor, string secret = null)
|
||||
{
|
||||
var openIddictApplicationManager = parent.PayTester.GetService<OpenIddictApplicationManager<BTCPayOpenIdClient>>();
|
||||
var client = new BTCPayOpenIdClient { Id = Guid.NewGuid().ToString(), ApplicationUserId = UserId};
|
||||
await openIddictApplicationManager.PopulateAsync(client, descriptor);
|
||||
await openIddictApplicationManager.CreateAsync(client, secret);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,9 +66,6 @@
|
|||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.20058.15" />
|
||||
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
@ -220,4 +217,8 @@
|
|||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
* the license and the contributors participating to this project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Security.OpenId;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.Authorization;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.AspNetCore;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using System.Security.Claims;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class AuthorizationController : Controller
|
||||
{
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> _authorizationManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IOptions<IdentityOptions> _IdentityOptions;
|
||||
|
||||
public AuthorizationController(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptions<IdentityOptions> identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_signInManager = signInManager;
|
||||
_authorizationManager = authorizationManager;
|
||||
_userManager = userManager;
|
||||
_IdentityOptions = identityOptions;
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize()
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest();
|
||||
// Retrieve the application details from the database.
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
|
||||
if (application == null)
|
||||
{
|
||||
return View("Error",
|
||||
new ErrorViewModel
|
||||
{
|
||||
Error = OpenIddictConstants.Errors.InvalidClient,
|
||||
ErrorDescription =
|
||||
"Details concerning the calling client application cannot be found in the database"
|
||||
});
|
||||
}
|
||||
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (!string.IsNullOrEmpty(
|
||||
await OpenIdExtensions.IsUserAuthorized(_authorizationManager, request, userId, application.Id)))
|
||||
{
|
||||
return await Authorize("YES", false);
|
||||
}
|
||||
|
||||
// Flow the request_id to allow OpenIddict to restore
|
||||
// the original authorization request from the cache.
|
||||
return View(new AuthorizeViewModel
|
||||
{
|
||||
ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
|
||||
RequestId = request.RequestId,
|
||||
Scope = request.GetScopes()
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize(string consent, bool createAuthorization = true)
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest();
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
return View("Error",
|
||||
new ErrorViewModel
|
||||
{
|
||||
Error = OpenIddictConstants.Errors.ServerError,
|
||||
ErrorDescription = "The specified user could not be found"
|
||||
});
|
||||
}
|
||||
|
||||
string type = null;
|
||||
switch (consent.ToUpperInvariant())
|
||||
{
|
||||
case "YESTEMPORARY":
|
||||
type = OpenIddictConstants.AuthorizationTypes.AdHoc;
|
||||
break;
|
||||
case "YES":
|
||||
type = OpenIddictConstants.AuthorizationTypes.Permanent;
|
||||
break;
|
||||
case "NO":
|
||||
default:
|
||||
// Notify OpenIddict that the authorization grant has been denied by the resource owner
|
||||
// to redirect the user agent to the client application using the appropriate response_mode.
|
||||
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
|
||||
var principal = await _signInManager.CreateUserPrincipalAsync(user);
|
||||
principal = await _signInManager.CreateUserPrincipalAsync(user);
|
||||
principal.SetScopes(request.GetScopes().Restrict(principal));
|
||||
principal.SetDestinations(_IdentityOptions.Value);
|
||||
if (createAuthorization)
|
||||
{
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
var authorization = await _authorizationManager.CreateAsync(User, user.Id, application.Id,
|
||||
type, principal.GetScopes());
|
||||
principal.SetInternalAuthorizationId(authorization.Id);
|
||||
}
|
||||
|
||||
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
|
||||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Controllers.RestApi
|
||||
{
|
||||
/// <summary>
|
||||
/// this controller serves as a testing endpoint for our OpenId unit tests
|
||||
/// </summary>
|
||||
[Route("api/test/openid")]
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.OpenId)]
|
||||
public class TestOpenIdController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public TestOpenIdController(UserManager<ApplicationUser> userManager, StoreRepository storeRepository)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
[HttpGet("me/id")]
|
||||
public string GetCurrentUserId()
|
||||
{
|
||||
return _userManager.GetUserId(User);
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
public async Task<ApplicationUser> GetCurrentUser()
|
||||
{
|
||||
return await _userManager.GetUserAsync(User);
|
||||
}
|
||||
|
||||
[HttpGet("me/is-admin")]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.OpenId)]
|
||||
public bool AmIAnAdmin()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
[HttpGet("me/stores")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
|
||||
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
|
||||
public async Task<StoreData[]> GetCurrentUserStores()
|
||||
{
|
||||
return await _storeRepository.GetStoresByUserId(_userManager.GetUserId(User));
|
||||
}
|
||||
|
||||
[HttpGet("me/stores/{storeId}/can-edit")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
|
||||
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
|
||||
public bool CanEdit(string storeId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using BTCPayServer.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NETCore.Encrypt.Extensions.Internal;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class OpenIddictExtensions
|
||||
{
|
||||
public static SecurityKey GetSigningKey(IConfiguration configuration, string fileName)
|
||||
{
|
||||
|
||||
var file = Path.Combine(configuration.GetDataDir(), fileName);
|
||||
using var rsa = new RSACryptoServiceProvider(2048);
|
||||
if (File.Exists(file))
|
||||
{
|
||||
rsa.FromXmlString2(File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
var contents = rsa.ToXmlString2(true);
|
||||
File.WriteAllText(file, contents);
|
||||
}
|
||||
return new RsaSecurityKey(rsa.ExportParameters(true));;
|
||||
}
|
||||
public static OpenIddictServerBuilder ConfigureSigningKey(this OpenIddictServerBuilder builder,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
return builder
|
||||
.AddSigningKey(GetSigningKey(configuration, "signing.rsaparams"))
|
||||
.AddEncryptionKey(GetSigningKey(configuration, "encrypting.rsaparams"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Xml;
|
||||
|
||||
namespace NETCore.Encrypt.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// .net core's implementatiosn are still marked as unsupported because of stupid decisions( https://github.com/dotnet/corefx/issues/23686)
|
||||
/// </summary>
|
||||
internal static class RsaKeyExtensions
|
||||
{
|
||||
#region XML
|
||||
|
||||
public static void FromXmlString2(this RSA rsa, string xmlString)
|
||||
{
|
||||
RSAParameters parameters = new RSAParameters();
|
||||
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(xmlString);
|
||||
|
||||
if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue", StringComparison.InvariantCulture))
|
||||
{
|
||||
foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
|
||||
{
|
||||
switch (node.Name)
|
||||
{
|
||||
case "Modulus":
|
||||
parameters.Modulus = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "Exponent":
|
||||
parameters.Exponent = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "P":
|
||||
parameters.P = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "Q":
|
||||
parameters.Q = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "DP":
|
||||
parameters.DP = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "DQ":
|
||||
parameters.DQ = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "InverseQ":
|
||||
parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
case "D":
|
||||
parameters.D = (string.IsNullOrEmpty(node.InnerText)
|
||||
? null
|
||||
: Convert.FromBase64String(node.InnerText));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid XML RSA key.");
|
||||
}
|
||||
|
||||
rsa.ImportParameters(parameters);
|
||||
}
|
||||
|
||||
public static string ToXmlString2(this RSA rsa, bool includePrivateParameters)
|
||||
{
|
||||
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
"<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
|
||||
parameters.Modulus != null ? Convert.ToBase64String(parameters.Modulus) : null,
|
||||
parameters.Exponent != null ? Convert.ToBase64String(parameters.Exponent) : null,
|
||||
parameters.P != null ? Convert.ToBase64String(parameters.P) : null,
|
||||
parameters.Q != null ? Convert.ToBase64String(parameters.Q) : null,
|
||||
parameters.DP != null ? Convert.ToBase64String(parameters.DP) : null,
|
||||
parameters.DQ != null ? Convert.ToBase64String(parameters.DQ) : null,
|
||||
parameters.InverseQ != null ? Convert.ToBase64String(parameters.InverseQ) : null,
|
||||
parameters.D != null ? Convert.ToBase64String(parameters.D) : null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -41,17 +41,6 @@ using Npgsql;
|
|||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.U2F;
|
||||
using BundlerMinifier.TagHelpers;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using Serilog;
|
||||
|
@ -67,7 +56,6 @@ namespace BTCPayServer.Hosting
|
|||
{
|
||||
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
||||
factory.ConfigureBuilder(o);
|
||||
o.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
||||
});
|
||||
services.AddHttpClient();
|
||||
services.AddHttpClient(nameof(ExplorerClientProvider), httpClient =>
|
||||
|
@ -221,7 +209,6 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<IHostedService, WalletReceiveCacheUpdater>();
|
||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, OpenIdAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
|
||||
|
||||
services.TryAddSingleton<ExplorerClientProvider>();
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
using OpenIddict.Abstractions;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -19,14 +18,10 @@ using System.IO;
|
|||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
using System.Net;
|
||||
using BTCPayServer.Security.OpenId;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Storage;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
|
@ -57,8 +52,6 @@ namespace BTCPayServer.Hosting
|
|||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
ConfigureOpenIddict(services);
|
||||
|
||||
services.AddBTCPayServer(Configuration);
|
||||
services.AddProviderStorage();
|
||||
services.AddSession();
|
||||
|
@ -95,12 +88,6 @@ namespace BTCPayServer.Hosting
|
|||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
options.Password.RequireUppercase = false;
|
||||
// Configure Identity to use the same JWT claims as OpenIddict instead
|
||||
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
|
||||
// which saves you from doing the mapping in your authorization controller.
|
||||
options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name;
|
||||
options.ClaimsIdentity.UserIdClaimType = OpenIddictConstants.Claims.Subject;
|
||||
options.ClaimsIdentity.RoleClaimType = OpenIddictConstants.Claims.Role;
|
||||
});
|
||||
// If the HTTPS certificate path is not set this logic will NOT be used and the default Kestrel binding logic will be.
|
||||
string httpsCertificateFilePath = Configuration.GetOrDefault<string>("HttpsCertificateFilePath", null);
|
||||
|
@ -143,67 +130,6 @@ namespace BTCPayServer.Hosting
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
private DirectoryInfo GetDataDir()
|
||||
{
|
||||
return new DirectoryInfo(Configuration.GetDataDir(DefaultConfiguration.GetNetworkType(Configuration)));
|
||||
}
|
||||
|
||||
private void ConfigureOpenIddict(IServiceCollection services)
|
||||
{
|
||||
// Register the OpenIddict services.
|
||||
services.AddOpenIddict()
|
||||
.AddCore(options =>
|
||||
{
|
||||
// Configure OpenIddict to use the Entity Framework Core stores and entities.
|
||||
options.UseEntityFrameworkCore()
|
||||
.UseDbContext<ApplicationDbContext>()
|
||||
.ReplaceDefaultEntities<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>,
|
||||
BTCPayOpenIdToken, string>();
|
||||
})
|
||||
.AddServer(options =>
|
||||
{
|
||||
options.UseAspNetCore()
|
||||
.EnableStatusCodePagesIntegration()
|
||||
.EnableAuthorizationEndpointPassthrough()
|
||||
.EnableLogoutEndpointPassthrough()
|
||||
.EnableAuthorizationEndpointCaching()
|
||||
.DisableTransportSecurityRequirement();
|
||||
|
||||
// Enable the token endpoint (required to use the password flow).
|
||||
options.SetTokenEndpointUris("/connect/token");
|
||||
options.SetAuthorizationEndpointUris("/connect/authorize");
|
||||
options.SetLogoutEndpointUris("/connect/logout");
|
||||
|
||||
//we do not care about these granular controls for now
|
||||
options.IgnoreScopePermissions();
|
||||
options.IgnoreEndpointPermissions();
|
||||
// Allow client applications various flows
|
||||
options.AllowImplicitFlow();
|
||||
options.AllowClientCredentialsFlow();
|
||||
options.AllowRefreshTokenFlow();
|
||||
options.AllowPasswordFlow();
|
||||
options.AllowAuthorizationCodeFlow();
|
||||
options.UseRollingTokens();
|
||||
|
||||
options.RegisterScopes(
|
||||
OpenIddictConstants.Scopes.OpenId,
|
||||
BTCPayScopes.StoreManagement,
|
||||
BTCPayScopes.ServerManagement
|
||||
);
|
||||
options.AddEventHandler(PasswordGrantTypeEventHandler.Descriptor);
|
||||
options.AddEventHandler(OpenIdGrantHandlerCheckCanSignIn.Descriptor);
|
||||
options.AddEventHandler(ClientCredentialsGrantTypeEventHandler.Descriptor);
|
||||
options.AddEventHandler(LogoutEventHandler.Descriptor);
|
||||
options.ConfigureSigningKey(Configuration);
|
||||
})
|
||||
.AddValidation(options =>
|
||||
{
|
||||
options.UseLocalServer();
|
||||
options.UseAspNetCore();
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IWebHostEnvironment env,
|
||||
|
@ -224,6 +150,10 @@ namespace BTCPayServer.Hosting
|
|||
});
|
||||
}
|
||||
}
|
||||
private DirectoryInfo GetDataDir()
|
||||
{
|
||||
return new DirectoryInfo(Configuration.GetDataDir(DefaultConfiguration.GetNetworkType(Configuration)));
|
||||
}
|
||||
|
||||
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
||||
{
|
||||
|
|
|
@ -53,7 +53,6 @@ namespace BTCPayServer
|
|||
l.AddFilter("Microsoft", LogLevel.Error);
|
||||
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
|
||||
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
|
||||
l.AddFilter("OpenIddict.Server.OpenIddictServerProvider", LogLevel.Error);
|
||||
l.AddProvider(new CustomConsoleLogProvider(processor));
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenIddict.Validation;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class AuthenticationSchemes
|
||||
{
|
||||
public const string Cookie = "Identity.Application";
|
||||
public const string Bitpay = "Bitpay";
|
||||
public const string OpenId = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
|
||||
public const string ApiKey = "GreenfieldApiKey";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using OpenIdConnectRequest = OpenIddict.Abstractions.OpenIddictRequest;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public abstract class BaseOpenIdGrantHandler<T> :
|
||||
IOpenIddictServerHandler<T>
|
||||
where T : OpenIddictServerEvents.BaseContext
|
||||
{
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
private readonly OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> _authorizationManager;
|
||||
protected readonly SignInManager<ApplicationUser> _signInManager;
|
||||
protected readonly IOptions<IdentityOptions> _identityOptions;
|
||||
|
||||
protected BaseOpenIdGrantHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_authorizationManager = authorizationManager;
|
||||
_signInManager = signInManager;
|
||||
_identityOptions = identityOptions;
|
||||
}
|
||||
|
||||
|
||||
protected Task<ClaimsPrincipal> CreateClaimsPrincipalAsync(OpenIdConnectRequest request, ApplicationUser user)
|
||||
{
|
||||
return OpenIdExtensions.CreateClaimsPrincipalAsync(_applicationManager, _authorizationManager,
|
||||
_identityOptions.Value, _signInManager, request, user);
|
||||
}
|
||||
public abstract ValueTask HandleAsync(T notification);
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public class ClientCredentialsGrantTypeEventHandler :
|
||||
BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequestContext>
|
||||
{
|
||||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } =
|
||||
OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.HandleTokenRequestContext>()
|
||||
.UseScopedHandler<ClientCredentialsGrantTypeEventHandler>()
|
||||
.Build();
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public ClientCredentialsGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions,
|
||||
UserManager<ApplicationUser> userManager) : base(applicationManager, authorizationManager, signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public override async ValueTask HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequestContext notification)
|
||||
{
|
||||
var request = notification.Request;
|
||||
var context = notification;
|
||||
if (!request.IsClientCredentialsGrantType())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
if (application == null)
|
||||
{
|
||||
context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidClient,
|
||||
description: "The client application was not found in the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(application.ApplicationUserId);
|
||||
context.Principal = await CreateClaimsPrincipalAsync(request, user);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using Microsoft.AspNetCore;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public class LogoutEventHandler : IOpenIddictServerHandler<OpenIddictServerEvents.HandleLogoutRequestContext>
|
||||
{
|
||||
protected readonly SignInManager<ApplicationUser> _signInManager;
|
||||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } =
|
||||
OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.HandleLogoutRequestContext>()
|
||||
.UseScopedHandler<LogoutEventHandler>()
|
||||
.Build();
|
||||
public LogoutEventHandler(SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(
|
||||
OpenIddictServerEvents.HandleLogoutRequestContext notification)
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public static class OpenIdExtensions
|
||||
{
|
||||
public static ImmutableHashSet<string> Restrict(this ImmutableArray<string> scopes, ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
HashSet<string> restricted = new HashSet<string>();
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
if (scope == BTCPayScopes.ServerManagement && !claimsPrincipal.IsInRole(Roles.ServerAdmin))
|
||||
continue;
|
||||
restricted.Add(scope);
|
||||
}
|
||||
return restricted.ToImmutableHashSet();
|
||||
}
|
||||
public static async Task<ClaimsPrincipal> CreateClaimsPrincipalAsync(OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
IdentityOptions identityOptions,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
OpenIddictRequest request,
|
||||
ApplicationUser user)
|
||||
{
|
||||
var principal = await signInManager.CreateUserPrincipalAsync(user);
|
||||
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
|
||||
{
|
||||
principal.SetScopes(request.GetScopes().Restrict(principal));
|
||||
}
|
||||
else if (request.IsAuthorizationCodeGrantType() &&
|
||||
string.IsNullOrEmpty(principal.GetInternalAuthorizationId()))
|
||||
{
|
||||
var app = await applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
var authorizationId = await IsUserAuthorized(authorizationManager, request, user.Id, app.Id);
|
||||
if (!string.IsNullOrEmpty(authorizationId))
|
||||
{
|
||||
principal.SetInternalAuthorizationId(authorizationId);
|
||||
}
|
||||
}
|
||||
|
||||
principal.SetDestinations(identityOptions);
|
||||
return principal;
|
||||
}
|
||||
|
||||
public static void SetDestinations(this ClaimsPrincipal principal, IdentityOptions identityOptions)
|
||||
{
|
||||
foreach (var claim in principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(identityOptions, claim, principal));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetDestinations(IdentityOptions identityOptions, Claim claim,
|
||||
ClaimsPrincipal principal)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case OpenIddictConstants.Claims.Name:
|
||||
case OpenIddictConstants.Claims.Email:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> IsUserAuthorized(
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
OpenIddictRequest request, string userId, string applicationId)
|
||||
{
|
||||
var authorizations = await authorizationManager.ListAsync(queryable =>
|
||||
queryable.Where(authorization =>
|
||||
authorization.Subject == userId &&
|
||||
authorization.Application.Id == applicationId &&
|
||||
authorization.Status == OpenIddictConstants.Statuses.Valid)).ToArrayAsync();
|
||||
|
||||
if (authorizations.Length > 0)
|
||||
{
|
||||
var scopeTasks = authorizations.Select(authorization =>
|
||||
(authorizationManager.GetScopesAsync(authorization).AsTask(), authorization.Id));
|
||||
await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1));
|
||||
|
||||
var authorizationsWithSufficientScopes = scopeTasks
|
||||
.Select((tuple) => (Id: tuple.Id, Scopes: tuple.Item1.Result))
|
||||
.Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any());
|
||||
|
||||
if (authorizationsWithSufficientScopes.Any())
|
||||
{
|
||||
return authorizationsWithSufficientScopes.First().Id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using OpenIddict.Abstractions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using Microsoft.AspNetCore;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public class OpenIdGrantHandlerCheckCanSignIn :
|
||||
BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequestContext>
|
||||
{
|
||||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } =
|
||||
OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.HandleTokenRequestContext>()
|
||||
.UseScopedHandler<OpenIdGrantHandlerCheckCanSignIn>()
|
||||
.Build();
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public OpenIdGrantHandlerCheckCanSignIn(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(
|
||||
applicationManager, authorizationManager, signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public override async ValueTask HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequestContext notification)
|
||||
{
|
||||
var request = notification.Request;
|
||||
if (!request.IsRefreshTokenGrantType() && !request.IsAuthorizationCodeGrantType())
|
||||
{
|
||||
// Allow other handlers to process the event.
|
||||
return;
|
||||
}
|
||||
|
||||
var httpContext = notification.Transaction.GetHttpRequest().HttpContext;
|
||||
var authenticateResult = (await httpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme));
|
||||
|
||||
var user = await _userManager.GetUserAsync(authenticateResult.Principal);
|
||||
if (user == null)
|
||||
{
|
||||
notification.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The token is no longer valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the user is still allowed to sign in.
|
||||
if (!await _signInManager.CanSignInAsync(user))
|
||||
{
|
||||
notification.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The user is no longer allowed to sign in.");
|
||||
return;
|
||||
}
|
||||
|
||||
notification.Principal = await this.CreateClaimsPrincipalAsync(request, user);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.U2F;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using Microsoft.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Security.OpenId
|
||||
{
|
||||
public class PasswordGrantTypeEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequestContext>
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly NicolasDorier.RateLimits.RateLimitService _rateLimitService;
|
||||
private readonly U2FService _u2FService;
|
||||
|
||||
public PasswordGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
NicolasDorier.RateLimits.RateLimitService rateLimitService,
|
||||
IOptions<IdentityOptions> identityOptions, U2FService u2FService) : base(applicationManager,
|
||||
authorizationManager, signInManager, identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_rateLimitService = rateLimitService;
|
||||
_u2FService = u2FService;
|
||||
}
|
||||
|
||||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } =
|
||||
OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.HandleTokenRequestContext>()
|
||||
.UseScopedHandler<PasswordGrantTypeEventHandler>()
|
||||
.Build();
|
||||
|
||||
public override async ValueTask HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequestContext notification)
|
||||
{
|
||||
var request = notification.Request;
|
||||
if (!request.IsPasswordGrantType())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var httpContext = notification.Transaction.GetHttpRequest().HttpContext;
|
||||
await _rateLimitService.Throttle(ZoneLimits.Login, httpContext.Connection.RemoteIpAddress.ToString(), httpContext.RequestAborted);
|
||||
var user = await _userManager.FindByNameAsync(request.Username);
|
||||
if (user == null || await _u2FService.HasDevices(user.Id) ||
|
||||
!(await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true))
|
||||
.Succeeded)
|
||||
{
|
||||
notification.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The specified credentials are invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
notification.Principal = await CreateClaimsPrincipalAsync(request, user);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using static BTCPayServer.Security.OpenId.RestAPIPolicies;
|
||||
using OpenIddict.Abstractions;
|
||||
using BTCPayServer.Security.OpenId;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class OpenIdAuthorizationHandler : AuthorizationHandler<PolicyRequirement>
|
||||
{
|
||||
private readonly HttpContext _HttpContext;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public OpenIdAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
_HttpContext = httpContextAccessor.HttpContext;
|
||||
_userManager = userManager;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
|
||||
{
|
||||
if (context.User.Identity.AuthenticationType != "AuthenticationTypes.Federation")
|
||||
return;
|
||||
|
||||
bool success = false;
|
||||
switch (requirement.Policy)
|
||||
{
|
||||
case Policies.CanModifyStoreSettings.Key:
|
||||
if (!context.HasScopes(BTCPayScopes.StoreManagement))
|
||||
break;
|
||||
// TODO: It should be possible to grant permission to a specific store
|
||||
// we can do this by adding saving a claim with the specific store id
|
||||
// to the access_token
|
||||
string storeId = _HttpContext.GetImplicitStoreId();
|
||||
if (storeId == null)
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var userid = _userManager.GetUserId(context.User);
|
||||
if (string.IsNullOrEmpty(userid))
|
||||
break;
|
||||
var store = await _storeRepository.FindStore((string)storeId, userid);
|
||||
if (store == null)
|
||||
break;
|
||||
success = true;
|
||||
_HttpContext.SetStoreData(store);
|
||||
}
|
||||
break;
|
||||
case Policies.CanModifyServerSettings.Key:
|
||||
if (!context.HasScopes(BTCPayScopes.ServerManagement))
|
||||
break;
|
||||
// For this authorization, we stil check in database because it is super sensitive.
|
||||
var user = await _userManager.GetUserAsync(context.User);
|
||||
if (user == null)
|
||||
break;
|
||||
if (!await _userManager.IsInRoleAsync(user, Roles.ServerAdmin))
|
||||
break;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using OpenIddict.Abstractions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
|
@ -16,7 +10,7 @@ namespace BTCPayServer.Security
|
|||
{
|
||||
public static bool HasScopes(this AuthorizationHandlerContext context, params string[] scopes)
|
||||
{
|
||||
return scopes.All(s => context.User.HasClaim(c => c.Type == OpenIddictConstants.Claims.Scope && c.Value.Split(' ').Contains(s)));
|
||||
return scopes.All(s => context.User.HasClaim(c => c.Type.Equals("scope", StringComparison.InvariantCultureIgnoreCase) && c.Value.Split(' ').Contains(s)));
|
||||
}
|
||||
public static string GetImplicitStoreId(this HttpContext httpContext)
|
||||
{
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
@using BTCPayServer.Security.OpenId
|
||||
@using OpenIddict.Abstractions
|
||||
@model BTCPayServer.Models.Authorization.AuthorizeViewModel
|
||||
@{
|
||||
|
||||
var scopeMappings = new Dictionary<string, (string Title, string Description)>()
|
||||
{
|
||||
{BTCPayScopes.StoreManagement, ("Manage your stores", "The app will be able to create, modify and delete all your stores.")},
|
||||
{BTCPayScopes.ServerManagement, ("Manage your server", "The app will have total control on your server")},
|
||||
};
|
||||
}
|
||||
<form method="post">
|
||||
<input type="hidden" name="request_id" value="@Model.RequestId"/>
|
||||
<section>
|
||||
<div class="card container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 section-heading">
|
||||
<h2>Authorization Request</h2>
|
||||
<hr class="primary">
|
||||
<p>@Model.ApplicationName is requesting access to your account.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="list-group list-group-flush">
|
||||
@foreach (var scope in Model.Scope)
|
||||
{
|
||||
@if (scopeMappings.TryGetValue(scope, out var text))
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<h5 class="mb-1">@text.Title</h5>
|
||||
<p class="mb-1">@text.Description.</p>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-lg-12 text-center">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" name="consent" id="consent-yes" type="submit" value="Yes">Authorize app</button>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<button class="dropdown-item" name="consent" id="consent-yes-temporary" type="submit" value="YesTemporary">Authorize app until session ends</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" id="consent-no" name="consent" type="submit" value="No">Cancel</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
Loading…
Add table
Reference in a new issue