mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +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="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" 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" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using OpenIddict.EntityFrameworkCore.Models;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -261,28 +260,6 @@ namespace BTCPayServer.Data
|
||||||
.HasOne(o => o.WalletData)
|
.HasOne(o => o.WalletData)
|
||||||
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
|
.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;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BTCPayOpenIdClient> OpenIdClients { get; set; }
|
|
||||||
|
|
||||||
public List<StoredFile> StoredFiles
|
public List<StoredFile> StoredFiles
|
||||||
{
|
{
|
||||||
get;
|
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");
|
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 =>
|
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("InvoiceDataId")
|
b.Property<string>("InvoiceDataId")
|
||||||
|
@ -812,42 +654,6 @@ namespace BTCPayServer.Migrations
|
||||||
b.ToTable("AspNetUserTokens");
|
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 =>
|
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||||
|
@ -877,31 +683,6 @@ namespace BTCPayServer.Migrations
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.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 =>
|
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
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.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using OpenIddict.Abstractions;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
@ -298,7 +297,7 @@ namespace BTCPayServer.Tests
|
||||||
if (userId != null)
|
if (userId != null)
|
||||||
{
|
{
|
||||||
List<Claim> claims = new List<Claim>();
|
List<Claim> claims = new List<Claim>();
|
||||||
claims.Add(new Claim(OpenIddictConstants.Claims.Subject, userId));
|
claims.Add(new Claim(ClaimTypes.NameIdentifier, userId));
|
||||||
if (isAdmin)
|
if (isAdmin)
|
||||||
claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin));
|
claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin));
|
||||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie));
|
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie));
|
||||||
|
|
|
@ -18,8 +18,6 @@ using BTCPayServer.Tests.Logging;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Lightning.CLightning;
|
using BTCPayServer.Lightning.CLightning;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using OpenIddict.Abstractions;
|
|
||||||
using OpenIddict.Core;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
|
|
||||||
|
@ -194,14 +192,5 @@ namespace BTCPayServer.Tests
|
||||||
if (storeController.ModelState.ErrorCount != 0)
|
if (storeController.ModelState.ErrorCount != 0)
|
||||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
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="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||||
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
||||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
<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.Razor.RuntimeCompilation" Version="3.1.1" Condition="'$(Configuration)' == 'Debug'" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -220,4 +217,8 @@
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</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.Services.Apps;
|
||||||
using BTCPayServer.U2F;
|
using BTCPayServer.U2F;
|
||||||
using BundlerMinifier.TagHelpers;
|
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 Microsoft.AspNetCore.Authorization;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
@ -67,7 +56,6 @@ namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
var factory = provider.GetRequiredService<ApplicationDbContextFactory>();
|
||||||
factory.ConfigureBuilder(o);
|
factory.ConfigureBuilder(o);
|
||||||
o.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
|
||||||
});
|
});
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
services.AddHttpClient(nameof(ExplorerClientProvider), httpClient =>
|
services.AddHttpClient(nameof(ExplorerClientProvider), httpClient =>
|
||||||
|
@ -221,7 +209,6 @@ namespace BTCPayServer.Hosting
|
||||||
services.AddSingleton<IHostedService, WalletReceiveCacheUpdater>();
|
services.AddSingleton<IHostedService, WalletReceiveCacheUpdater>();
|
||||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||||
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
||||||
services.AddScoped<IAuthorizationHandler, OpenIdAuthorizationHandler>();
|
|
||||||
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
|
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
|
||||||
|
|
||||||
services.TryAddSingleton<ExplorerClientProvider>();
|
services.TryAddSingleton<ExplorerClientProvider>();
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using OpenIddict.Validation.AspNetCore;
|
|
||||||
using OpenIddict.Abstractions;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
@ -19,14 +18,10 @@ using System.IO;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using OpenIddict.EntityFrameworkCore.Models;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using BTCPayServer.Security.OpenId;
|
|
||||||
using BTCPayServer.PaymentRequest;
|
using BTCPayServer.PaymentRequest;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Storage;
|
using BTCPayServer.Storage;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using OpenIddict.Core;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
|
@ -57,8 +52,6 @@ namespace BTCPayServer.Hosting
|
||||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
ConfigureOpenIddict(services);
|
|
||||||
|
|
||||||
services.AddBTCPayServer(Configuration);
|
services.AddBTCPayServer(Configuration);
|
||||||
services.AddProviderStorage();
|
services.AddProviderStorage();
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
|
@ -95,12 +88,6 @@ namespace BTCPayServer.Hosting
|
||||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||||
options.Lockout.AllowedForNewUsers = true;
|
options.Lockout.AllowedForNewUsers = true;
|
||||||
options.Password.RequireUppercase = false;
|
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.
|
// 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);
|
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(
|
public void Configure(
|
||||||
IApplicationBuilder app,
|
IApplicationBuilder app,
|
||||||
IWebHostEnvironment env,
|
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)
|
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("Microsoft", LogLevel.Error);
|
||||||
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
|
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
|
||||||
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
|
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
|
||||||
l.AddFilter("OpenIddict.Server.OpenIddictServerProvider", LogLevel.Error);
|
|
||||||
l.AddProvider(new CustomConsoleLogProvider(processor));
|
l.AddProvider(new CustomConsoleLogProvider(processor));
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
using System;
|
namespace BTCPayServer.Security
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using OpenIddict.Validation;
|
|
||||||
using OpenIddict.Validation.AspNetCore;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
|
||||||
{
|
{
|
||||||
public class AuthenticationSchemes
|
public class AuthenticationSchemes
|
||||||
{
|
{
|
||||||
public const string Cookie = "Identity.Application";
|
public const string Cookie = "Identity.Application";
|
||||||
public const string Bitpay = "Bitpay";
|
public const string Bitpay = "Bitpay";
|
||||||
public const string OpenId = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
|
|
||||||
public const string ApiKey = "GreenfieldApiKey";
|
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;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using OpenIddict.Abstractions;
|
|
||||||
using BTCPayServer.Data;
|
|
||||||
using BTCPayServer.Services.Stores;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
|
@ -16,7 +10,7 @@ namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
public static bool HasScopes(this AuthorizationHandlerContext context, params string[] scopes)
|
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)
|
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