diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..15ccc1f7f066e0dd9eb461d5f3f27d9cda808d6a
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "cSpell.words": [
+ "configurationstore",
+ "Dapr",
+ "Healthchecks",
+ "Idsrv",
+ "Linq",
+ "Pluggable",
+ "secretstore",
+ "statestore"
+ ]
+}
\ No newline at end of file
diff --git a/DaprTool.Solution.sln b/DaprTool.Solution.sln
index a9c01186e54b8f640944b14839a4f5d4feb6c096..da4058803824996f8f67ad68bda89bd4c281b974 100644
--- a/DaprTool.Solution.sln
+++ b/DaprTool.Solution.sln
@@ -125,6 +125,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAdmin.Client", "src\Web\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAdmin", "src\Web\WebAdmin\WebAdmin.csproj", "{EA794699-4DD1-4622-A80F-DB138AB53D86}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin", "src\IdentityServer4\src\Idsrv4.Admin\Idsrv4.Admin.csproj", "{69BAC3F6-575C-49C6-B51C-2C198178BC85}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.STS.Identity", "src\IdentityServer4\src\Idsrv4.Admin.STS.Identity\Idsrv4.Admin.STS.Identity.csproj", "{B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.Api", "src\IdentityServer4\src\Idsrv4.Admin.Api\Idsrv4.Admin.Api.csproj", "{D1A7D06B-09B9-459B-B443-259E6BCF3E27}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.EntityFramework.Shared", "src\IdentityServer4\src\Idsrv4.Admin.EntityFramework.Shared\Idsrv4.Admin.EntityFramework.Shared.csproj", "{03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.EntityFramework.PostgreSQL", "src\IdentityServer4\src\Idsrv4.Admin.EntityFramework.PostgreSQL\Idsrv4.Admin.EntityFramework.PostgreSQL.csproj", "{297596B1-296D-47C7-A4A4-0C1C25A347F2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.Shared", "src\IdentityServer4\src\Idsrv4.Admin.Shared\Idsrv4.Admin.Shared.csproj", "{AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.AuditLogging", "src\IdentityServer4\src\Idsrv4.Admin.AuditLogging\Idsrv4.Admin.AuditLogging.csproj", "{3A8C58F1-027A-4B82-BEFF-AA82383AE8D7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.AuditLogging.EntityFramework", "src\IdentityServer4\src\Idsrv4.Admin.AuditLogging.EntityFramework\Idsrv4.Admin.AuditLogging.EntityFramework.csproj", "{36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Idsrv4.Admin.UI", "src\IdentityServer4\src\Idsrv4.Admin.UI\Idsrv4.Admin.UI.csproj", "{07E8E5C4-DA82-44DD-B526-6A4150D5C9FD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -227,6 +245,42 @@ Global
{EA794699-4DD1-4622-A80F-DB138AB53D86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA794699-4DD1-4622-A80F-DB138AB53D86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA794699-4DD1-4622-A80F-DB138AB53D86}.Release|Any CPU.Build.0 = Release|Any CPU
+ {69BAC3F6-575C-49C6-B51C-2C198178BC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {69BAC3F6-575C-49C6-B51C-2C198178BC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69BAC3F6-575C-49C6-B51C-2C198178BC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {69BAC3F6-575C-49C6-B51C-2C198178BC85}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1A7D06B-09B9-459B-B443-259E6BCF3E27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1A7D06B-09B9-459B-B443-259E6BCF3E27}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1A7D06B-09B9-459B-B443-259E6BCF3E27}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1A7D06B-09B9-459B-B443-259E6BCF3E27}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85}.Release|Any CPU.Build.0 = Release|Any CPU
+ {297596B1-296D-47C7-A4A4-0C1C25A347F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {297596B1-296D-47C7-A4A4-0C1C25A347F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {297596B1-296D-47C7-A4A4-0C1C25A347F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {297596B1-296D-47C7-A4A4-0C1C25A347F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3A8C58F1-027A-4B82-BEFF-AA82383AE8D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A8C58F1-027A-4B82-BEFF-AA82383AE8D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A8C58F1-027A-4B82-BEFF-AA82383AE8D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A8C58F1-027A-4B82-BEFF-AA82383AE8D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07E8E5C4-DA82-44DD-B526-6A4150D5C9FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07E8E5C4-DA82-44DD-B526-6A4150D5C9FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07E8E5C4-DA82-44DD-B526-6A4150D5C9FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07E8E5C4-DA82-44DD-B526-6A4150D5C9FD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -278,6 +332,15 @@ Global
{9FCF0D70-CF60-4C0D-8667-42DB50BC6EE2} = {6720D68A-1AF5-48BA-8CB5-349E70E151B0}
{306E933B-914F-4935-A924-DA756F766753} = {6720D68A-1AF5-48BA-8CB5-349E70E151B0}
{EA794699-4DD1-4622-A80F-DB138AB53D86} = {6720D68A-1AF5-48BA-8CB5-349E70E151B0}
+ {69BAC3F6-575C-49C6-B51C-2C198178BC85} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {B28F0B35-FA5E-4FCA-89E4-87DF29DEEC71} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {D1A7D06B-09B9-459B-B443-259E6BCF3E27} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {03A34FBE-B8ED-4D47-8BF0-3AD55FDD9E85} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {297596B1-296D-47C7-A4A4-0C1C25A347F2} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {AB633ACE-EB70-4ABB-AE2C-07C893EFD1C0} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {3A8C58F1-027A-4B82-BEFF-AA82383AE8D7} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {36A1CAB5-53B0-46E2-A083-DD3CA9E08BB1} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
+ {07E8E5C4-DA82-44DD-B526-6A4150D5C9FD} = {D2F20A9C-E732-43EA-9615-70F9FD46253A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CE377903-BDA1-4347-BEC2-62ED2F807EE3}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b0e6f36e9c2522c2c8244afbe53780e0725f6483..bf6d1d9051da65014d3191d678b1deff3a99c6df 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,30 +16,28 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
@@ -47,8 +45,8 @@
+
-
@@ -58,6 +56,14 @@
+
+
+
+
+
+
+
+
@@ -66,13 +72,24 @@
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -88,4 +105,41 @@
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props.bakup b/Directory.Packages.props.bakup
new file mode 100644
index 0000000000000000000000000000000000000000..2dac5cf4953ad9509980a103fd690d255b997735
--- /dev/null
+++ b/Directory.Packages.props.bakup
@@ -0,0 +1,125 @@
+
+
+ true
+
+
+ 8.0.0
+ 8.0.0
+ 8.0.4
+ 1.13.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SyncPackageVersions.ps1 b/SyncPackageVersions.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..b99f83bbc5786890307cced3d1c38b9629fd42cd
--- /dev/null
+++ b/SyncPackageVersions.ps1
@@ -0,0 +1,65 @@
+# 设置相对路径
+param(
+ [Parameter(Mandatory=$true, HelpMessage="Please provide the path to the source directory. For example: .\src\IdentityServer4")]
+ [string]$srcDirectory
+)
+
+# 验证目录是否存在
+if (-not (Test-Path $srcDirectory)) {
+ Write-Error "The specified source directory does not exist: $srcDirectory"
+ Write-Host "Please provide a valid directory path. Example: .\src\IdentityServer4"
+ return
+}
+
+$packagesPropsPath = ".\Directory.Packages.props"
+
+# 读取 Directory.Packages.props 文件以获取现有的包版本
+[xml]$packagesProps = Get-Content $packagesPropsPath
+
+# 递归查找所有 csproj 文件
+$csprojFiles = Get-ChildItem $srcDirectory -Recurse -Filter "*.csproj"
+
+foreach ($csprojFile in $csprojFiles) {
+ [xml]$csproj = Get-Content $csprojFile.FullName
+
+ # 遍历所有 ItemGroup
+ foreach ($itemGroup in $csproj.Project.ItemGroup) {
+ # 遍历当前 ItemGroup 中的所有 PackageReference 和 DotNetCliToolReference
+ foreach ($reference in $itemGroup.PackageReference, $itemGroup.DotNetCliToolReference) {
+ $packageName = $reference.Include
+ if ([string]::IsNullOrEmpty($packageName)) {
+ $packageName = $reference.Update
+ }
+ $packageVersion = $reference.Version
+
+ # 确保包名称和版本都不为空
+ if ([string]::IsNullOrWhiteSpace($packageName) -or [string]::IsNullOrWhiteSpace($packageVersion)) {
+ Write-Host "Skipping invalid reference in $($csprojFile.Name)"
+ continue
+ }
+
+ # 检查 PackageVersion 是否已在 Directory.Packages.props 中定义
+ $existingPackage = $packagesProps.Project.ItemGroup.PackageVersion | Where-Object { $_.Include -eq $packageName }
+
+ if (-not $existingPackage) {
+ # 如果不存在,则添加新的 PackageVersion
+ $newNode = $packagesProps.CreateElement("PackageVersion")
+ $newNode.SetAttribute("Include", $packageName)
+ $newNode.SetAttribute("Version", $packageVersion)
+ $packagesProps.Project.ItemGroup.AppendChild($newNode) | Out-Null
+ }
+
+ # 检查是否存在 Version 属性,并移除它
+ if ($reference.Version) {
+ $reference.RemoveAttribute("Version")
+ }
+ }
+ }
+
+ # 保存修改后的 csproj 文件
+ $csproj.Save($csprojFile.FullName)
+}
+
+# 保存修改后的 Directory.Packages.props 文件
+$packagesProps.Save($packagesPropsPath)
+
diff --git a/src/IdentityServer4/Directory.Build.props b/src/IdentityServer4/Directory.Build.props
new file mode 100644
index 0000000000000000000000000000000000000000..973a9c255a54bd8be469b4fc04cdd36603fc9acb
--- /dev/null
+++ b/src/IdentityServer4/Directory.Build.props
@@ -0,0 +1,27 @@
+
+
+
+ 8.4.0
+ iamshen
+ latest
+ IdentityServer4 Admin OpenIDConnect OAuth2 Identity
+ https://gitee.com/gold-cloud/reborn-identity-server4-admin
+ LICENSE.md
+ icon.png
+
+
+
+
+ True
+ \
+
+
+
+
+
+ True
+ \
+
+
+
+
diff --git a/src/IdentityServer4/Idsrv4.Admin.sln b/src/IdentityServer4/Idsrv4.Admin.sln
new file mode 100644
index 0000000000000000000000000000000000000000..b51db7a36eeb1181ff880e6ae026365090f14cd5
--- /dev/null
+++ b/src/IdentityServer4/Idsrv4.Admin.sln
@@ -0,0 +1,155 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34723.18
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{588205D4-3A30-4DA4-849D-C7422C396DAA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin", "src\Idsrv4.Admin\Idsrv4.Admin.csproj", "{E3713598-3375-4E81-9EC7-58DC090789BD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sts", "sts", "{63D44665-AC4C-45F4-A2C7-A7DB394F44C4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework", "src\Idsrv4.Admin.EntityFramework\Idsrv4.Admin.EntityFramework.csproj", "{D9F5B8B1-01F5-4996-8E75-A41532CF32CD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.BusinessLogic", "src\Idsrv4.Admin.BusinessLogic\Idsrv4.Admin.BusinessLogic.csproj", "{491B30A8-D4A1-42E8-8DEE-4093E0E45C36}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.STS.Identity", "src\Idsrv4.Admin.STS.Identity\Idsrv4.Admin.STS.Identity.csproj", "{72F17B1A-88D9-47FD-AA35-1C700E51CD0E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework.Identity", "src\Idsrv4.Admin.EntityFramework.Identity\Idsrv4.Admin.EntityFramework.Identity.csproj", "{2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.BusinessLogic.Shared", "src\Idsrv4.Admin.BusinessLogic.Shared\Idsrv4.Admin.BusinessLogic.Shared.csproj", "{C360A0D5-1671-4738-BC5D-BED0E8A24D66}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.BusinessLogic.Identity", "src\Idsrv4.Admin.BusinessLogic.Identity\Idsrv4.Admin.BusinessLogic.Identity.csproj", "{CA63CC7B-BE27-4737-AE91-42E43F729A1E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.Api", "src\Idsrv4.Admin.Api\Idsrv4.Admin.Api.csproj", "{8F112368-2E45-4C3A-922E-85AB6056F559}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework.Shared", "src\Idsrv4.Admin.EntityFramework.Shared\Idsrv4.Admin.EntityFramework.Shared.csproj", "{E18F8C70-7448-4039-9D78-1369D7F498EF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework.Extensions", "src\Idsrv4.Admin.EntityFramework.Extensions\Idsrv4.Admin.EntityFramework.Extensions.csproj", "{2DD3CB7D-462E-4039-B684-81B1E88C7C6A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework.PostgreSQL", "src\Idsrv4.Admin.EntityFramework.PostgreSQL\Idsrv4.Admin.EntityFramework.PostgreSQL.csproj", "{3ECDC91E-0D3E-4E4D-A34E-D33BB714578D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "database", "database", "{2A514C8F-6A53-41CA-AB41-B644E7BC92A7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "business", "business", "{EE588CE5-51D0-4E98-A2B3-40EC8E655931}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.Shared", "src\Idsrv4.Admin.Shared\Idsrv4.Admin.Shared.csproj", "{61B285F0-EE06-4AEE-AAF3-71492CBD11C5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.UI", "src\Idsrv4.Admin.UI\Idsrv4.Admin.UI.csproj", "{6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.EntityFramework.Configuration", "src\Idsrv4.Admin.EntityFramework.Configuration\Idsrv4.Admin.EntityFramework.Configuration.csproj", "{45FB23BE-A7F9-4172-8868-B5E387007644}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.Shared.Configuration", "src\Idsrv4.Admin.Shared.Configuration\Idsrv4.Admin.Shared.Configuration.csproj", "{D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "auditLogging", "auditLogging", "{CBC54971-1FE1-44E8-AA82-C394560B4E81}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.AuditLogging", "src\Idsrv4.Admin.AuditLogging\Idsrv4.Admin.AuditLogging.csproj", "{F843A1AB-BE38-453C-9B1F-30433DFAF82B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idsrv4.Admin.AuditLogging.EntityFramework", "src\Idsrv4.Admin.AuditLogging.EntityFramework\Idsrv4.Admin.AuditLogging.EntityFramework.csproj", "{45AAF398-DA52-47A2-87D1-01B143BB92A4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E6E956E2-37F6-4D44-B8CA-BFEDBECD1C56}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E3713598-3375-4E81-9EC7-58DC090789BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3713598-3375-4E81-9EC7-58DC090789BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3713598-3375-4E81-9EC7-58DC090789BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3713598-3375-4E81-9EC7-58DC090789BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D9F5B8B1-01F5-4996-8E75-A41532CF32CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D9F5B8B1-01F5-4996-8E75-A41532CF32CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D9F5B8B1-01F5-4996-8E75-A41532CF32CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D9F5B8B1-01F5-4996-8E75-A41532CF32CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {491B30A8-D4A1-42E8-8DEE-4093E0E45C36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {491B30A8-D4A1-42E8-8DEE-4093E0E45C36}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {491B30A8-D4A1-42E8-8DEE-4093E0E45C36}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {491B30A8-D4A1-42E8-8DEE-4093E0E45C36}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72F17B1A-88D9-47FD-AA35-1C700E51CD0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72F17B1A-88D9-47FD-AA35-1C700E51CD0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72F17B1A-88D9-47FD-AA35-1C700E51CD0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72F17B1A-88D9-47FD-AA35-1C700E51CD0E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C360A0D5-1671-4738-BC5D-BED0E8A24D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C360A0D5-1671-4738-BC5D-BED0E8A24D66}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C360A0D5-1671-4738-BC5D-BED0E8A24D66}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C360A0D5-1671-4738-BC5D-BED0E8A24D66}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA63CC7B-BE27-4737-AE91-42E43F729A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA63CC7B-BE27-4737-AE91-42E43F729A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA63CC7B-BE27-4737-AE91-42E43F729A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA63CC7B-BE27-4737-AE91-42E43F729A1E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8F112368-2E45-4C3A-922E-85AB6056F559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8F112368-2E45-4C3A-922E-85AB6056F559}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8F112368-2E45-4C3A-922E-85AB6056F559}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8F112368-2E45-4C3A-922E-85AB6056F559}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E18F8C70-7448-4039-9D78-1369D7F498EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E18F8C70-7448-4039-9D78-1369D7F498EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E18F8C70-7448-4039-9D78-1369D7F498EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E18F8C70-7448-4039-9D78-1369D7F498EF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2DD3CB7D-462E-4039-B684-81B1E88C7C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2DD3CB7D-462E-4039-B684-81B1E88C7C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2DD3CB7D-462E-4039-B684-81B1E88C7C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2DD3CB7D-462E-4039-B684-81B1E88C7C6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3ECDC91E-0D3E-4E4D-A34E-D33BB714578D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3ECDC91E-0D3E-4E4D-A34E-D33BB714578D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3ECDC91E-0D3E-4E4D-A34E-D33BB714578D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3ECDC91E-0D3E-4E4D-A34E-D33BB714578D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {45FB23BE-A7F9-4172-8868-B5E387007644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45FB23BE-A7F9-4172-8868-B5E387007644}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45FB23BE-A7F9-4172-8868-B5E387007644}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45FB23BE-A7F9-4172-8868-B5E387007644}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F843A1AB-BE38-453C-9B1F-30433DFAF82B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F843A1AB-BE38-453C-9B1F-30433DFAF82B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F843A1AB-BE38-453C-9B1F-30433DFAF82B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F843A1AB-BE38-453C-9B1F-30433DFAF82B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {45AAF398-DA52-47A2-87D1-01B143BB92A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45AAF398-DA52-47A2-87D1-01B143BB92A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45AAF398-DA52-47A2-87D1-01B143BB92A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45AAF398-DA52-47A2-87D1-01B143BB92A4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E3713598-3375-4E81-9EC7-58DC090789BD} = {588205D4-3A30-4DA4-849D-C7422C396DAA}
+ {D9F5B8B1-01F5-4996-8E75-A41532CF32CD} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {491B30A8-D4A1-42E8-8DEE-4093E0E45C36} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
+ {72F17B1A-88D9-47FD-AA35-1C700E51CD0E} = {63D44665-AC4C-45F4-A2C7-A7DB394F44C4}
+ {2FAECDE3-8D21-4C36-BFF1-3F7C1A56F0D4} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {C360A0D5-1671-4738-BC5D-BED0E8A24D66} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
+ {CA63CC7B-BE27-4737-AE91-42E43F729A1E} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
+ {8F112368-2E45-4C3A-922E-85AB6056F559} = {588205D4-3A30-4DA4-849D-C7422C396DAA}
+ {E18F8C70-7448-4039-9D78-1369D7F498EF} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {2DD3CB7D-462E-4039-B684-81B1E88C7C6A} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {3ECDC91E-0D3E-4E4D-A34E-D33BB714578D} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {61B285F0-EE06-4AEE-AAF3-71492CBD11C5} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
+ {6DD24C2C-0FB5-4C37-8B42-5DACA0FDE4EC} = {588205D4-3A30-4DA4-849D-C7422C396DAA}
+ {45FB23BE-A7F9-4172-8868-B5E387007644} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
+ {D49A2D61-AEEB-457C-B3BA-D1322EB2F4EC} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
+ {F843A1AB-BE38-453C-9B1F-30433DFAF82B} = {CBC54971-1FE1-44E8-AA82-C394560B4E81}
+ {45AAF398-DA52-47A2-87D1-01B143BB92A4} = {CBC54971-1FE1-44E8-AA82-C394560B4E81}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B3166EDE-037B-4C68-BEBA-5DE9C5E3DB82}
+ EndGlobalSection
+EndGlobal
diff --git a/src/IdentityServer4/Idsrv4.Admin.sln.DotSettings b/src/IdentityServer4/Idsrv4.Admin.sln.DotSettings
new file mode 100644
index 0000000000000000000000000000000000000000..6b9a75765074707f8c41e39d63d68765e050553b
--- /dev/null
+++ b/src/IdentityServer4/Idsrv4.Admin.sln.DotSettings
@@ -0,0 +1,11 @@
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/src/IdentityServer4/LICENSE.md b/src/IdentityServer4/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..56c3802a7aa43f436705014f80688baae50f95ec
--- /dev/null
+++ b/src/IdentityServer4/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Jan Skoruba
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/IdentityServer4/README.md b/src/IdentityServer4/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1d389dccbfbf118676b0da8a28198cbe459d46b5
--- /dev/null
+++ b/src/IdentityServer4/README.md
@@ -0,0 +1,59 @@
+# Idsrv4.Admin
+
+
+
+[](https://www.nuget.org/packages/Idsrv4.Admin.Templates)
+[](https://www.nuget.org/packages/Idsrv4.Admin.Templates)
+
+
+The administration for the IdentityServer4 and Asp.Net Core Identity
+The application is written in the **Asp.Net Core MVC - using .NET 8**
+
+
+## QuickStart
+
+### Requirements
+
+- [Install](https://www.microsoft.com/net/download/windows#/current) the latest .NET 8 SDK
+
+
+### Installation via dotnet new template
+
+```bash
+# 安装/更新
+dotnet new install Idsrv4.Admin.Templates
+# 卸载
+dotnet new uninstall Idsrv4.Admin.Templates
+```
+
+### Create new project:
+
+new solution
+
+```bash
+dotnet new reborn.is4admin --name SampleIds4.Admin --title "Sample IdentityServer4 Admin" --adminrole Administrator --adminclientid sample_identity_admin --adminclientsecret sample_admin_client_secret --force
+
+```
+
+options:
+
+```bash
+--name: [string value] The project name
+
+--title: [string value] The title and footer of the administration
+
+--adminrole: [string value] The name of admin role, that is used to authorize the
+
+--adminclientid: [string value] The name of client, that is be used in the IdentityServer4
+
+--adminclientsecret: [string value] The value of client secret, that is be used in the IdentityServer4
+```
+
+
+
+
+# Note
+
+> This project, modified by iamshen from [IdentityServer4.Admin](https://github.com/skoruba/IdentityServer4.Admin), has been upgraded to .NET 8.
+
+> The project also references [Reborn.IdentityServer4](https://www.nuget.org/packages/Reborn.IdentityServer4), which also supports .NET 8.
\ No newline at end of file
diff --git a/src/IdentityServer4/package/icon.png b/src/IdentityServer4/package/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c4054921b5e038245426971edc1f8c6aa2fbbea
Binary files /dev/null and b/src/IdentityServer4/package/icon.png differ
diff --git a/src/IdentityServer4/shared/identitydata.json b/src/IdentityServer4/shared/identitydata.json
new file mode 100644
index 0000000000000000000000000000000000000000..00e562874389a82bea55d6237f5f47412242ab44
--- /dev/null
+++ b/src/IdentityServer4/shared/identitydata.json
@@ -0,0 +1,25 @@
+{
+ "IdentityData": {
+ "Roles": [
+ {
+ "Name": "SkorubaIdentityAdminAdministrator"
+ }
+ ],
+ "Users": [
+ {
+ "Username": "admin",
+ "Password": "Pa$$word123",
+ "Email": "admin@skoruba.com",
+ "Roles": [
+ "SkorubaIdentityAdminAdministrator"
+ ],
+ "Claims": [
+ {
+ "Type": "name",
+ "Value": "admin"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/shared/identityserverdata.json b/src/IdentityServer4/shared/identityserverdata.json
new file mode 100644
index 0000000000000000000000000000000000000000..04997e008284d2b4ea577f9a5b419489d210f40f
--- /dev/null
+++ b/src/IdentityServer4/shared/identityserverdata.json
@@ -0,0 +1,134 @@
+{
+ "IdentityServerData": {
+ "IdentityResources": [
+ {
+ "Name": "roles",
+ "Enabled": true,
+ "DisplayName": "Roles",
+ "UserClaims": [
+ "role"
+ ]
+ },
+ {
+ "Name": "openid",
+ "Enabled": true,
+ "Required": true,
+ "DisplayName": "Your user identifier",
+ "UserClaims": [
+ "sub"
+ ]
+ },
+ {
+ "Name": "profile",
+ "Enabled": true,
+ "DisplayName": "User profile",
+ "Description": "Your user profile information (first name, last name, etc.)",
+ "Emphasize": true,
+ "UserClaims": [
+ "name",
+ "family_name",
+ "given_name",
+ "middle_name",
+ "nickname",
+ "preferred_username",
+ "profile",
+ "picture",
+ "website",
+ "gender",
+ "birthdate",
+ "zoneinfo",
+ "locale",
+ "updated_at"
+ ]
+ },
+ {
+ "Name": "email",
+ "Enabled": true,
+ "DisplayName": "Your email address",
+ "Emphasize": true,
+ "UserClaims": [
+ "email",
+ "email_verified"
+ ]
+ },
+ {
+ "Name": "address",
+ "Enabled": true,
+ "DisplayName": "Your address",
+ "Emphasize": true,
+ "UserClaims": [
+ "address"
+ ]
+ }
+ ],
+ "ApiScopes": [
+ {
+ "Name": "skoruba_identity_admin_api",
+ "DisplayName": "skoruba_identity_admin_api",
+ "Required": true,
+ "UserClaims": [
+ "role",
+ "name"
+ ]
+ }
+ ],
+ "ApiResources": [
+ {
+ "Name": "skoruba_identity_admin_api",
+ "Scopes": [
+ "skoruba_identity_admin_api"
+ ]
+ }
+ ],
+ "Clients": [
+ {
+ "ClientId": "skoruba_identity_admin",
+ "ClientName": "skoruba_identity_admin",
+ "ClientUri": "https://admin.skoruba.local",
+ "AllowedGrantTypes": [
+ "authorization_code"
+ ],
+ "RequirePkce": true,
+ "ClientSecrets": [
+ {
+ "Value": "skoruba_admin_client_secret"
+ }
+ ],
+ "RedirectUris": [
+ "https://admin.skoruba.local/signin-oidc"
+ ],
+ "FrontChannelLogoutUri": "https://admin.skoruba.local/signout-oidc",
+ "PostLogoutRedirectUris": [
+ "https://admin.skoruba.local/signout-callback-oidc"
+ ],
+ "AllowedCorsOrigins": [
+ "https://admin.skoruba.local"
+ ],
+ "AllowedScopes": [
+ "openid",
+ "email",
+ "profile",
+ "roles"
+ ]
+ },
+ {
+ "ClientId": "skoruba_identity_admin_api_swaggerui",
+ "ClientName": "skoruba_identity_admin_api_swaggerui",
+ "AllowedGrantTypes": [
+ "authorization_code"
+ ],
+ "RequireClientSecret": false,
+ "RequirePkce": true,
+ "RedirectUris": [
+ "https://admin-api.skoruba.local/swagger/oauth2-redirect.html"
+ ],
+ "AllowedScopes": [
+ "skoruba_identity_admin_api"
+ ],
+ "AllowedCorsOrigins": [
+ "https://admin-api.skoruba.local"
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/shared/nginx/vhost.d/admin.skoruba.local_location b/src/IdentityServer4/shared/nginx/vhost.d/admin.skoruba.local_location
new file mode 100644
index 0000000000000000000000000000000000000000..f8869876ee384aad6e4660d477136cf6e4515148
--- /dev/null
+++ b/src/IdentityServer4/shared/nginx/vhost.d/admin.skoruba.local_location
@@ -0,0 +1,3 @@
+ proxy_buffer_size 128k;
+ proxy_buffers 4 256k;
+ proxy_busy_buffers_size 256k;
\ No newline at end of file
diff --git a/src/IdentityServer4/shared/nginx/vhost.d/sts.skoruba.local_location b/src/IdentityServer4/shared/nginx/vhost.d/sts.skoruba.local_location
new file mode 100644
index 0000000000000000000000000000000000000000..f8869876ee384aad6e4660d477136cf6e4515148
--- /dev/null
+++ b/src/IdentityServer4/shared/nginx/vhost.d/sts.skoruba.local_location
@@ -0,0 +1,3 @@
+ proxy_buffer_size 128k;
+ proxy_buffers 4 256k;
+ proxy_busy_buffers_size 256k;
\ No newline at end of file
diff --git a/src/IdentityServer4/shared/serilog.json b/src/IdentityServer4/shared/serilog.json
new file mode 100644
index 0000000000000000000000000000000000000000..2281494966c7f99f426211b98da4e80c542d8cbd
--- /dev/null
+++ b/src/IdentityServer4/shared/serilog.json
@@ -0,0 +1,36 @@
+{
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Console" ],
+ "MinimumLevel": {
+ "Default": "Debug",
+ "Override": {
+ "Microsoft": "Information",
+ "System": "Error"
+ }
+ },
+ "WriteTo": [
+ {
+ "Args": {
+ "outputTemplate": "[{Timestamp:o}][{Level:u4}][{ThreadId}][{SourceContext}] {Message}{NewLine}{Exception}"
+ },
+ "Name": "Console"
+ },
+ {
+ "Args": {
+ "buffered": true,
+ "flushToDiskInterval": 15,
+ "outputTemplate": "[{Timestamp:o}][{Level:u4}][{ThreadId}][{SourceContext}] {Message}{NewLine}{Exception}",
+ "pathFormat": "Logs//log-{Date}.txt",
+ "retainedFileCountLimit": 3,
+ "textFormatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
+ },
+ "Name": "RollingFile"
+ }
+ ],
+ "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
+ "Properties": {
+ "Product": "Admin",
+ "Env": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AdminApiConfiguration.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AdminApiConfiguration.cs
new file mode 100644
index 0000000000000000000000000000000000000000..45b01bbd9e2212ac641a4641495e5f1e188ddadd
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AdminApiConfiguration.cs
@@ -0,0 +1,24 @@
+namespace Idsrv4.Admin.Api.Configuration;
+
+public class AdminApiConfiguration
+{
+ public string ApiName { get; set; }
+
+ public string ApiVersion { get; set; }
+
+ public string IdentityServerBaseUrl { get; set; }
+
+ public string ApiBaseUrl { get; set; }
+
+ public string OidcSwaggerUIClientId { get; set; }
+
+ public bool RequireHttpsMetadata { get; set; }
+
+ public string OidcApiName { get; set; }
+
+ public string AdministrationRole { get; set; }
+
+ public bool CorsAllowAnyOrigin { get; set; }
+
+ public string[] CorsAllowOrigins { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericControllerRouteConvention.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericControllerRouteConvention.cs
new file mode 100644
index 0000000000000000000000000000000000000000..62c0398aa80f951f11ac740578dca017d081ad0c
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericControllerRouteConvention.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+
+namespace Idsrv4.Admin.Api.Configuration.ApplicationParts;
+
+public class GenericControllerRouteConvention : IControllerModelConvention
+{
+ public void Apply(ControllerModel controller)
+ {
+ if (controller.ControllerType.IsGenericType)
+ {
+ // this change is required because some of the controllers have generic parameters
+ // and require resolution that will remove arity from the type
+ // as well as remove the 'Controller' at the end of string
+
+ var name = controller.ControllerType.Name;
+ var nameWithoutArity = name.Substring(0, name.IndexOf('`'));
+ controller.ControllerName = nameWithoutArity.Substring(0, nameWithoutArity.LastIndexOf("Controller"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericTypeControllerFeatureProvider.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericTypeControllerFeatureProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..deed5825e2819988c8960c95f411368152e2eb18
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/ApplicationParts/GenericTypeControllerFeatureProvider.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Idsrv4.Admin.BusinessLogic.Identity.Dtos.Identity;
+
+namespace Idsrv4.Admin.Api.Configuration.ApplicationParts;
+
+public class GenericTypeControllerFeatureProvider : IApplicationFeatureProvider
+ where TUserDto : UserDto, new()
+ where TRoleDto : RoleDto, new()
+ where TUser : IdentityUser
+ where TRole : IdentityRole
+ where TKey : IEquatable
+ where TUserClaim : IdentityUserClaim
+ where TUserRole : IdentityUserRole
+ where TUserLogin : IdentityUserLogin
+ where TRoleClaim : IdentityRoleClaim
+ where TUserToken : IdentityUserToken
+ where TUsersDto : UsersDto
+ where TRolesDto : RolesDto
+ where TUserRolesDto : UserRolesDto
+ where TUserClaimsDto : UserClaimsDto
+ where TUserProviderDto : UserProviderDto
+ where TUserProvidersDto : UserProvidersDto
+ where TUserChangePasswordDto : UserChangePasswordDto
+ where TRoleClaimsDto : RoleClaimsDto
+ where TUserClaimDto : UserClaimDto
+ where TRoleClaimDto : RoleClaimDto
+{
+ public void PopulateFeature(IEnumerable parts, ControllerFeature feature)
+ {
+ var currentAssembly =
+ typeof(GenericTypeControllerFeatureProvider).Assembly;
+ var controllerTypes = currentAssembly.GetExportedTypes()
+ .Where(t => typeof(ControllerBase).IsAssignableFrom(t) && t.IsGenericTypeDefinition)
+ .Select(t => t.GetTypeInfo());
+
+ var type = GetType();
+ var genericType = type.GetGenericTypeDefinition().GetTypeInfo();
+ var parameters = genericType.GenericTypeParameters
+ .Select((p, i) => new { p.Name, Index = i })
+ .ToDictionary(a => a.Name, a => type.GenericTypeArguments[a.Index]);
+
+ foreach (var controllerType in controllerTypes)
+ {
+ var typeArguments = controllerType.GenericTypeParameters
+ .Select(p => parameters[p.Name])
+ .ToArray();
+
+ feature.Controllers.Add(controllerType.MakeGenericType(typeArguments).GetTypeInfo());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditAction.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditAction.cs
new file mode 100644
index 0000000000000000000000000000000000000000..097f751993d035ee2ff2698f25b5ca7e323223b8
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditAction.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Idsrv4.Admin.AuditLogging.Events;
+
+namespace Idsrv4.Admin.Api.AuditLogging;
+
+public class ApiAuditAction : IAuditAction
+{
+ public ApiAuditAction(IHttpContextAccessor accessor)
+ {
+ Action = new
+ {
+ accessor.HttpContext.TraceIdentifier,
+ RequestUrl = accessor.HttpContext.Request.GetDisplayUrl(),
+ HttpMethod = accessor.HttpContext.Request.Method
+ };
+ }
+
+ public object Action { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditSubject.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditSubject.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b521ef7d0071d8aeec6397986b70ce495c6b9d45
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/ApiAuditSubject.cs
@@ -0,0 +1,36 @@
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Idsrv4.Admin.Api.Configuration;
+using Idsrv4.Admin.AuditLogging.Constants;
+using Idsrv4.Admin.AuditLogging.Events;
+
+namespace Idsrv4.Admin.Api.AuditLogging;
+
+public class ApiAuditSubject : IAuditSubject
+{
+ public ApiAuditSubject(IHttpContextAccessor accessor, AuditLoggingConfiguration auditLoggingConfiguration)
+ {
+ var subClaim = accessor.HttpContext.User.FindFirst(auditLoggingConfiguration.SubjectIdentifierClaim);
+ var nameClaim = accessor.HttpContext.User.FindFirst(auditLoggingConfiguration.SubjectNameClaim);
+ var clientIdClaim = accessor.HttpContext.User.FindFirst(auditLoggingConfiguration.ClientIdClaim);
+
+ SubjectIdentifier = subClaim == null ? clientIdClaim.Value : subClaim.Value;
+ SubjectName = subClaim == null ? clientIdClaim.Value : nameClaim?.Value;
+ SubjectType = subClaim == null ? AuditSubjectTypes.Machine : AuditSubjectTypes.User;
+
+ SubjectAdditionalData = new
+ {
+ RemoteIpAddress = accessor.HttpContext.Connection?.RemoteIpAddress?.ToString(),
+ LocalIpAddress = accessor.HttpContext.Connection?.LocalIpAddress?.ToString(),
+ Claims = accessor.HttpContext.User.Claims?.Select(x => new { x.Type, x.Value })
+ };
+ }
+
+ public string SubjectName { get; set; }
+
+ public string SubjectType { get; set; }
+
+ public object SubjectAdditionalData { get; set; }
+
+ public string SubjectIdentifier { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/AuditLoggingConfiguration.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/AuditLoggingConfiguration.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bb4ff76f7153db641b39daa0dd7922628f7ca535
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/AuditLogging/AuditLoggingConfiguration.cs
@@ -0,0 +1,12 @@
+namespace Idsrv4.Admin.Api.Configuration;
+
+public class AuditLoggingConfiguration
+{
+ public string Source { get; set; }
+
+ public string SubjectIdentifierClaim { get; set; }
+
+ public string SubjectNameClaim { get; set; }
+
+ public string ClientIdClaim { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Authorization/AuthorizeCheckOperationFilter.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Authorization/AuthorizeCheckOperationFilter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..979676d9eae7fb0a9179e3480ae0e4d1d855ef28
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Authorization/AuthorizeCheckOperationFilter.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Idsrv4.Admin.Api.Configuration.Authorization;
+
+public class AuthorizeCheckOperationFilter : IOperationFilter
+{
+ private readonly AdminApiConfiguration _adminApiConfiguration;
+
+ public AuthorizeCheckOperationFilter(AdminApiConfiguration adminApiConfiguration)
+ {
+ _adminApiConfiguration = adminApiConfiguration;
+ }
+
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ var hasAuthorize = context.MethodInfo.DeclaringType != null &&
+ (context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType()
+ .Any()
+ || context.MethodInfo.GetCustomAttributes(true).OfType().Any());
+
+ if (hasAuthorize)
+ {
+ operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
+ operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
+
+ operation.Security = new List
+ {
+ new()
+ {
+ [
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "oauth2"
+ }
+ }
+ ] = new[] { _adminApiConfiguration.OidcApiName }
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/AuthorizationConsts.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/AuthorizationConsts.cs
new file mode 100644
index 0000000000000000000000000000000000000000..37e436e7290058b8da13ddd79be101d28f922260
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/AuthorizationConsts.cs
@@ -0,0 +1,6 @@
+namespace Idsrv4.Admin.Api.Configuration.Constants;
+
+public class AuthorizationConsts
+{
+ public const string AdministrationPolicy = "RequireAdministratorRole";
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/ConfigurationConsts.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/ConfigurationConsts.cs
new file mode 100644
index 0000000000000000000000000000000000000000..84f01d276c9489c59b2f7dbe0fde486dc92ea15a
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Constants/ConfigurationConsts.cs
@@ -0,0 +1,18 @@
+namespace Idsrv4.Admin.Api.Configuration.Constants;
+
+public class ConfigurationConsts
+{
+ public const string ConfigurationDbConnectionStringKey = "ConfigurationDbConnection";
+
+ public const string PersistedGrantDbConnectionStringKey = "PersistedGrantDbConnection";
+
+ public const string IdentityDbConnectionStringKey = "IdentityDbConnection";
+
+ public const string AdminLogDbConnectionStringKey = "AdminLogDbConnection";
+
+ public const string AdminAuditLogDbConnectionStringKey = "AdminAuditLogDbConnection";
+
+ public const string DataProtectionDbConnectionStringKey = "DataProtectionDbConnection";
+
+ public const string ResourcesPath = "Resources";
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Test/StartupTest.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Test/StartupTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..60887730a4699f067e0e42b6c969bcfddb04c779
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Configuration/Test/StartupTest.cs
@@ -0,0 +1,196 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Identity;
+using Idsrv4.Admin.Api.Middlewares;
+namespace Idsrv4.Admin.Api.Configuration.Test;
+
+public class Startup
+{
+ public Startup(IWebHostEnvironment env, IConfiguration configuration)
+ {
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
+ HostingEnvironment = env;
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ public IWebHostEnvironment HostingEnvironment { get; }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ var adminApiConfiguration =
+ Configuration.GetSection(nameof(AdminApiConfiguration)).Get();
+ services.AddSingleton(adminApiConfiguration);
+
+ // Add DbContexts
+ RegisterDbContexts(services);
+
+ services.AddDataProtection();
+
+ // Add email senders which is currently setup for SendGrid and SMTP
+ services.AddEmailSenders(Configuration);
+
+ services.AddScoped();
+ services.AddScoped();
+
+ // Add authentication services
+ RegisterAuthentication(services);
+
+ // Add authorization services
+ RegisterAuthorization(services);
+
+ var profileTypes = new HashSet
+ {
+ typeof(IdentityMapperProfile)
+ };
+
+ services.AddAdminAspNetIdentityServices(profileTypes);
+
+ services
+ .AddAdminServices();
+
+ services.AddAdminApiCors(adminApiConfiguration);
+
+ services.AddMvcServices();
+
+ services.AddSwaggerGen(options =>
+ {
+ options.SwaggerDoc(adminApiConfiguration.ApiVersion,
+ new OpenApiInfo { Title = adminApiConfiguration.ApiName, Version = adminApiConfiguration.ApiVersion });
+
+ options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.OAuth2,
+ Flows = new OpenApiOAuthFlows
+ {
+ AuthorizationCode = new OpenApiOAuthFlow
+ {
+ AuthorizationUrl = new Uri($"{adminApiConfiguration.IdentityServerBaseUrl}/connect/authorize"),
+ TokenUrl = new Uri($"{adminApiConfiguration.IdentityServerBaseUrl}/connect/token"),
+ Scopes = new Dictionary
+ {
+ { adminApiConfiguration.OidcApiName, adminApiConfiguration.ApiName }
+ }
+ }
+ }
+ });
+ options.OperationFilter();
+ });
+
+ services.AddAuditEventLogging(Configuration);
+
+ services
+ .AddIdSHealthChecks(Configuration, adminApiConfiguration);
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, AdminApiConfiguration adminApiConfiguration)
+ {
+ app.AddForwardHeaders();
+
+ if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
+
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint($"{adminApiConfiguration.ApiBaseUrl}/swagger/v1/swagger.json",
+ adminApiConfiguration.ApiName);
+
+ c.OAuthClientId(adminApiConfiguration.OidcSwaggerUIClientId);
+ c.OAuthAppName(adminApiConfiguration.ApiName);
+ c.OAuthUsePkce();
+ });
+
+ app.UseRouting();
+ UseAuthentication(app);
+ app.UseCors();
+ app.UseAuthorization();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+
+ endpoints.MapHealthChecks("/health", new HealthCheckOptions
+ {
+ ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
+ });
+ });
+ }
+
+ public virtual void RegisterDbContexts(IServiceCollection services)
+ {
+ services
+ .AddDbContexts(Configuration);
+ }
+
+ public virtual void RegisterAuthentication(IServiceCollection services)
+ {
+ services.AddApiAuthentication(Configuration);
+ }
+
+ public virtual void RegisterAuthorization(IServiceCollection services)
+ {
+ services.AddAuthorizationPolicies();
+ }
+
+ public virtual void UseAuthentication(IApplicationBuilder app)
+ {
+ app.UseAuthentication();
+ }
+}
+
+public class StartupTest(IWebHostEnvironment env, IConfiguration configuration) : Startup(env, configuration)
+{
+ public override void RegisterDbContexts(IServiceCollection services)
+ {
+ services
+ .RegisterDbContextsStaging();
+ }
+
+ public override void RegisterAuthentication(IServiceCollection services)
+ {
+ services
+ .AddIdentity(options
+ => Configuration.GetSection(nameof(IdentityOptions)).Bind(options))
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders();
+
+ services.AddAuthentication(options =>
+ {
+ options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
+ }).AddCookie(JwtBearerDefaults.AuthenticationScheme);
+ }
+
+ public override void RegisterAuthorization(IServiceCollection services)
+ {
+ services.AddAuthorizationPolicies();
+ }
+
+ public override void UseAuthentication(IApplicationBuilder app)
+ {
+ app.UseAuthentication();
+ app.UseMiddleware();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiResourcesController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiResourcesController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aacbee6b3d1f37a9c338562fa1feecdc84dfb25f
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiResourcesController.cs
@@ -0,0 +1,175 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.ApiResources;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Mappers;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Dtos.Configuration;
+using Idsrv4.Admin.BusinessLogic.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class ApiResourcesController : ControllerBase
+{
+ private readonly IApiResourceService _apiResourceService;
+ private readonly IApiErrorResources _errorResources;
+
+ public ApiResourcesController(IApiResourceService apiResourceService, IApiErrorResources errorResources)
+ {
+ _apiResourceService = apiResourceService;
+ _errorResources = errorResources;
+ }
+
+ [HttpGet]
+ public async Task> Get(string searchText, int page = 1, int pageSize = 10)
+ {
+ var apiResourcesDto = await _apiResourceService.GetApiResourcesAsync(searchText, page, pageSize);
+ var apiResourcesApiDto = apiResourcesDto.ToApiResourceApiModel();
+
+ return Ok(apiResourcesApiDto);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(int id)
+ {
+ var apiResourceDto = await _apiResourceService.GetApiResourceAsync(id);
+ var apiResourceApiDto = apiResourceDto.ToApiResourceApiModel();
+
+ return Ok(apiResourceApiDto);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task Post([FromBody] ApiResourceApiDto apiResourceApi)
+ {
+ var apiResourceDto = apiResourceApi.ToApiResourceApiModel();
+
+ if (!apiResourceDto.Id.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var apiResourceId = await _apiResourceService.AddApiResourceAsync(apiResourceDto);
+ apiResourceApi.Id = apiResourceId;
+
+ return CreatedAtAction(nameof(Get), new { id = apiResourceId }, apiResourceApi);
+ }
+
+ [HttpPut]
+ public async Task Put([FromBody] ApiResourceApiDto apiResourceApi)
+ {
+ var apiResourceDto = apiResourceApi.ToApiResourceApiModel();
+
+ await _apiResourceService.GetApiResourceAsync(apiResourceDto.Id);
+ await _apiResourceService.UpdateApiResourceAsync(apiResourceDto);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id)
+ {
+ var apiResourceDto = new ApiResourceDto { Id = id };
+
+ await _apiResourceService.GetApiResourceAsync(apiResourceDto.Id);
+ await _apiResourceService.DeleteApiResourceAsync(apiResourceDto);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Secrets")]
+ public async Task> GetSecrets(int id, int page = 1, int pageSize = 10)
+ {
+ var apiSecretsDto = await _apiResourceService.GetApiSecretsAsync(id, page, pageSize);
+ var apiSecretsApiDto = apiSecretsDto.ToApiResourceApiModel();
+
+ return Ok(apiSecretsApiDto);
+ }
+
+ [HttpGet("Secrets/{secretId}")]
+ public async Task> GetSecret(int secretId)
+ {
+ var apiSecretsDto = await _apiResourceService.GetApiSecretAsync(secretId);
+ var apiSecretApiDto = apiSecretsDto.ToApiResourceApiModel();
+
+ return Ok(apiSecretApiDto);
+ }
+
+ [HttpPost("{id}/Secrets")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostSecret(int id, [FromBody] ApiSecretApiDto clientSecretApi)
+ {
+ var secretsDto = clientSecretApi.ToApiResourceApiModel();
+ secretsDto.ApiResourceId = id;
+
+ if (!secretsDto.ApiSecretId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var secretId = await _apiResourceService.AddApiSecretAsync(secretsDto);
+ clientSecretApi.Id = secretId;
+
+ return CreatedAtAction(nameof(GetSecret), new { secretId }, clientSecretApi);
+ }
+
+ [HttpDelete("Secrets/{secretId}")]
+ public async Task DeleteSecret(int secretId)
+ {
+ var apiSecret = new ApiSecretsDto { ApiSecretId = secretId };
+
+ await _apiResourceService.GetApiSecretAsync(apiSecret.ApiSecretId);
+ await _apiResourceService.DeleteApiSecretAsync(apiSecret);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Properties")]
+ public async Task> GetProperties(int id, int page = 1, int pageSize = 10)
+ {
+ var apiResourcePropertiesDto = await _apiResourceService.GetApiResourcePropertiesAsync(id, page, pageSize);
+ var apiResourcePropertiesApiDto = apiResourcePropertiesDto.ToApiResourceApiModel();
+
+ return Ok(apiResourcePropertiesApiDto);
+ }
+
+ [HttpGet("Properties/{propertyId}")]
+ public async Task> GetProperty(int propertyId)
+ {
+ var apiResourcePropertiesDto = await _apiResourceService.GetApiResourcePropertyAsync(propertyId);
+ var apiResourcePropertyApiDto = apiResourcePropertiesDto.ToApiResourceApiModel();
+
+ return Ok(apiResourcePropertyApiDto);
+ }
+
+ [HttpPost("{id}/Properties")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostProperty(int id, [FromBody] ApiResourcePropertyApiDto apiPropertyApi)
+ {
+ var apiResourcePropertiesDto = apiPropertyApi.ToApiResourceApiModel();
+ apiResourcePropertiesDto.ApiResourceId = id;
+
+ if (!apiResourcePropertiesDto.ApiResourcePropertyId.Equals(default))
+ return BadRequest(_errorResources.CannotSetId());
+
+ var propertyId = await _apiResourceService.AddApiResourcePropertyAsync(apiResourcePropertiesDto);
+ apiPropertyApi.Id = propertyId;
+
+ return CreatedAtAction(nameof(GetProperty), new { propertyId }, apiPropertyApi);
+ }
+
+ [HttpDelete("Properties/{propertyId}")]
+ public async Task DeleteProperty(int propertyId)
+ {
+ var apiResourceProperty = new ApiResourcePropertiesDto { ApiResourcePropertyId = propertyId };
+
+ await _apiResourceService.GetApiResourcePropertyAsync(apiResourceProperty.ApiResourcePropertyId);
+ await _apiResourceService.DeleteApiResourcePropertyAsync(apiResourceProperty);
+
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiScopesController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiScopesController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3bfa5691c07e2554525b5aeb11913abe2bb2adf9
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ApiScopesController.cs
@@ -0,0 +1,133 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.ApiScopes;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Mappers;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Dtos.Configuration;
+using Idsrv4.Admin.BusinessLogic.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class ApiScopesController : ControllerBase
+{
+ private readonly IApiScopeService _apiScopeService;
+ private readonly IApiErrorResources _errorResources;
+
+ public ApiScopesController(IApiErrorResources errorResources, IApiScopeService apiScopeService)
+ {
+ _errorResources = errorResources;
+ _apiScopeService = apiScopeService;
+ }
+
+ [HttpGet]
+ public async Task> GetScopes(string search, int page = 1, int pageSize = 10)
+ {
+ var apiScopesDto = await _apiScopeService.GetApiScopesAsync(search, page, pageSize);
+ var apiScopesApiDto = apiScopesDto.ToApiScopeApiModel();
+
+ return Ok(apiScopesApiDto);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetScope(int id)
+ {
+ var apiScopesDto = await _apiScopeService.GetApiScopeAsync(id);
+ var apiScopeApiDto = apiScopesDto.ToApiScopeApiModel();
+
+ return Ok(apiScopeApiDto);
+ }
+
+ [HttpGet("{id}/Properties")]
+ public async Task> GetScopeProperties(int id, int page = 1,
+ int pageSize = 10)
+ {
+ var apiScopePropertiesDto = await _apiScopeService.GetApiScopePropertiesAsync(id, page, pageSize);
+ var apiScopePropertiesApiDto = apiScopePropertiesDto.ToApiScopeApiModel();
+
+ return Ok(apiScopePropertiesApiDto);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostScope([FromBody] ApiScopeApiDto apiScopeApi)
+ {
+ var apiScope = apiScopeApi.ToApiScopeApiModel();
+
+ if (!apiScope.Id.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var id = await _apiScopeService.AddApiScopeAsync(apiScope);
+ apiScope.Id = id;
+
+ return CreatedAtAction(nameof(GetScope), new { scopeId = id }, apiScope);
+ }
+
+ [HttpPost("{id}/Properties")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostProperty(int id, [FromBody] ApiScopePropertyApiDto apiScopePropertyApi)
+ {
+ var apiResourcePropertiesDto = apiScopePropertyApi.ToApiScopeApiModel();
+ apiResourcePropertiesDto.ApiScopeId = id;
+
+ if (!apiResourcePropertiesDto.ApiScopePropertyId.Equals(default))
+ return BadRequest(_errorResources.CannotSetId());
+
+ var propertyId = await _apiScopeService.AddApiScopePropertyAsync(apiResourcePropertiesDto);
+ apiScopePropertyApi.Id = propertyId;
+
+ return CreatedAtAction(nameof(GetProperty), new { propertyId }, apiScopePropertyApi);
+ }
+
+ [HttpGet("Properties/{propertyId}")]
+ public async Task> GetProperty(int propertyId)
+ {
+ var apiScopePropertyAsync = await _apiScopeService.GetApiScopePropertyAsync(propertyId);
+ var resourcePropertyApiDto = apiScopePropertyAsync.ToApiScopeApiModel();
+
+ return Ok(resourcePropertyApiDto);
+ }
+
+ [HttpDelete("Properties/{propertyId}")]
+ public async Task DeleteProperty(int propertyId)
+ {
+ var apiScopePropertiesDto = new ApiScopePropertiesDto { ApiScopePropertyId = propertyId };
+
+ await _apiScopeService.GetApiScopePropertyAsync(apiScopePropertiesDto.ApiScopePropertyId);
+ await _apiScopeService.DeleteApiScopePropertyAsync(apiScopePropertiesDto);
+
+ return Ok();
+ }
+
+ [HttpPut]
+ public async Task PutScope([FromBody] ApiScopeApiDto apiScopeApi)
+ {
+ var apiScope = apiScopeApi.ToApiScopeApiModel();
+
+ await _apiScopeService.GetApiScopeAsync(apiScope.Id);
+
+ await _apiScopeService.UpdateApiScopeAsync(apiScope);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task DeleteScope(int id)
+ {
+ var apiScope = new ApiScopeDto { Id = id };
+
+ await _apiScopeService.GetApiScopeAsync(apiScope.Id);
+
+ await _apiScopeService.DeleteApiScopeAsync(apiScope);
+
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ClientsController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ClientsController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..068bc8c7d76ecb470fa57f905cfc5c3a430712df
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/ClientsController.cs
@@ -0,0 +1,233 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.Clients;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Mappers;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Dtos.Configuration;
+using Idsrv4.Admin.BusinessLogic.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class ClientsController : ControllerBase
+{
+ private readonly IClientService _clientService;
+ private readonly IApiErrorResources _errorResources;
+
+ public ClientsController(IClientService clientService, IApiErrorResources errorResources)
+ {
+ _clientService = clientService;
+ _errorResources = errorResources;
+ }
+
+ [HttpGet]
+ public async Task> Get(string searchText, int page = 1, int pageSize = 10)
+ {
+ var clientsDto = await _clientService.GetClientsAsync(searchText, page, pageSize);
+ var clientsApiDto = clientsDto.ToClientApiModel();
+
+ return Ok(clientsApiDto);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(int id)
+ {
+ var clientDto = await _clientService.GetClientAsync(id);
+ var clientApiDto = clientDto.ToClientApiModel();
+
+ return Ok(clientApiDto);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task Post([FromBody] ClientApiDto client)
+ {
+ var clientDto = client.ToClientApiModel();
+
+ if (!clientDto.Id.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var id = await _clientService.AddClientAsync(clientDto);
+ client.Id = id;
+
+ return CreatedAtAction(nameof(Get), new { id }, client);
+ }
+
+ [HttpPut]
+ public async Task Put([FromBody] ClientApiDto client)
+ {
+ var clientDto = client.ToClientApiModel();
+
+ await _clientService.GetClientAsync(clientDto.Id);
+ await _clientService.UpdateClientAsync(clientDto, true, true);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id)
+ {
+ var clientDto = new ClientDto { Id = id };
+
+ await _clientService.GetClientAsync(clientDto.Id);
+ await _clientService.RemoveClientAsync(clientDto);
+
+ return Ok();
+ }
+
+ [HttpPost("Clone")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostClientClone([FromBody] ClientCloneApiDto client)
+ {
+ var clientCloneDto = client.ToClientApiModel();
+
+ var originalClient = await _clientService.GetClientAsync(clientCloneDto.Id);
+ var id = await _clientService.CloneClientAsync(clientCloneDto);
+ originalClient.Id = id;
+
+ return CreatedAtAction(nameof(Get), new { id }, originalClient);
+ }
+
+ [HttpGet("{id}/Secrets")]
+ public async Task> GetSecrets(int id, int page = 1, int pageSize = 10)
+ {
+ var clientSecretsDto = await _clientService.GetClientSecretsAsync(id, page, pageSize);
+ var clientSecretsApiDto = clientSecretsDto.ToClientApiModel();
+
+ return Ok(clientSecretsApiDto);
+ }
+
+ [HttpGet("Secrets/{secretId}")]
+ public async Task> GetSecret(int secretId)
+ {
+ var clientSecretsDto = await _clientService.GetClientSecretAsync(secretId);
+ var clientSecretDto = clientSecretsDto.ToClientApiModel();
+
+ return Ok(clientSecretDto);
+ }
+
+ [HttpPost("{id}/Secrets")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostSecret(int id, [FromBody] ClientSecretApiDto clientSecretApi)
+ {
+ var secretsDto = clientSecretApi.ToClientApiModel();
+ secretsDto.ClientId = id;
+
+ if (!secretsDto.ClientSecretId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var secretId = await _clientService.AddClientSecretAsync(secretsDto);
+ clientSecretApi.Id = secretId;
+
+ return CreatedAtAction(nameof(GetSecret), new { secretId }, clientSecretApi);
+ }
+
+ [HttpDelete("Secrets/{secretId}")]
+ public async Task DeleteSecret(int secretId)
+ {
+ var clientSecret = new ClientSecretsDto { ClientSecretId = secretId };
+
+ await _clientService.GetClientSecretAsync(clientSecret.ClientSecretId);
+ await _clientService.DeleteClientSecretAsync(clientSecret);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Properties")]
+ public async Task> GetProperties(int id, int page = 1, int pageSize = 10)
+ {
+ var clientPropertiesDto = await _clientService.GetClientPropertiesAsync(id, page, pageSize);
+ var clientPropertiesApiDto = clientPropertiesDto.ToClientApiModel();
+
+ return Ok(clientPropertiesApiDto);
+ }
+
+ [HttpGet("Properties/{propertyId}")]
+ public async Task> GetProperty(int propertyId)
+ {
+ var clientPropertiesDto = await _clientService.GetClientPropertyAsync(propertyId);
+ var clientPropertyApiDto = clientPropertiesDto.ToClientApiModel();
+
+ return Ok(clientPropertyApiDto);
+ }
+
+ [HttpPost("{id}/Properties")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostProperty(int id, [FromBody] ClientPropertyApiDto clientPropertyApi)
+ {
+ var clientPropertiesDto = clientPropertyApi.ToClientApiModel();
+ clientPropertiesDto.ClientId = id;
+
+ if (!clientPropertiesDto.ClientPropertyId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var propertyId = await _clientService.AddClientPropertyAsync(clientPropertiesDto);
+ clientPropertyApi.Id = propertyId;
+
+ return CreatedAtAction(nameof(GetProperty), new { propertyId }, clientPropertyApi);
+ }
+
+ [HttpDelete("Properties/{propertyId}")]
+ public async Task DeleteProperty(int propertyId)
+ {
+ var clientProperty = new ClientPropertiesDto { ClientPropertyId = propertyId };
+
+ await _clientService.GetClientPropertyAsync(clientProperty.ClientPropertyId);
+ await _clientService.DeleteClientPropertyAsync(clientProperty);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Claims")]
+ public async Task> GetClaims(int id, int page = 1, int pageSize = 10)
+ {
+ var clientClaimsDto = await _clientService.GetClientClaimsAsync(id, page, pageSize);
+ var clientClaimsApiDto = clientClaimsDto.ToClientApiModel();
+
+ return Ok(clientClaimsApiDto);
+ }
+
+ [HttpGet("Claims/{claimId}")]
+ public async Task> GetClaim(int claimId)
+ {
+ var clientClaimsDto = await _clientService.GetClientClaimAsync(claimId);
+ var clientClaimApiDto = clientClaimsDto.ToClientApiModel();
+
+ return Ok(clientClaimApiDto);
+ }
+
+ [HttpPost("{id}/Claims")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostClaim(int id, [FromBody] ClientClaimApiDto clientClaimApiDto)
+ {
+ var clientClaimsDto = clientClaimApiDto.ToClientApiModel();
+ clientClaimsDto.ClientId = id;
+
+ if (!clientClaimsDto.ClientClaimId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var claimId = await _clientService.AddClientClaimAsync(clientClaimsDto);
+ clientClaimApiDto.Id = claimId;
+
+ return CreatedAtAction(nameof(GetClaim), new { claimId }, clientClaimApiDto);
+ }
+
+ [HttpDelete("Claims/{claimId}")]
+ public async Task DeleteClaim(int claimId)
+ {
+ var clientClaimsDto = new ClientClaimsDto { ClientClaimId = claimId };
+
+ await _clientService.GetClientClaimAsync(claimId);
+ await _clientService.DeleteClientClaimAsync(clientClaimsDto);
+
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/IdentityResourcesController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/IdentityResourcesController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a4bb5f90d312578a6520d4ee7ff0d07974bba16
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/IdentityResourcesController.cs
@@ -0,0 +1,138 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.IdentityResources;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Mappers;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Dtos.Configuration;
+using Idsrv4.Admin.BusinessLogic.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class IdentityResourcesController : ControllerBase
+{
+ private readonly IApiErrorResources _errorResources;
+ private readonly IIdentityResourceService _identityResourceService;
+
+ public IdentityResourcesController(IIdentityResourceService identityResourceService,
+ IApiErrorResources errorResources)
+ {
+ _identityResourceService = identityResourceService;
+ _errorResources = errorResources;
+ }
+
+ [HttpGet]
+ public async Task> Get(string searchText, int page = 1, int pageSize = 10)
+ {
+ var identityResourcesDto = await _identityResourceService.GetIdentityResourcesAsync(searchText, page, pageSize);
+ var identityResourcesApiDto = identityResourcesDto.ToIdentityResourceApiModel();
+
+ return Ok(identityResourcesApiDto);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(int id)
+ {
+ var identityResourceDto = await _identityResourceService.GetIdentityResourceAsync(id);
+ var identityResourceApiModel = identityResourceDto.ToIdentityResourceApiModel();
+
+ return Ok(identityResourceApiModel);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task Post([FromBody] IdentityResourceApiDto identityResourceApi)
+ {
+ var identityResourceDto = identityResourceApi.ToIdentityResourceApiModel();
+
+ if (!identityResourceDto.Id.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ var id = await _identityResourceService.AddIdentityResourceAsync(identityResourceDto);
+ identityResourceApi.Id = id;
+
+ return CreatedAtAction(nameof(Get), new { id }, identityResourceApi);
+ }
+
+ [HttpPut]
+ public async Task Put([FromBody] IdentityResourceApiDto identityResourceApi)
+ {
+ var identityResource = identityResourceApi.ToIdentityResourceApiModel();
+
+ await _identityResourceService.GetIdentityResourceAsync(identityResource.Id);
+ await _identityResourceService.UpdateIdentityResourceAsync(identityResource);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id)
+ {
+ var identityResource = new IdentityResourceDto { Id = id };
+
+ await _identityResourceService.GetIdentityResourceAsync(identityResource.Id);
+ await _identityResourceService.DeleteIdentityResourceAsync(identityResource);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Properties")]
+ public async Task> GetProperties(int id, int page = 1,
+ int pageSize = 10)
+ {
+ var identityResourcePropertiesDto =
+ await _identityResourceService.GetIdentityResourcePropertiesAsync(id, page, pageSize);
+ var identityResourcePropertiesApiDto =
+ identityResourcePropertiesDto.ToIdentityResourceApiModel();
+
+ return Ok(identityResourcePropertiesApiDto);
+ }
+
+ [HttpGet("Properties/{propertyId}")]
+ public async Task> GetProperty(int propertyId)
+ {
+ var identityResourcePropertiesDto = await _identityResourceService.GetIdentityResourcePropertyAsync(propertyId);
+ var identityResourcePropertyApiDto =
+ identityResourcePropertiesDto.ToIdentityResourceApiModel();
+
+ return Ok(identityResourcePropertyApiDto);
+ }
+
+ [HttpPost("{id}/Properties")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task PostProperty(int id,
+ [FromBody] IdentityResourcePropertyApiDto identityResourcePropertyApi)
+ {
+ var identityResourcePropertiesDto =
+ identityResourcePropertyApi.ToIdentityResourceApiModel();
+ identityResourcePropertiesDto.IdentityResourceId = id;
+
+ if (!identityResourcePropertiesDto.IdentityResourcePropertyId.Equals(default))
+ return BadRequest(_errorResources.CannotSetId());
+
+ var propertyId = await _identityResourceService.AddIdentityResourcePropertyAsync(identityResourcePropertiesDto);
+ identityResourcePropertyApi.Id = propertyId;
+
+ return CreatedAtAction(nameof(GetProperty), new { propertyId }, identityResourcePropertyApi);
+ }
+
+ [HttpDelete("Properties/{propertyId}")]
+ public async Task DeleteProperty(int propertyId)
+ {
+ var identityResourceProperty = new IdentityResourcePropertiesDto { IdentityResourcePropertyId = propertyId };
+
+ await _identityResourceService.GetIdentityResourcePropertyAsync(identityResourceProperty
+ .IdentityResourcePropertyId);
+ await _identityResourceService.DeleteIdentityResourcePropertyAsync(identityResourceProperty);
+
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/PersistedGrantsController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/PersistedGrantsController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..198519aa7fff55bd6ba4fb29e84b814969e317a4
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/PersistedGrantsController.cs
@@ -0,0 +1,88 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.PersistedGrants;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Helpers;
+using Idsrv4.Admin.Api.Mappers;
+using Idsrv4.Admin.BusinessLogic.Identity.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class PersistedGrantsController : ControllerBase
+{
+ private readonly IPersistedGrantAspNetIdentityService _persistedGrantsService;
+
+ public PersistedGrantsController(IPersistedGrantAspNetIdentityService persistedGrantsService)
+ {
+ _persistedGrantsService = persistedGrantsService;
+ }
+
+ [HttpGet("Subjects")]
+ public async Task> Get(string searchText, int page = 1,
+ int pageSize = 10)
+ {
+ var persistedGrantsDto =
+ await _persistedGrantsService.GetPersistedGrantsByUsersAsync(searchText, page, pageSize);
+ var persistedGrantSubjectsApiDto = persistedGrantsDto.ToPersistedGrantApiModel();
+
+ return Ok(persistedGrantSubjectsApiDto);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(string id)
+ {
+ var persistedGrantDto =
+ await _persistedGrantsService.GetPersistedGrantAsync(UrlHelpers.QueryStringUnSafeHash(id));
+ var persistedGrantApiDto = persistedGrantDto.ToPersistedGrantApiModel();
+
+ ParsePersistedGrantKey(persistedGrantApiDto);
+
+ return Ok(persistedGrantApiDto);
+ }
+
+ [HttpGet("Subjects/{subjectId}")]
+ public async Task> GetBySubject(string subjectId, int page = 1,
+ int pageSize = 10)
+ {
+ var persistedGrantDto = await _persistedGrantsService.GetPersistedGrantsByUserAsync(subjectId, page, pageSize);
+ var persistedGrantApiDto = persistedGrantDto.ToPersistedGrantApiModel();
+
+ ParsePersistedGrantKeys(persistedGrantApiDto);
+
+ return Ok(persistedGrantApiDto);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(string id)
+ {
+ await _persistedGrantsService.DeletePersistedGrantAsync(UrlHelpers.QueryStringUnSafeHash(id));
+
+ return Ok();
+ }
+
+ [HttpDelete("Subjects/{subjectId}")]
+ public async Task DeleteBySubject(string subjectId)
+ {
+ await _persistedGrantsService.DeletePersistedGrantsAsync(subjectId);
+
+ return Ok();
+ }
+
+ private void ParsePersistedGrantKey(PersistedGrantApiDto persistedGrantApiDto)
+ {
+ if (!string.IsNullOrEmpty(persistedGrantApiDto.Key))
+ persistedGrantApiDto.Key = UrlHelpers.QueryStringSafeHash(persistedGrantApiDto.Key);
+ }
+
+ private void ParsePersistedGrantKeys(PersistedGrantsApiDto persistedGrantApiDto)
+ {
+ persistedGrantApiDto.PersistedGrants.ForEach(ParsePersistedGrantKey);
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/RolesController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/RolesController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..24e5ec180bbaebd4d332bb63e6018c6f8167c404
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/RolesController.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.Roles;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Helpers.Localization;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Identity.Dtos.Identity;
+using Idsrv4.Admin.BusinessLogic.Identity.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class RolesController : ControllerBase
+ where TUserDto : UserDto, new()
+ where TRoleDto : RoleDto, new()
+ where TUser : IdentityUser
+ where TRole : IdentityRole
+ where TKey : IEquatable
+ where TUserClaim : IdentityUserClaim
+ where TUserRole : IdentityUserRole
+ where TUserLogin : IdentityUserLogin
+ where TRoleClaim : IdentityRoleClaim
+ where TUserToken : IdentityUserToken
+ where TUsersDto : UsersDto
+ where TRolesDto : RolesDto
+ where TUserRolesDto : UserRolesDto
+ where TUserClaimsDto : UserClaimsDto, new()
+ where TUserProviderDto : UserProviderDto
+ where TUserProvidersDto : UserProvidersDto
+ where TUserChangePasswordDto : UserChangePasswordDto
+ where TRoleClaimsDto : RoleClaimsDto, new()
+ where TUserClaimDto : UserClaimDto
+ where TRoleClaimDto : RoleClaimDto
+{
+ private readonly IApiErrorResources _errorResources;
+
+ private readonly IIdentityService
+ _identityService;
+
+ private readonly IGenericControllerLocalizer>
+ _localizer;
+
+ private readonly IMapper _mapper;
+
+ public RolesController(
+ IIdentityService
+ identityService,
+ IGenericControllerLocalizer>
+ localizer, IMapper mapper, IApiErrorResources errorResources)
+ {
+ _identityService = identityService;
+ _localizer = localizer;
+ _mapper = mapper;
+ _errorResources = errorResources;
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(TKey id)
+ {
+ var role = await _identityService.GetRoleAsync(id.ToString());
+
+ return Ok(role);
+ }
+
+ [HttpGet]
+ public async Task> Get(string searchText, int page = 1, int pageSize = 10)
+ {
+ var rolesDto = await _identityService.GetRolesAsync(searchText, page, pageSize);
+
+ return Ok(rolesDto);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task> Post([FromBody] TRoleDto role)
+ {
+ if (!EqualityComparer.Default.Equals(role.Id, default)) return BadRequest(_errorResources.CannotSetId());
+
+ var (identityResult, roleId) = await _identityService.CreateRoleAsync(role);
+ var createdRole = await _identityService.GetRoleAsync(roleId.ToString());
+
+ return CreatedAtAction(nameof(Get), new { id = createdRole.Id }, createdRole);
+ }
+
+ [HttpPut]
+ public async Task Put([FromBody] TRoleDto role)
+ {
+ await _identityService.GetRoleAsync(role.Id.ToString());
+ await _identityService.UpdateRoleAsync(role);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(TKey id)
+ {
+ var roleDto = new TRoleDto { Id = id };
+
+ await _identityService.GetRoleAsync(id.ToString());
+ await _identityService.DeleteRoleAsync(roleDto);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Users")]
+ public async Task> GetRoleUsers(string id, string searchText, int page = 1,
+ int pageSize = 10)
+ {
+ var usersDto = await _identityService.GetRoleUsersAsync(id, searchText, page, pageSize);
+
+ return Ok(usersDto);
+ }
+
+ [HttpGet("{id}/Claims")]
+ public async Task>> GetRoleClaims(string id, int page = 1, int pageSize = 10)
+ {
+ var roleClaimsDto = await _identityService.GetRoleClaimsAsync(id, page, pageSize);
+ var roleClaimsApiDto = _mapper.Map>(roleClaimsDto);
+
+ return Ok(roleClaimsApiDto);
+ }
+
+ [HttpPost("Claims")]
+ public async Task PostRoleClaims([FromBody] RoleClaimApiDto roleClaims)
+ {
+ var roleClaimsDto = _mapper.Map(roleClaims);
+
+ if (!roleClaimsDto.ClaimId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ await _identityService.CreateRoleClaimsAsync(roleClaimsDto);
+
+ return Ok();
+ }
+
+ [HttpPut("Claims")]
+ public async Task PutRoleClaims([FromBody] RoleClaimApiDto roleClaims)
+ {
+ var roleClaimsDto = _mapper.Map(roleClaims);
+
+ if (!roleClaimsDto.ClaimId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ await _identityService.UpdateRoleClaimsAsync(roleClaimsDto);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}/Claims")]
+ public async Task DeleteRoleClaims(TKey id, int claimId)
+ {
+ var roleDto = new TRoleClaimsDto { ClaimId = claimId, RoleId = id };
+
+ await _identityService.GetRoleClaimAsync(roleDto.RoleId.ToString(), roleDto.ClaimId);
+ await _identityService.DeleteRoleClaimAsync(roleDto);
+
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/UsersController.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/UsersController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..10c3e5ff46f50da9f523432f55ed1b3e4870acb8
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Controllers/UsersController.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using AutoMapper;
+using IdentityModel;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Idsrv4.Admin.Api.Configuration.Constants;
+using Idsrv4.Admin.Api.Dtos.Roles;
+using Idsrv4.Admin.Api.Dtos.Users;
+using Idsrv4.Admin.Api.ExceptionHandling;
+using Idsrv4.Admin.Api.Helpers.Localization;
+using Idsrv4.Admin.Api.Resources;
+using Idsrv4.Admin.BusinessLogic.Identity.Dtos.Identity;
+using Idsrv4.Admin.BusinessLogic.Identity.Services.Interfaces;
+
+namespace Idsrv4.Admin.Api.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+[TypeFilter(typeof(ControllerExceptionFilterAttribute))]
+[Produces("application/json", "application/problem+json")]
+[Authorize(Policy = AuthorizationConsts.AdministrationPolicy)]
+public class UsersController : ControllerBase
+ where TUserDto : UserDto, new()
+ where TRoleDto : RoleDto, new()
+ where TUser : IdentityUser
+ where TRole : IdentityRole
+ where TKey : IEquatable
+ where TUserClaim : IdentityUserClaim
+ where TUserRole : IdentityUserRole
+ where TUserLogin : IdentityUserLogin
+ where TRoleClaim : IdentityRoleClaim
+ where TUserToken : IdentityUserToken
+ where TUsersDto : UsersDto
+ where TRolesDto : RolesDto
+ where TUserRolesDto : UserRolesDto
+ where TUserClaimsDto : UserClaimsDto, new()
+ where TUserProviderDto : UserProviderDto
+ where TUserProvidersDto : UserProvidersDto
+ where TUserChangePasswordDto : UserChangePasswordDto
+ where TRoleClaimsDto : RoleClaimsDto
+ where TUserClaimDto : UserClaimDto
+ where TRoleClaimDto : RoleClaimDto
+{
+ private readonly IApiErrorResources _errorResources;
+
+ private readonly IIdentityService
+ _identityService;
+
+ private readonly IGenericControllerLocalizer>
+ _localizer;
+
+ private readonly IMapper _mapper;
+
+ public UsersController(
+ IIdentityService
+ identityService,
+ IGenericControllerLocalizer>
+ localizer, IMapper mapper, IApiErrorResources errorResources)
+ {
+ _identityService = identityService;
+ _localizer = localizer;
+ _mapper = mapper;
+ _errorResources = errorResources;
+ }
+
+ [HttpGet("{id}")]
+ public async Task> Get(TKey id)
+ {
+ var user = await _identityService.GetUserAsync(id.ToString());
+
+ return Ok(user);
+ }
+
+ [HttpGet]
+ public async Task> Get(string searchText, int page = 1, int pageSize = 10)
+ {
+ var usersDto = await _identityService.GetUsersAsync(searchText, page, pageSize);
+
+ return Ok(usersDto);
+ }
+
+ [HttpPost]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(400)]
+ public async Task> Post([FromBody] TUserDto user)
+ {
+ if (!EqualityComparer.Default.Equals(user.Id, default)) return BadRequest(_errorResources.CannotSetId());
+
+ var (identityResult, userId) = await _identityService.CreateUserAsync(user);
+ var createdUser = await _identityService.GetUserAsync(userId.ToString());
+
+ return CreatedAtAction(nameof(Get), new { id = createdUser.Id }, createdUser);
+ }
+
+ [HttpPut]
+ public async Task Put([FromBody] TUserDto user)
+ {
+ await _identityService.GetUserAsync(user.Id.ToString());
+ await _identityService.UpdateUserAsync(user);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(TKey id)
+ {
+ if (IsDeleteForbidden(id))
+ return StatusCode((int)HttpStatusCode.Forbidden);
+
+ var user = new TUserDto { Id = id };
+
+ await _identityService.GetUserAsync(user.Id.ToString());
+ await _identityService.DeleteUserAsync(user.Id.ToString(), user);
+
+ return Ok();
+ }
+
+ private bool IsDeleteForbidden(TKey id)
+ {
+ var userId = User.FindFirst(JwtClaimTypes.Subject);
+
+ return userId == null ? false : userId.Value == id.ToString();
+ }
+
+ [HttpGet("{id}/Roles")]
+ public async Task>> GetUserRoles(TKey id, int page = 1, int pageSize = 10)
+ {
+ var userRoles = await _identityService.GetUserRolesAsync(id.ToString(), page, pageSize);
+ var userRolesApiDto = _mapper.Map>(userRoles);
+
+ return Ok(userRolesApiDto);
+ }
+
+ [HttpPost("Roles")]
+ public async Task PostUserRoles([FromBody] UserRoleApiDto role)
+ {
+ var userRolesDto = _mapper.Map(role);
+
+ await _identityService.GetUserAsync(userRolesDto.UserId.ToString());
+ await _identityService.GetRoleAsync(userRolesDto.RoleId.ToString());
+
+ await _identityService.CreateUserRoleAsync(userRolesDto);
+
+ return Ok();
+ }
+
+ [HttpDelete("Roles")]
+ public async Task DeleteUserRoles([FromBody] UserRoleApiDto role)
+ {
+ var userRolesDto = _mapper.Map(role);
+
+ await _identityService.GetUserAsync(userRolesDto.UserId.ToString());
+ await _identityService.GetRoleAsync(userRolesDto.RoleId.ToString());
+
+ await _identityService.DeleteUserRoleAsync(userRolesDto);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Claims")]
+ public async Task>> GetUserClaims(TKey id, int page = 1, int pageSize = 10)
+ {
+ var claims = await _identityService.GetUserClaimsAsync(id.ToString(), page, pageSize);
+
+ var userClaimsApiDto = _mapper.Map>(claims);
+
+ return Ok(userClaimsApiDto);
+ }
+
+ [HttpPost("Claims")]
+ public async Task PostUserClaims([FromBody] UserClaimApiDto claim)
+ {
+ var userClaimDto = _mapper.Map(claim);
+
+ if (!userClaimDto.ClaimId.Equals(default)) return BadRequest(_errorResources.CannotSetId());
+
+ await _identityService.CreateUserClaimsAsync(userClaimDto);
+
+ return Ok();
+ }
+
+ [HttpPut("Claims")]
+ public async Task PutUserClaims([FromBody] UserClaimApiDto claim)
+ {
+ var userClaimDto = _mapper.Map(claim);
+
+ await _identityService.GetUserClaimAsync(userClaimDto.UserId.ToString(), userClaimDto.ClaimId);
+ await _identityService.UpdateUserClaimsAsync(userClaimDto);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}/Claims")]
+ public async Task DeleteUserClaims([FromRoute] TKey id, int claimId)
+ {
+ var userClaimsDto = new TUserClaimsDto
+ {
+ ClaimId = claimId,
+ UserId = id
+ };
+
+ await _identityService.GetUserClaimAsync(id.ToString(), claimId);
+ await _identityService.DeleteUserClaimAsync(userClaimsDto);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/Providers")]
+ public async Task>> GetUserProviders(TKey id)
+ {
+ var userProvidersDto = await _identityService.GetUserProvidersAsync(id.ToString());
+ var userProvidersApiDto = _mapper.Map>(userProvidersDto);
+
+ return Ok(userProvidersApiDto);
+ }
+
+ [HttpDelete("Providers")]
+ public async Task DeleteUserProviders([FromBody] UserProviderDeleteApiDto provider)
+ {
+ var providerDto = _mapper.Map(provider);
+
+ await _identityService.GetUserProviderAsync(providerDto.UserId.ToString(), providerDto.ProviderKey);
+ await _identityService.DeleteUserProvidersAsync(providerDto);
+
+ return Ok();
+ }
+
+ [HttpPost("ChangePassword")]
+ public async Task PostChangePassword([FromBody] UserChangePasswordApiDto password)
+ {
+ var userChangePasswordDto = _mapper.Map(password);
+
+ await _identityService.UserChangePasswordAsync(userChangePasswordDto);
+
+ return Ok();
+ }
+
+ [HttpGet("{id}/RoleClaims")]
+ public async Task>> GetRoleClaims(TKey id, string claimSearchText, int page = 1,
+ int pageSize = 10)
+ {
+ var roleClaimsDto =
+ await _identityService.GetUserRoleClaimsAsync(id.ToString(), claimSearchText, page, pageSize);
+ var roleClaimsApiDto = _mapper.Map>(roleClaimsDto);
+
+ return Ok(roleClaimsApiDto);
+ }
+
+ [HttpGet("ClaimType/{claimType}/ClaimValue/{claimValue}")]
+ public async Task> GetClaimUsers(string claimType, string claimValue, int page = 1,
+ int pageSize = 10)
+ {
+ var usersDto = await _identityService.GetClaimUsersAsync(claimType, claimValue, page, pageSize);
+
+ return Ok(usersDto);
+ }
+
+ [HttpGet("ClaimType/{claimType}")]
+ public async Task> GetClaimUsers(string claimType, int page = 1, int pageSize = 10)
+ {
+ var usersDto = await _identityService.GetClaimUsersAsync(claimType, null, page, pageSize);
+
+ return Ok(usersDto);
+ }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourceApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourceApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c60d11693ccdc04de6bceef588527f560497d4e3
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourceApiDto.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiResourceApiDto
+{
+ public ApiResourceApiDto()
+ {
+ UserClaims = new List();
+ Scopes = new List();
+ AllowedAccessTokenSigningAlgorithms = new List();
+ }
+
+ public int Id { get; set; }
+
+ [Required] public string Name { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public string Description { get; set; }
+
+ public bool Enabled { get; set; } = true;
+
+ public bool ShowInDiscoveryDocument { get; set; }
+
+ public List UserClaims { get; set; }
+
+ public List AllowedAccessTokenSigningAlgorithms { get; set; }
+
+ public List Scopes { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertiesApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertiesApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9b128eb7252be9bfd03a3022257f79538d990cd1
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertiesApiDto.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiResourcePropertiesApiDto
+{
+ public ApiResourcePropertiesApiDto()
+ {
+ ApiResourceProperties = new List();
+ }
+
+ public List ApiResourceProperties { get; set; }
+
+ public int TotalCount { get; set; }
+
+ public int PageSize { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertyApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertyApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..538014082ce2a16b59868bac0a61bc2a5dadf218
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcePropertyApiDto.cs
@@ -0,0 +1,8 @@
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiResourcePropertyApiDto
+{
+ public int Id { get; set; }
+ public string Key { get; set; }
+ public string Value { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcesApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcesApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b946a6f556a16ae4fa26a3108bd91382a6d2e640
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiResourcesApiDto.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiResourcesApiDto
+{
+ public ApiResourcesApiDto()
+ {
+ ApiResources = new List();
+ }
+
+ public int PageSize { get; set; }
+
+ public int TotalCount { get; set; }
+
+ public List ApiResources { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6028c71f0f99e8578dfd2442edb40c38881d1003
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretApiDto.cs
@@ -0,0 +1,17 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiSecretApiDto
+{
+ [Required] public string Type { get; set; } = "SharedSecret";
+
+ public int Id { get; set; }
+
+ public string Description { get; set; }
+
+ [Required] public string Value { get; set; }
+
+ public DateTime? Expiration { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretsApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretsApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9bb892f70c3491f8aaccbf21be4697e62c22aff5
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiResources/ApiSecretsApiDto.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiResources;
+
+public class ApiSecretsApiDto
+{
+ public ApiSecretsApiDto()
+ {
+ ApiSecrets = new List();
+ }
+
+ public int TotalCount { get; set; }
+
+ public int PageSize { get; set; }
+
+ public List ApiSecrets { get; set; }
+}
\ No newline at end of file
diff --git a/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiScopes/ApiScopeApiDto.cs b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiScopes/ApiScopeApiDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2d0ffd1fa4d7758e945504a476a2a4bbad9682ed
--- /dev/null
+++ b/src/IdentityServer4/src/Idsrv4.Admin.Api/Dtos/ApiScopes/ApiScopeApiDto.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace Idsrv4.Admin.Api.Dtos.ApiScopes;
+
+public class ApiScopeApiDto
+{
+ public ApiScopeApiDto()
+ {
+ UserClaims = new List