From 2043dbfce657be6afd389d21dc7edc58625bd7aa Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 6 Nov 2024 13:00:34 +0000 Subject: [PATCH] Local Passwords validation --- global.json | 1 - src/Yavsc.Abstract/Authentication/Scope.cs | 4 +- .../FileSystem/AbstractFileSystemHelpers.cs | 6 +- .../FileSystem/UserDirectoryInfo.cs | 9 +- src/Yavsc.Server/Models/Auth/Client.cs | 17 +- .../Models/Messaging/DimissClicked.cs | 4 +- .../Models/Relationship/Contact.cs | 8 +- .../Models/Relationship/StaticContact.cs | 12 + src/Yavsc.Server/Settings/SiteSettings.cs | 4 +- .../Account/ForgotPasswordViewModel.cs | 5 +- .../Account/ResetPasswordViewModel.cs | 12 +- .../ViewModels/Administration/RoleInfo.cs | 22 +- .../Blogspot/FileSystemApiController.cs | 2 +- .../DimissClicksApiController.cs | 50 +-- src/Yavsc/Config.cs | 3 +- .../Accounting/AccountController.cs | 356 +++++++++++++++++- .../AdministrationController.cs | 15 +- .../Controllers/Consent/ConsentController.cs | 265 +++++++++++++ .../Controllers/Device/DeviceController.cs | 233 ++++++++++++ .../Diagnostics/DiagnosticsController.cs | 30 ++ .../Diagnostics/DiagnosticsViewModel.cs | 32 ++ .../Diagnostics/LogoutViewModel.cs | 11 + src/Yavsc/Controllers/FileSystemController.cs | 2 +- src/Yavsc/Controllers/HomeController.cs | 4 +- .../Controllers/SecurityHeadersAttribute.cs | 56 +++ src/Yavsc/Extensions/ControlerExtensions.cs | 27 ++ .../{ => Extensions}/HostingExtensions.cs | 57 +-- src/Yavsc/Helpers/OAuthHelpers.cs | 4 +- src/Yavsc/Models/Access/AccountOptions.cs | 17 + src/Yavsc/Models/Access/ConsentInputModel.cs | 17 + src/Yavsc/Models/Access/ConsentViewModel.cs | 19 + .../Access/DeviceAuthorizationInputModel.cs | 11 + .../Access/DeviceAuthorizationViewModel.cs | 13 + src/Yavsc/Models/Access/GrantViewModel.cs | 19 + src/Yavsc/Models/Access/GrantsViewModel.cs | 11 + src/Yavsc/Models/Access/LoggedOutViewModel.cs | 19 + src/Yavsc/Models/Access/LogoutInputModel.cs | 11 + .../Models/Access/ProcessConsentResult.cs | 21 ++ src/Yavsc/Models/Access/RedirectViewModel.cs | 10 + src/Yavsc/Models/Access/ScopeViewModel.cs | 16 + src/Yavsc/Models/ApplicationDbContext.cs | 11 +- src/Yavsc/Models/ConsentOptions.cs | 16 + src/Yavsc/Pages/Account/AccessDenied.cshtml | 10 - .../Pages/Account/AccessDenied.cshtml.cs | 13 - .../Pages/Account/ForgotPassword/Index.cshtml | 26 -- .../Account/ForgotPassword/Index.cshtml.cs | 113 ------ src/Yavsc/Pages/Account/Login/Index.cshtml | 136 ------- src/Yavsc/Pages/Account/Login/Index.cshtml.cs | 216 ----------- src/Yavsc/Pages/Account/Login/InputModel.cs | 17 - src/Yavsc/Pages/Account/Login/LoginOptions.cs | 12 - src/Yavsc/Pages/Account/Login/ViewModel.cs | 28 -- .../Pages/Account/Logout/Index.cshtml.cs | 104 ----- .../Pages/Account/Logout/LoggedOut.cshtml | 30 -- .../Pages/Account/Logout/LoggedOut.cshtml.cs | 36 -- .../Account/Logout/LoggedOutViewModel.cs | 15 - .../Pages/Account/Logout/LogoutOptions.cs | 11 - src/Yavsc/Pages/Activity/Create.cshtml | 83 ---- src/Yavsc/Pages/Activity/Delete.cshtml | 40 -- src/Yavsc/Pages/Activity/Details.cshtml | 37 -- src/Yavsc/Pages/Activity/Edit.cshtml | 86 ----- src/Yavsc/Pages/Activity/Index.cshtml | 89 ----- src/Yavsc/Pages/Ciba/All.cshtml | 48 --- src/Yavsc/Pages/Ciba/All.cshtml.cs | 28 -- src/Yavsc/Pages/Ciba/Consent.cshtml | 98 ----- src/Yavsc/Pages/Ciba/Consent.cshtml.cs | 228 ----------- src/Yavsc/Pages/Ciba/ConsentOptions.cs | 14 - src/Yavsc/Pages/Ciba/Index.cshtml | 30 -- src/Yavsc/Pages/Ciba/Index.cshtml.cs | 42 --- src/Yavsc/Pages/Ciba/InputModel.cs | 12 - src/Yavsc/Pages/Ciba/ViewModel.cs | 34 -- src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml | 47 --- src/Yavsc/Pages/Consent/ConsentOptions.cs | 14 - src/Yavsc/Pages/Consent/Index.cshtml.cs | 236 ------------ src/Yavsc/Pages/Consent/InputModel.cs | 13 - src/Yavsc/Pages/Consent/ViewModel.cs | 33 -- src/Yavsc/Pages/Consent/_ScopeListItem.cshtml | 47 --- src/Yavsc/Pages/Device/DeviceOptions.cs | 15 - src/Yavsc/Pages/Device/Index.cshtml | 141 ------- src/Yavsc/Pages/Device/Index.cshtml.cs | 220 ----------- src/Yavsc/Pages/Device/InputModel.cs | 14 - src/Yavsc/Pages/Device/Success.cshtml.cs | 16 - src/Yavsc/Pages/Device/ViewModel.cs | 25 -- src/Yavsc/Pages/Diagnostics/Index.cshtml | 67 ---- src/Yavsc/Pages/Diagnostics/Index.cshtml.cs | 34 -- src/Yavsc/Pages/Diagnostics/ViewModel.cs | 32 -- src/Yavsc/Pages/Extensions.cs | 42 --- src/Yavsc/Pages/ExternalLogin/Callback.cshtml | 19 - .../Pages/ExternalLogin/Callback.cshtml.cs | 203 ---------- .../Pages/ExternalLogin/Challenge.cshtml | 19 - .../Pages/ExternalLogin/Challenge.cshtml.cs | 48 --- src/Yavsc/Pages/Grants/Index.cshtml.cs | 82 ---- src/Yavsc/Pages/Grants/ViewModel.cs | 22 -- src/Yavsc/Pages/Home/Error/Index.cshtml | 35 -- src/Yavsc/Pages/Home/Error/Index.cshtml.cs | 40 -- src/Yavsc/Pages/Home/Error/ViewModel.cs | 20 - src/Yavsc/Pages/IdentityServerSuppressions.cs | 22 -- src/Yavsc/Pages/Index.cshtml | 46 --- src/Yavsc/Pages/Index.cshtml.cs | 27 -- src/Yavsc/Pages/Log.cs | 87 ----- src/Yavsc/Pages/Redirect/Index.cshtml | 14 - src/Yavsc/Pages/Redirect/Index.cshtml.cs | 25 -- src/Yavsc/Pages/SecurityHeadersAttribute.cs | 57 --- .../Pages/ServerSideSessions/Index.cshtml | 147 -------- .../Pages/ServerSideSessions/Index.cshtml.cs | 67 ---- src/Yavsc/Pages/Shared/_Layout.cshtml | 29 -- src/Yavsc/Pages/Shared/_Nav.cshtml | 45 --- src/Yavsc/Pages/Telemetry.cs | 142 ------- src/Yavsc/Pages/_ViewImports.cshtml | 40 -- src/Yavsc/Pages/_ViewStart.cshtml | 3 - src/Yavsc/Program.cs | 11 +- src/Yavsc/Services/YavscClientStore.cs | 66 ++++ src/Yavsc/Startup.cs | 5 +- .../ViewComponents/DirectoryViewComponent.cs | 22 +- .../ViewModels/Account/ExternalProvider.cs | 8 + .../ViewModels/Account/LoginInputModel.cs | 14 + .../ViewModels/Account/LoginViewModel.cs | 15 +- .../ViewModels/Account/RedirectViewModel.cs | 7 + src/Yavsc/Views/Account/AccessDenied.cshtml | 25 +- src/Yavsc/Views/Account/AccountCreated.cshtml | 10 - .../Views/Account/AccountCreated.fr.cshtml | 11 - src/Yavsc/Views/Account/AdminDelete.cshtml | 14 - .../Account/AdminSendConfirationEmail.cshtml | 12 - src/Yavsc/Views/Account/Delete.cshtml | 14 - src/Yavsc/Views/Account/EmailConfirmed.cshtml | 15 - .../ExternalLoginConfirmation.en.cshtml | 42 --- .../ExternalLoginConfirmation.fr.cshtml | 42 --- .../Views/Account/ExternalLoginFailure.cshtml | 8 - src/Yavsc/Views/Account/ForgotPassword.cshtml | 33 +- .../Account/ForgotPasswordConfirmation.cshtml | 11 +- src/Yavsc/Views/Account/Index.cshtml | 6 - src/Yavsc/Views/Account/Lockout.cshtml | 8 - src/Yavsc/Views/Account/LoggedOut.cshtml | 34 ++ src/Yavsc/Views/Account/Login.cshtml | 147 +++++--- .../Account/Logout.cshtml} | 8 +- src/Yavsc/Views/Account/Register.cshtml | 54 --- src/Yavsc/Views/Account/Register.fr.cshtml | 54 --- src/Yavsc/Views/Account/ResetPassword.cshtml | 49 +-- .../Account/ResetPasswordConfirmation.cshtml | 8 - src/Yavsc/Views/Account/SendCode.cshtml | 21 -- .../Views/Account/SendConfirationEmail.cshtml | 13 - src/Yavsc/Views/Account/UserList.cshtml | 68 ---- src/Yavsc/Views/Account/VerifyCode.cshtml | 39 -- src/Yavsc/Views/Administration/Index.cshtml | 2 +- .../{Pages => Views}/Device/Success.cshtml | 5 - src/Yavsc/Views/Device/UserCodeCapture.cshtml | 23 ++ .../Device/UserCodeConfirmation.cshtml} | 49 +-- src/Yavsc/Views/Devices/Delete.cshtml | 46 --- src/Yavsc/Views/Devices/Details.cshtml | 42 --- src/Yavsc/Views/Devices/Index.cshtml | 46 --- src/Yavsc/Views/Diagnostics/Index.cshtml | 64 ++++ .../{Pages => Views}/Grants/Index.cshtml | 13 +- src/Yavsc/Views/Home/About.cshtml | 33 ++ src/Yavsc/Views/Home/AboutAccess.cshtml | 2 + src/Yavsc/Views/Home/Index.cshtml | 1 + src/Yavsc/Views/Shared/_Layout.cshtml | 8 +- src/Yavsc/Views/Shared/_LoginPartial.cshtml | 8 +- src/Yavsc/Views/Shared/_Nav.cshtml | 6 +- .../Shared}/_ScopeListItem.cshtml | 9 +- .../Shared/_ValidationSummary.cshtml | 0 src/Yavsc/Views/_ViewImports.cshtml | 3 + src/Yavsc/Yavsc.csproj | 4 +- src/Yavsc/wwwroot/css/site.scss | 17 + ...{signin-redirect.js => signin-redirect js} | 0 163 files changed, 1824 insertions(+), 4784 deletions(-) create mode 100644 src/Yavsc.Server/Models/Relationship/StaticContact.cs create mode 100644 src/Yavsc/Controllers/Consent/ConsentController.cs create mode 100644 src/Yavsc/Controllers/Device/DeviceController.cs create mode 100644 src/Yavsc/Controllers/Diagnostics/DiagnosticsController.cs create mode 100644 src/Yavsc/Controllers/Diagnostics/DiagnosticsViewModel.cs create mode 100644 src/Yavsc/Controllers/Diagnostics/LogoutViewModel.cs create mode 100644 src/Yavsc/Controllers/SecurityHeadersAttribute.cs create mode 100644 src/Yavsc/Extensions/ControlerExtensions.cs rename src/Yavsc/{ => Extensions}/HostingExtensions.cs (90%) create mode 100644 src/Yavsc/Models/Access/AccountOptions.cs create mode 100644 src/Yavsc/Models/Access/ConsentInputModel.cs create mode 100644 src/Yavsc/Models/Access/ConsentViewModel.cs create mode 100644 src/Yavsc/Models/Access/DeviceAuthorizationInputModel.cs create mode 100644 src/Yavsc/Models/Access/DeviceAuthorizationViewModel.cs create mode 100644 src/Yavsc/Models/Access/GrantViewModel.cs create mode 100644 src/Yavsc/Models/Access/GrantsViewModel.cs create mode 100644 src/Yavsc/Models/Access/LoggedOutViewModel.cs create mode 100644 src/Yavsc/Models/Access/LogoutInputModel.cs create mode 100644 src/Yavsc/Models/Access/ProcessConsentResult.cs create mode 100644 src/Yavsc/Models/Access/RedirectViewModel.cs create mode 100644 src/Yavsc/Models/Access/ScopeViewModel.cs create mode 100644 src/Yavsc/Models/ConsentOptions.cs delete mode 100644 src/Yavsc/Pages/Account/AccessDenied.cshtml delete mode 100644 src/Yavsc/Pages/Account/AccessDenied.cshtml.cs delete mode 100644 src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml delete mode 100644 src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Account/Login/Index.cshtml delete mode 100644 src/Yavsc/Pages/Account/Login/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Account/Login/InputModel.cs delete mode 100644 src/Yavsc/Pages/Account/Login/LoginOptions.cs delete mode 100644 src/Yavsc/Pages/Account/Login/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Account/Logout/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml delete mode 100644 src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml.cs delete mode 100644 src/Yavsc/Pages/Account/Logout/LoggedOutViewModel.cs delete mode 100644 src/Yavsc/Pages/Account/Logout/LogoutOptions.cs delete mode 100644 src/Yavsc/Pages/Activity/Create.cshtml delete mode 100644 src/Yavsc/Pages/Activity/Delete.cshtml delete mode 100644 src/Yavsc/Pages/Activity/Details.cshtml delete mode 100644 src/Yavsc/Pages/Activity/Edit.cshtml delete mode 100644 src/Yavsc/Pages/Activity/Index.cshtml delete mode 100644 src/Yavsc/Pages/Ciba/All.cshtml delete mode 100644 src/Yavsc/Pages/Ciba/All.cshtml.cs delete mode 100644 src/Yavsc/Pages/Ciba/Consent.cshtml delete mode 100644 src/Yavsc/Pages/Ciba/Consent.cshtml.cs delete mode 100644 src/Yavsc/Pages/Ciba/ConsentOptions.cs delete mode 100644 src/Yavsc/Pages/Ciba/Index.cshtml delete mode 100644 src/Yavsc/Pages/Ciba/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Ciba/InputModel.cs delete mode 100644 src/Yavsc/Pages/Ciba/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml delete mode 100644 src/Yavsc/Pages/Consent/ConsentOptions.cs delete mode 100644 src/Yavsc/Pages/Consent/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Consent/InputModel.cs delete mode 100644 src/Yavsc/Pages/Consent/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Consent/_ScopeListItem.cshtml delete mode 100644 src/Yavsc/Pages/Device/DeviceOptions.cs delete mode 100644 src/Yavsc/Pages/Device/Index.cshtml delete mode 100644 src/Yavsc/Pages/Device/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Device/InputModel.cs delete mode 100644 src/Yavsc/Pages/Device/Success.cshtml.cs delete mode 100644 src/Yavsc/Pages/Device/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Diagnostics/Index.cshtml delete mode 100644 src/Yavsc/Pages/Diagnostics/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Diagnostics/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Extensions.cs delete mode 100644 src/Yavsc/Pages/ExternalLogin/Callback.cshtml delete mode 100644 src/Yavsc/Pages/ExternalLogin/Callback.cshtml.cs delete mode 100644 src/Yavsc/Pages/ExternalLogin/Challenge.cshtml delete mode 100644 src/Yavsc/Pages/ExternalLogin/Challenge.cshtml.cs delete mode 100644 src/Yavsc/Pages/Grants/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Grants/ViewModel.cs delete mode 100644 src/Yavsc/Pages/Home/Error/Index.cshtml delete mode 100644 src/Yavsc/Pages/Home/Error/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Home/Error/ViewModel.cs delete mode 100644 src/Yavsc/Pages/IdentityServerSuppressions.cs delete mode 100644 src/Yavsc/Pages/Index.cshtml delete mode 100644 src/Yavsc/Pages/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Log.cs delete mode 100644 src/Yavsc/Pages/Redirect/Index.cshtml delete mode 100644 src/Yavsc/Pages/Redirect/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/SecurityHeadersAttribute.cs delete mode 100644 src/Yavsc/Pages/ServerSideSessions/Index.cshtml delete mode 100644 src/Yavsc/Pages/ServerSideSessions/Index.cshtml.cs delete mode 100644 src/Yavsc/Pages/Shared/_Layout.cshtml delete mode 100644 src/Yavsc/Pages/Shared/_Nav.cshtml delete mode 100644 src/Yavsc/Pages/Telemetry.cs delete mode 100644 src/Yavsc/Pages/_ViewImports.cshtml delete mode 100644 src/Yavsc/Pages/_ViewStart.cshtml create mode 100644 src/Yavsc/Services/YavscClientStore.cs create mode 100644 src/Yavsc/ViewModels/Account/ExternalProvider.cs create mode 100644 src/Yavsc/ViewModels/Account/LoginInputModel.cs create mode 100644 src/Yavsc/ViewModels/Account/RedirectViewModel.cs delete mode 100644 src/Yavsc/Views/Account/AccountCreated.cshtml delete mode 100644 src/Yavsc/Views/Account/AccountCreated.fr.cshtml delete mode 100644 src/Yavsc/Views/Account/AdminDelete.cshtml delete mode 100755 src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml delete mode 100644 src/Yavsc/Views/Account/Delete.cshtml delete mode 100755 src/Yavsc/Views/Account/EmailConfirmed.cshtml delete mode 100755 src/Yavsc/Views/Account/ExternalLoginConfirmation.en.cshtml delete mode 100755 src/Yavsc/Views/Account/ExternalLoginConfirmation.fr.cshtml delete mode 100755 src/Yavsc/Views/Account/ExternalLoginFailure.cshtml mode change 100755 => 100644 src/Yavsc/Views/Account/ForgotPassword.cshtml mode change 100755 => 100644 src/Yavsc/Views/Account/ForgotPasswordConfirmation.cshtml delete mode 100644 src/Yavsc/Views/Account/Index.cshtml delete mode 100755 src/Yavsc/Views/Account/Lockout.cshtml create mode 100644 src/Yavsc/Views/Account/LoggedOut.cshtml mode change 100755 => 100644 src/Yavsc/Views/Account/Login.cshtml rename src/Yavsc/{Pages/Account/Logout/Index.cshtml => Views/Account/Logout.cshtml} (65%) delete mode 100755 src/Yavsc/Views/Account/Register.cshtml delete mode 100755 src/Yavsc/Views/Account/Register.fr.cshtml mode change 100755 => 100644 src/Yavsc/Views/Account/ResetPassword.cshtml delete mode 100755 src/Yavsc/Views/Account/ResetPasswordConfirmation.cshtml delete mode 100755 src/Yavsc/Views/Account/SendCode.cshtml delete mode 100755 src/Yavsc/Views/Account/SendConfirationEmail.cshtml delete mode 100644 src/Yavsc/Views/Account/UserList.cshtml delete mode 100755 src/Yavsc/Views/Account/VerifyCode.cshtml rename src/Yavsc/{Pages => Views}/Device/Success.cshtml (75%) create mode 100644 src/Yavsc/Views/Device/UserCodeCapture.cshtml rename src/Yavsc/{Pages/Consent/Index.cshtml => Views/Device/UserCodeConfirmation.cshtml} (69%) delete mode 100644 src/Yavsc/Views/Devices/Delete.cshtml delete mode 100644 src/Yavsc/Views/Devices/Details.cshtml delete mode 100644 src/Yavsc/Views/Devices/Index.cshtml create mode 100644 src/Yavsc/Views/Diagnostics/Index.cshtml rename src/Yavsc/{Pages => Views}/Grants/Index.cshtml (94%) rename src/Yavsc/{Pages/Device => Views/Shared}/_ScopeListItem.cshtml (85%) rename src/Yavsc/{Pages => Views}/Shared/_ValidationSummary.cshtml (100%) rename src/Yavsc/wwwroot/js/{signin-redirect.js => signin-redirect js} (100%) diff --git a/global.json b/global.json index 161898d3..952b7d76 100644 --- a/global.json +++ b/global.json @@ -5,7 +5,6 @@ "tests" ], "sdk": { - "version": "8.0.200", "runtime": "dotnet", "architecture": "x64" }, diff --git a/src/Yavsc.Abstract/Authentication/Scope.cs b/src/Yavsc.Abstract/Authentication/Scope.cs index 89129087..5df037a4 100644 --- a/src/Yavsc.Abstract/Authentication/Scope.cs +++ b/src/Yavsc.Abstract/Authentication/Scope.cs @@ -5,10 +5,12 @@ namespace Yavsc.Models.Auth { public class Scope { - [Key] + [Key][Required] public string Id { get; set; } + [MaxLength(1024)][Required] + public string Description { get; set; } } diff --git a/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs b/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs index 7da35379..0b4aefbb 100644 --- a/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs +++ b/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs @@ -65,9 +65,9 @@ namespace Yavsc.Helpers return sb.ToString(); } - public static UserDirectoryInfo GetUserFiles(string userName, string subdir) + public static UserDirectoryInfo GetUserFiles(string userId, string subdir) { - UserDirectoryInfo di = new UserDirectoryInfo(UserFilesDirName, userName, subdir); + UserDirectoryInfo di = new UserDirectoryInfo(UserFilesDirName, userId, subdir); return di; } public static bool IsRegularFile(string userName, string subdir) @@ -82,7 +82,7 @@ namespace Yavsc.Helpers public static char[] AlfaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray(); // Only accept descent remote file names - public static char[] ValidFileNameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=_~. %#".ToCharArray(); + public static char[] ValidFileNameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=_~. %#@".ToCharArray(); // Estimate signature file name format public static Func diff --git a/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs b/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs index ce3589f1..77b7e6e4 100644 --- a/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs +++ b/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs @@ -23,12 +23,13 @@ namespace Yavsc.ViewModels.UserFiles { } - public UserDirectoryInfo(string userReposPath, string username, string path) + + public UserDirectoryInfo(string userReposPath, string userId, string path) { - if (string.IsNullOrWhiteSpace(username)) + if (string.IsNullOrWhiteSpace(userId)) throw new NotSupportedException("No user name, no user dir."); - UserName = username; - var finalPath = path == null ? username : Path.Combine(username, path); + UserName = userId; + var finalPath = path == null ? userId : Path.Combine(userId, path); if (!finalPath.IsValidYavscPath()) throw new InvalidOperationException( $"File name contains invalid chars ({finalPath})"); diff --git a/src/Yavsc.Server/Models/Auth/Client.cs b/src/Yavsc.Server/Models/Auth/Client.cs index 4de3a3bd..c071503c 100644 --- a/src/Yavsc.Server/Models/Auth/Client.cs +++ b/src/Yavsc.Server/Models/Auth/Client.cs @@ -1,20 +1,29 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Yavsc.Models.Auth { public class Client { - [Key] + [Key][Required][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } + + [Required] + [MaxLength(128)] public string DisplayName { get; set; } - public string RedirectUri { get; set; } - [MaxLength(100)] + + [MaxLength(512)] + public string? RedirectUri { get; set; } + + + [MaxLength(512)] public string LogoutRedirectUri { get; set; } + [MaxLength(512)] public string Secret { get; set; } public ApplicationTypes Type { get; set; } public bool Active { get; set; } public int RefreshTokenLifeTime { get; set; } - + public int AccessTokenLifetime { get; set; } } } diff --git a/src/Yavsc.Server/Models/Messaging/DimissClicked.cs b/src/Yavsc.Server/Models/Messaging/DimissClicked.cs index 814e18ab..3485bb1f 100644 --- a/src/Yavsc.Server/Models/Messaging/DimissClicked.cs +++ b/src/Yavsc.Server/Models/Messaging/DimissClicked.cs @@ -4,7 +4,7 @@ using Yavsc.Attributes.Validation; namespace Yavsc.Models.Messaging { - public class DimissClicked + public class DismissClicked { [YaRequired] public string UserId { get; set; } @@ -19,4 +19,4 @@ namespace Yavsc.Models.Messaging public virtual Notification Notified { get; set; } } -} \ No newline at end of file +} diff --git a/src/Yavsc.Server/Models/Relationship/Contact.cs b/src/Yavsc.Server/Models/Relationship/Contact.cs index 0c0f4ba6..7976432b 100644 --- a/src/Yavsc.Server/Models/Relationship/Contact.cs +++ b/src/Yavsc.Server/Models/Relationship/Contact.cs @@ -7,10 +7,10 @@ namespace Yavsc.Models.Relationship { public class Contact: IContact { - [YaRequired()] + [Required()] public string UserId { get; set; } - [YaRequired()] + [Required()] public string OwnerId { get; set; } public string Name { get; set; } @@ -22,10 +22,10 @@ namespace Yavsc.Models.Relationship public virtual PostalAddress PostalAddress { get; set; } - [ForeignKeyAttribute("OwnerId"),NotMapped] + [ForeignKey("OwnerId"),NotMapped] public virtual ApplicationUser Owner { get; set; } - [ForeignKeyAttribute("UserId"),NotMapped] + [ForeignKey("UserId"),NotMapped] public virtual ApplicationUser User { get; set; } } } diff --git a/src/Yavsc.Server/Models/Relationship/StaticContact.cs b/src/Yavsc.Server/Models/Relationship/StaticContact.cs new file mode 100644 index 00000000..7ae6ab6a --- /dev/null +++ b/src/Yavsc.Server/Models/Relationship/StaticContact.cs @@ -0,0 +1,12 @@ + +namespace Yavsc.Models.Relationship +{ +public class StaticContact +{ + public string Name { get; set; } + public string EMail { get; set; } + public PostalAddress? PostalAddress { get; set; } +} + +} + diff --git a/src/Yavsc.Server/Settings/SiteSettings.cs b/src/Yavsc.Server/Settings/SiteSettings.cs index dc5ae869..500c5e7a 100644 --- a/src/Yavsc.Server/Settings/SiteSettings.cs +++ b/src/Yavsc.Server/Settings/SiteSettings.cs @@ -26,12 +26,12 @@ namespace Yavsc /// Owner's email /// /// - public Contact Owner { get; set; } + public StaticContact Owner { get; set; } /// /// Administrator's email /// /// - public Contact Admin { get; set; } + public StaticContact Admin { get; set; } public string DataDir { get; set; } public string Avatars { get; set; } = "avatars"; diff --git a/src/Yavsc.Server/ViewModels/Account/ForgotPasswordViewModel.cs b/src/Yavsc.Server/ViewModels/Account/ForgotPasswordViewModel.cs index ded3d55a..6af77fe4 100644 --- a/src/Yavsc.Server/ViewModels/Account/ForgotPasswordViewModel.cs +++ b/src/Yavsc.Server/ViewModels/Account/ForgotPasswordViewModel.cs @@ -1,13 +1,12 @@ using System.ComponentModel.DataAnnotations; -using Yavsc.Attributes.Validation; namespace Yavsc.ViewModels.Account { public class ForgotPasswordViewModel { - [YaRequired] - [YaStringLength(512)] + [Required] + [StringLength(512)] public string? LoginOrEmail { get; set; } } } diff --git a/src/Yavsc.Server/ViewModels/Account/ResetPasswordViewModel.cs b/src/Yavsc.Server/ViewModels/Account/ResetPasswordViewModel.cs index b72e6cb5..a7f65c70 100644 --- a/src/Yavsc.Server/ViewModels/Account/ResetPasswordViewModel.cs +++ b/src/Yavsc.Server/ViewModels/Account/ResetPasswordViewModel.cs @@ -5,12 +5,18 @@ namespace Yavsc.ViewModels.Account { public class ResetPasswordViewModel { - [YaRequired] + [Required] + public string Id { get; set; } + + [Required] + public string Code { get; set; } + + [Required] [EmailAddress] public string Email { get; set; } - [YaRequired] - [YaStringLength(100, ErrorMessage = "Le {0} doit être long d'au moins {2} caractères.", MinimumLength = 6)] + [Required] + [StringLength(100, ErrorMessage = "Le {0} doit être long d'au moins {2} caractères.", MinimumLength = 6)] [DataType(DataType.Password)] public string Password { get; set; } diff --git a/src/Yavsc.Server/ViewModels/Administration/RoleInfo.cs b/src/Yavsc.Server/ViewModels/Administration/RoleInfo.cs index 4067e314..d810099b 100644 --- a/src/Yavsc.Server/ViewModels/Administration/RoleInfo.cs +++ b/src/Yavsc.Server/ViewModels/Administration/RoleInfo.cs @@ -1,19 +1,9 @@ namespace Yavsc.ViewModels.Administration { public class RoleInfo - { - public RoleInfo () - { - - } - public RoleInfo ( string roleName, string roleId, string[] users) - { - Name = roleName; // role.Name; - Id = roleId; // role.Id; - Users = users ; // role.Users.Select(u => u.UserId).ToArray(); - } - public string Id { get; set; } - public string Name { get; set; } - public string[] Users { get; set; } - } -} \ No newline at end of file + { + public string Id { get; set; } + public string Name { get; set; } + public int UserCount { get; set; } + } +} diff --git a/src/Yavsc/ApiControllers/Blogspot/FileSystemApiController.cs b/src/Yavsc/ApiControllers/Blogspot/FileSystemApiController.cs index af85d263..8ab6b094 100644 --- a/src/Yavsc/ApiControllers/Blogspot/FileSystemApiController.cs +++ b/src/Yavsc/ApiControllers/Blogspot/FileSystemApiController.cs @@ -43,7 +43,7 @@ namespace Yavsc.ApiControllers { if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState); // _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}"); - var files = AbstractFileSystemHelpers.GetUserFiles(User.Identity.Name, subdir); + var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir); return Ok(files); } diff --git a/src/Yavsc/ApiControllers/DimissClicksApiController.cs b/src/Yavsc/ApiControllers/DimissClicksApiController.cs index 2917da6d..d3b31afc 100644 --- a/src/Yavsc/ApiControllers/DimissClicksApiController.cs +++ b/src/Yavsc/ApiControllers/DimissClicksApiController.cs @@ -21,17 +21,17 @@ namespace Yavsc.Controllers // GET: api/DimissClicksApi [HttpGet] - public IEnumerable GetDimissClicked() + public IEnumerable GetDismissClicked() { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - return _context.DimissClicked.Where(d=>d.UserId == uid); + return _context.DismissClicked.Where(d=>d.UserId == uid); } [HttpGet("click/{noteid}"),AllowAnonymous] public async Task Click(long noteid ) { if (User.IsSignedIn()) - return await PostDimissClicked(new DimissClicked { NotificationId= noteid, UserId = User.GetUserId()}); + return await PostDismissClicked(new DismissClicked { NotificationId= noteid, UserId = User.GetUserId()}); await HttpContext.Session.LoadAsync(); var clicked = HttpContext.Session.GetString("clicked"); if (clicked == null) { @@ -41,8 +41,8 @@ namespace Yavsc.Controllers return Ok(); } // GET: api/DimissClicksApi/5 - [HttpGet("{id}", Name = "GetDimissClicked")] - public async Task GetDimissClicked([FromRoute] string id) + [HttpGet("{id}", Name = "GetDismissClicked")] + public async Task GetDismissClicked([FromRoute] string id) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (uid != id) return new ChallengeResult(); @@ -52,34 +52,34 @@ namespace Yavsc.Controllers return BadRequest(ModelState); } - DimissClicked dimissClicked = await _context.DimissClicked.SingleAsync(m => m.UserId == id); + DismissClicked DismissClicked = await _context.DismissClicked.SingleAsync(m => m.UserId == id); - if (dimissClicked == null) + if (DismissClicked == null) { return NotFound(); } - return Ok(dimissClicked); + return Ok(DismissClicked); } // PUT: api/DimissClicksApi/5 [HttpPut("{id}")] - public async Task PutDimissClicked([FromRoute] string id, [FromBody] DimissClicked dimissClicked) + public async Task PutDismissClicked([FromRoute] string id, [FromBody] DismissClicked DismissClicked) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (uid != id || uid != dimissClicked.UserId) return new ChallengeResult(); + if (uid != id || uid != DismissClicked.UserId) return new ChallengeResult(); if (!ModelState.IsValid) { return BadRequest(ModelState); } - if (id != dimissClicked.UserId) + if (id != DismissClicked.UserId) { return BadRequest(); } - _context.Entry(dimissClicked).State = EntityState.Modified; + _context.Entry(DismissClicked).State = EntityState.Modified; try { @@ -87,7 +87,7 @@ namespace Yavsc.Controllers } catch (DbUpdateConcurrencyException) { - if (!DimissClickedExists(id)) + if (!DismissClickedExists(id)) { return NotFound(); } @@ -102,24 +102,24 @@ namespace Yavsc.Controllers // POST: api/DimissClicksApi [HttpPost] - public async Task PostDimissClicked([FromBody] DimissClicked dimissClicked) + public async Task PostDismissClicked([FromBody] DismissClicked DismissClicked) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (uid != dimissClicked.UserId) return new ChallengeResult(); + if (uid != DismissClicked.UserId) return new ChallengeResult(); if (!ModelState.IsValid) { return BadRequest(ModelState); } - _context.DimissClicked.Add(dimissClicked); + _context.DismissClicked.Add(DismissClicked); try { await _context.SaveChangesAsync(User.GetUserId()); } catch (DbUpdateException) { - if (DimissClickedExists(dimissClicked.UserId)) + if (DismissClickedExists(DismissClicked.UserId)) { return new StatusCodeResult(StatusCodes.Status409Conflict); } @@ -129,12 +129,12 @@ namespace Yavsc.Controllers } } - return CreatedAtRoute("GetDimissClicked", new { id = dimissClicked.UserId }, dimissClicked); + return CreatedAtRoute("GetDismissClicked", new { id = DismissClicked.UserId }, DismissClicked); } // DELETE: api/DimissClicksApi/5 [HttpDelete("{id}")] - public async Task DeleteDimissClicked([FromRoute] string id) + public async Task DeleteDismissClicked([FromRoute] string id) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (!User.IsInRole("Administrator")) @@ -145,16 +145,16 @@ namespace Yavsc.Controllers return BadRequest(ModelState); } - DimissClicked dimissClicked = await _context.DimissClicked.SingleAsync(m => m.UserId == id); - if (dimissClicked == null) + DismissClicked DismissClicked = await _context.DismissClicked.SingleAsync(m => m.UserId == id); + if (DismissClicked == null) { return NotFound(); } - _context.DimissClicked.Remove(dimissClicked); + _context.DismissClicked.Remove(DismissClicked); await _context.SaveChangesAsync(User.GetUserId()); - return Ok(dimissClicked); + return Ok(DismissClicked); } protected override void Dispose(bool disposing) @@ -166,9 +166,9 @@ namespace Yavsc.Controllers base.Dispose(disposing); } - private bool DimissClickedExists(string id) + private bool DismissClickedExists(string id) { - return _context.DimissClicked.Count(e => e.UserId == id) > 0; + return _context.DismissClicked.Count(e => e.UserId == id) > 0; } } } diff --git a/src/Yavsc/Config.cs b/src/Yavsc/Config.cs index 02bd9098..938a9c1a 100644 --- a/src/Yavsc/Config.cs +++ b/src/Yavsc/Config.cs @@ -1,4 +1,5 @@ -using Duende.IdentityServer.Models; + +using IdentityServer4.Models; using Yavsc.Settings; namespace Yavsc; diff --git a/src/Yavsc/Controllers/Accounting/AccountController.cs b/src/Yavsc/Controllers/Accounting/AccountController.cs index 902ca337..77f4ec65 100644 --- a/src/Yavsc/Controllers/Accounting/AccountController.cs +++ b/src/Yavsc/Controllers/Accounting/AccountController.cs @@ -13,6 +13,20 @@ using Yavsc.ViewModels.Account; using Yavsc.Helpers; using Yavsc.Abstract.Manage; using Yavsc.Interface; +using IdentityServer4.Test; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Yavsc.Models.Access; +using IdentityServer4.Models; +using Yavsc.Extensions; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4; +using IdentityModel; +using System.Security.Cryptography; +using System.Text.Unicode; +using System.Text; namespace Yavsc.Controllers { @@ -34,8 +48,15 @@ namespace Yavsc.Controllers // TwilioSettings _twilioSettings; readonly ApplicationDbContext _dbContext; - + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly IEventService _events; public AccountController( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IAuthenticationSchemeProvider schemeProvider, + IEventService events, UserManager userManager, SignInManager signInManager, ITrueEmailSender emailSender, @@ -44,6 +65,11 @@ namespace Yavsc.Controllers IStringLocalizer localizer, ApplicationDbContext dbContext) { + _interaction = interaction; + _clientStore = clientStore; + _schemeProvider = schemeProvider; + _events = events; + _userManager = userManager; _signInManager = signInManager; _emailSender = emailSender; @@ -55,6 +81,327 @@ namespace Yavsc.Controllers } + /// + /// Entry point into the login workflow + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await BuildLoginViewModelAsync(returnUrl); + + if (vm.IsExternalLoginOnly) + { + // we only have one option for logging in and it's an external provider + return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); + } + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginInputModel model, string button) + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + // the user clicked the "cancel" button + if (button != "login") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + return Redirect(model.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + + var user = await _userManager.FindByNameAsync(model.Username); + if (user!=null) { + + + var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true); + + // validate username/password against in-memory store + if (signin.Succeeded) + { + await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); + + // only set explicit expiration here if user chooses "remember me". + // otherwise we rely upon expiration configured in cookie middleware. + AuthenticationProperties props = null; + if (AccountOptions.AllowRememberLogin && model.RememberLogin) + { + props = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) + }; + }; + + // issue authentication cookie with subject ID and username + var isuser = new IdentityServerUser(user.Id) + { + DisplayName = user.UserName + }; + + await HttpContext.SignInAsync(isuser, props); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(model.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(model.ReturnUrl)) + { + return Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)); + ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await BuildLoginViewModelAsync(model); + return View(vm); + } + + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // build a model so the logged out page knows what to display + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + // delete local authentication cookie + await HttpContext.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + // check if we need to trigger sign-out at an upstream identity provider + if (vm.TriggerExternalSignout) + { + // build a return URL so the upstream provider will redirect back + // to us after the user has logged out. this allows us to then + // complete our single sign-out processing. + string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); + + // this triggers a redirect to the external provider for sign-out + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + } + + return View("LoggedOut", vm); + } + + [HttpGet] + public IActionResult AccessDenied() + { + return View(); + } + + + /*****************************************/ + /* helper APIs for the AccountController */ + /*****************************************/ + private async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) + { + var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider; + + // this is meant to short circuit the UI and only trigger the one external IdP + var vm = new LoginViewModel + { + EnableLocalLogin = local, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + }; + + if (!local) + { + vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; + } + + return vm; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null) + .Select(x => new ExternalProvider + { + DisplayName = x.DisplayName ?? x.Name, + AuthenticationScheme = x.Name + }).ToList(); + + var allowLocal = true; + if (context?.Client.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + ExternalProviders = providers.ToArray() + }; + } + + private async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberLogin = model.RememberLogin; + return vm; + } + + private async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + if (User?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + private async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + if (User?.Identity.IsAuthenticated == true) + { + var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) + { + var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); + if (providerSupportsSignout) + { + if (vm.LogoutId == null) + { + // if there's no current logout context, we need to create one + // this captures necessary info from the current logged in user + // before we signout and redirect away to the external IdP for signout + vm.LogoutId = await _interaction.CreateLogoutContextAsync(); + } + + vm.ExternalAuthenticationScheme = idp; + } + } + } + + return vm; + } + + [Authorize(Roles = Constants.AdminGroupName)] public IActionResult Index() { @@ -570,9 +917,14 @@ namespace Yavsc.Controllers public async Task ResetPassword(string id, string code) { var user = await _userManager.FindByIdAsync(id); + if (user==null) return new BadRequestResult(); // We just serve the form to reset here. - return View(new ResetPasswordViewModel { }); + return View(new ResetPasswordViewModel { + Id = id, + Code = code, + Email = user.Email + }); } // POST: /Account/ResetPassword diff --git a/src/Yavsc/Controllers/Administration/AdministrationController.cs b/src/Yavsc/Controllers/Administration/AdministrationController.cs index c50de9e1..41770082 100644 --- a/src/Yavsc/Controllers/Administration/AdministrationController.cs +++ b/src/Yavsc/Controllers/Administration/AdministrationController.cs @@ -103,16 +103,23 @@ namespace Yavsc.Controllers await _userManager.FindByIdAsync(User.GetUserId()), Constants.AdminGroupName); - var roles = _roleManager.Roles.Select(x => new RoleInfo { + var roles = await _roleManager.Roles.Select(x => new RoleInfo { Id = x.Id, - Name = x.Name - }); + Name = x.Name + }).ToArrayAsync(); + foreach (var role in roles) + { + var uinrole = await _userManager.GetUsersInRoleAsync(role.Name); + + role.UserCount = uinrole.Count(); + } var assembly = GetType().Assembly; ViewBag.ThisAssembly = assembly.FullName; ViewBag.RunTimeVersion = assembly.ImageRuntimeVersion; + var rolesArray = roles.ToArray(); return View(new AdminViewModel { - Roles = roles.ToArray(), + Roles = rolesArray, AdminCount = adminCount.Count, YouAreAdmin = youAreAdmin, UserCount = userCount diff --git a/src/Yavsc/Controllers/Consent/ConsentController.cs b/src/Yavsc/Controllers/Consent/ConsentController.cs new file mode 100644 index 00000000..b85fcad7 --- /dev/null +++ b/src/Yavsc/Controllers/Consent/ConsentController.cs @@ -0,0 +1,265 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Events; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Validation; +using System.Collections.Generic; +using System; +using Yavsc.Models.Access; +using Yavsc.Extensions; + +namespace Yavsc.Controllers +{ + /// + /// This controller processes the consent UI + /// + [SecurityHeaders] + [Authorize] + public class ConsentController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ConsentController( + IIdentityServerInteractionService interaction, + IEventService events, + ILogger logger) + { + _interaction = interaction; + _events = events; + _logger = logger; + } + + /// + /// Shows the consent screen + /// + /// + /// + [HttpGet] + public async Task Index(string returnUrl) + { + var vm = await BuildViewModelAsync(returnUrl); + if (vm != null) + { + return View("Index", vm); + } + + return View("Error"); + } + + /// + /// Handles the consent screen postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Index(ConsentInputModel model) + { + var result = await ProcessConsent(model); + + if (result.IsRedirect) + { + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + if (context?.IsNativeClient() == true) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", result.RedirectUri); + } + + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError(string.Empty, result.ValidationError); + } + + if (result.ShowView) + { + return View("Index", result.ViewModel); + } + + return View("Error"); + } + + /*****************************************/ + /* helper APIs for the ConsentController */ + /*****************************************/ + private async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model?.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model?.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + return CreateConsentViewModel(model, returnUrl, request); + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request) + { + var vm = new ConsentViewModel + { + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + Description = model?.Description, + + ReturnUrl = returnUrl, + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach(var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + var displayName = apiScope.DisplayName ?? apiScope.Name; + if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + { + displayName += ":" + parsedScopeValue.ParsedParameter; + } + + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = displayName, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} diff --git a/src/Yavsc/Controllers/Device/DeviceController.cs b/src/Yavsc/Controllers/Device/DeviceController.cs new file mode 100644 index 00000000..2e69b95a --- /dev/null +++ b/src/Yavsc/Controllers/Device/DeviceController.cs @@ -0,0 +1,233 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Configuration; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Validation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Yavsc.Models.Access; + +namespace Yavsc.Controllers +{ + [Authorize] + [SecurityHeaders] + public class DeviceController : Controller + { + private readonly IDeviceFlowInteractionService _interaction; + private readonly IEventService _events; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DeviceController( + IDeviceFlowInteractionService interaction, + IEventService eventService, + IOptions options, + ILogger logger) + { + _interaction = interaction; + _events = eventService; + _options = options; + _logger = logger; + } + + [HttpGet] + public async Task Index() + { + string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + string userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + vm.ConfirmUserCode = true; + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UserCodeCapture(string userCode) + { + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Callback(DeviceAuthorizationInputModel model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + + var result = await ProcessConsent(model); + if (result.HasValidationError) return View("Error"); + + return View("Success"); + } + + private async Task ProcessConsent(DeviceAuthorizationInputModel model) + { + var result = new ProcessConsentResult(); + + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(userCode); + if (request != null) + { + return CreateConsentViewModel(userCode, model, request); + } + + return null; + } + + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + { + var vm = new DeviceAuthorizationViewModel + { + UserCode = userCode, + Description = model?.Description, + + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + // todo: use the parsed scope value in the display? + DisplayName = apiScope.DisplayName ?? apiScope.Name, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} diff --git a/src/Yavsc/Controllers/Diagnostics/DiagnosticsController.cs b/src/Yavsc/Controllers/Diagnostics/DiagnosticsController.cs new file mode 100644 index 00000000..ffdfb78d --- /dev/null +++ b/src/Yavsc/Controllers/Diagnostics/DiagnosticsController.cs @@ -0,0 +1,30 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Yavsc.Models; + +namespace Yavsc.Controllers +{ + [SecurityHeaders] + [Authorize] + public class DiagnosticsController : Controller + { + public async Task Index() + { + var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; + if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) + { + return NotFound(); + } + + var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); + return View(model); + } + } +} diff --git a/src/Yavsc/Controllers/Diagnostics/DiagnosticsViewModel.cs b/src/Yavsc/Controllers/Diagnostics/DiagnosticsViewModel.cs new file mode 100644 index 00000000..d88cbc5e --- /dev/null +++ b/src/Yavsc/Controllers/Diagnostics/DiagnosticsViewModel.cs @@ -0,0 +1,32 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using Microsoft.AspNetCore.Authentication; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text; + +namespace Yavsc.Models +{ + public class DiagnosticsViewModel + { + public DiagnosticsViewModel(AuthenticateResult result) + { + AuthenticateResult = result; + + if (result.Properties.Items.ContainsKey("client_list")) + { + var encoded = result.Properties.Items["client_list"]; + var bytes = Base64Url.Decode(encoded); + var value = Encoding.UTF8.GetString(bytes); + + Clients = JsonConvert.DeserializeObject(value); + } + } + + public AuthenticateResult AuthenticateResult { get; } + public IEnumerable Clients { get; } = new List(); + } +} diff --git a/src/Yavsc/Controllers/Diagnostics/LogoutViewModel.cs b/src/Yavsc/Controllers/Diagnostics/LogoutViewModel.cs new file mode 100644 index 00000000..8a6dedc0 --- /dev/null +++ b/src/Yavsc/Controllers/Diagnostics/LogoutViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class LogoutViewModel : LogoutInputModel + { + public bool ShowLogoutPrompt { get; set; } = true; + } +} diff --git a/src/Yavsc/Controllers/FileSystemController.cs b/src/Yavsc/Controllers/FileSystemController.cs index a3ab4616..36ebcb04 100644 --- a/src/Yavsc/Controllers/FileSystemController.cs +++ b/src/Yavsc/Controllers/FileSystemController.cs @@ -15,7 +15,7 @@ namespace Yavsc.Controllers if (subdir !=null) if (!subdir.IsValidYavscPath()) return new BadRequestResult(); - var files = AbstractFileSystemHelpers.GetUserFiles(User.Identity.Name, subdir); + var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir); return View(files); } } diff --git a/src/Yavsc/Controllers/HomeController.cs b/src/Yavsc/Controllers/HomeController.cs index 9edbe1f1..097ffcf5 100644 --- a/src/Yavsc/Controllers/HomeController.cs +++ b/src/Yavsc/Controllers/HomeController.cs @@ -37,12 +37,12 @@ namespace Yavsc.Controllers long[] clicked = null; if (uid == null) { - await HttpContext.Session.LoadAsync(); + // await HttpContext.Session.LoadAsync(); var strclicked = HttpContext.Session.GetString("clicked"); if (strclicked != null) clicked = strclicked.Split(':').Select(c => long.Parse(c)).ToArray(); if (clicked == null) clicked = new long[0]; } - else clicked = _dbContext.DimissClicked.Where(d => d.UserId == uid).Select(d => d.NotificationId).ToArray(); + else clicked = _dbContext.DismissClicked.Where(d => d.UserId == uid).Select(d => d.NotificationId).ToArray(); var notes = _dbContext.Notification.Where( n => !clicked.Contains(n.Id) ); diff --git a/src/Yavsc/Controllers/SecurityHeadersAttribute.cs b/src/Yavsc/Controllers/SecurityHeadersAttribute.cs new file mode 100644 index 00000000..a82ed1f2 --- /dev/null +++ b/src/Yavsc/Controllers/SecurityHeadersAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Yavsc +{ + public class SecurityHeadersAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + var result = context.Result; + if (result is ViewResult) + { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) + { + context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) + { + context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; + // also consider adding upgrade-insecure-requests once you have HTTPS in place for production + //csp += "upgrade-insecure-requests;"; + // also an example if you need client images to be displayed from twitter + // csp += "img-src 'self' https://pbs.twimg.com;"; + + // once for standards compliant browsers + if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + var referrer_policy = "no-referrer"; + if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) + { + context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); + } + } + } + } +} diff --git a/src/Yavsc/Extensions/ControlerExtensions.cs b/src/Yavsc/Extensions/ControlerExtensions.cs new file mode 100644 index 00000000..0de88133 --- /dev/null +++ b/src/Yavsc/Extensions/ControlerExtensions.cs @@ -0,0 +1,27 @@ +using IdentityServer4.Models; +using Microsoft.AspNetCore.Mvc; +using Yavsc.Models.Access; + +namespace Yavsc.Extensions ; + + public static class Extensions + { + /// + /// Checks if the redirect URI is for a native client. + /// + /// + public static bool IsNativeClient(this AuthorizationRequest context) + { + return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) + && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); + } + + public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + { + controller.HttpContext.Response.StatusCode = 200; + controller.HttpContext.Response.Headers["Location"] = ""; + + return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); + } + } + \ No newline at end of file diff --git a/src/Yavsc/HostingExtensions.cs b/src/Yavsc/Extensions/HostingExtensions.cs similarity index 90% rename from src/Yavsc/HostingExtensions.cs rename to src/Yavsc/Extensions/HostingExtensions.cs index 11641d31..404e7283 100644 --- a/src/Yavsc/HostingExtensions.cs +++ b/src/Yavsc/Extensions/HostingExtensions.cs @@ -1,6 +1,9 @@ using System.Globalization; -using Duende.IdentityServer; +using System.Security.Permissions; +using Google.Apis.Auth.OAuth2; using Google.Apis.Util.Store; +using IdentityServer4; +using IdentityServer4.Test; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; @@ -22,11 +25,13 @@ using Yavsc.Interface; using Yavsc.Models; using Yavsc.Models.Billing; using Yavsc.Models.Haircut; +using Yavsc.Models.Market; using Yavsc.Models.Workflow; using Yavsc.Services; using Yavsc.Settings; -namespace Yavsc; +namespace Yavsc.Extensions; + internal static class HostingExtensions { @@ -129,15 +134,12 @@ internal static class HostingExtensions public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { - IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() - .AddEnvironmentVariables() - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - IConfigurationRoot configuration = configurationBuilder.Build(); - - string? googleClientFile = configuration["Authentication:Google:GoogleWebClientJson"]; - string? googleServiceAccountJsonFile = configuration["Authentication:Google:GoogleServiceAccountJson"]; + var siteSection = builder.Configuration.GetSection("Site"); + var smtpSection = builder.Configuration.GetSection("Smtp"); + var paypalSection = builder.Configuration.GetSection("Authentication:PayPal"); + + string? googleClientFile = builder.Configuration["Authentication:Google:GoogleWebClientJson"]; + string? googleServiceAccountJsonFile = builder.Configuration["Authentication:Google:GoogleServiceAccountJson"]; if (googleClientFile != null) { Config.GoogleWebClientConfiguration = new ConfigurationBuilder().AddJsonFile(googleClientFile).Build(); @@ -148,9 +150,14 @@ internal static class HostingExtensions FileInfo safile = new FileInfo(googleServiceAccountJsonFile); Config.GServiceAccount = JsonConvert.DeserializeObject(safile.OpenText().ReadToEnd()); } - + string? googleClientId = builder.Configuration["Authentication:Google:ClientId"]; + string? googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; var services = builder.Services; + services.Configure(siteSection); + services.Configure(smtpSection); + services.Configure(paypalSection); + services.AddRazorPages(); services.AddSignalR(o => { @@ -178,7 +185,10 @@ internal static class HostingExtensions .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) - .AddAspNetIdentity().AddServerSideSessions(); + ; + services.AddSession(); + // TODO .AddServerSideSessionStore() + services.AddAuthentication() .AddGoogle(options => @@ -188,8 +198,8 @@ internal static class HostingExtensions // register your IdentityServer with Google at https://console.developers.google.com // enable the Google+ API // set the redirect URI to https://localhost:5001/signin-google - options.ClientId = "325408689282-6bekh7p3guj4k0f3301a6frf025cnrk1.apps.googleusercontent.com"; - options.ClientSecret = "XV1DLrq8cQE2JI4gZP3h6d8y"; + options.ClientId = googleClientId; + options.ClientSecret = googleClientSecret; }); services.Configure(options => { @@ -222,15 +232,13 @@ internal static class HostingExtensions }; }); - services.AddOptions(); - - _ = services.AddCors(options => + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => { - options.AddPolicy("CorsPolicy", builder => - { - _ = builder.WithOrigins("*"); - }); + _ = builder.WithOrigins("*"); }); + }); // Add the system clock service @@ -278,7 +286,7 @@ internal static class HostingExtensions { options.ResourcesPath = "Resources"; }); - var dataDir = new DirectoryInfo(configuration["Site:DataDir"]); + var dataDir = new DirectoryInfo(builder.Configuration["Site:DataDir"]); // Add session related services. services.AddDataProtection().PersistKeysToFileSystem(dataDir); @@ -300,7 +308,7 @@ internal static class HostingExtensions }); _ = services.AddControllersWithViews() .AddNewtonsoftJson(); - LoadGoogleConfig(configuration); + LoadGoogleConfig(builder.Configuration); return builder.Build(); } @@ -311,6 +319,7 @@ internal static class HostingExtensions { app.UseDeveloperExceptionPage(); } + app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); diff --git a/src/Yavsc/Helpers/OAuthHelpers.cs b/src/Yavsc/Helpers/OAuthHelpers.cs index 0363b73a..efd3942b 100644 --- a/src/Yavsc/Helpers/OAuthHelpers.cs +++ b/src/Yavsc/Helpers/OAuthHelpers.cs @@ -6,7 +6,7 @@ public class Helper { public static string GetHash(string input) { - HashAlgorithm hashAlgorithm = new SHA256CryptoServiceProvider(); + HashAlgorithm hashAlgorithm = SHA256CryptoServiceProvider.Create(); byte[] byteValue = System.Text.Encoding.UTF8.GetBytes(input); @@ -15,4 +15,4 @@ public class Helper return Convert.ToBase64String(byteHash); } } -} \ No newline at end of file +} diff --git a/src/Yavsc/Models/Access/AccountOptions.cs b/src/Yavsc/Models/Access/AccountOptions.cs new file mode 100644 index 00000000..9c544316 --- /dev/null +++ b/src/Yavsc/Models/Access/AccountOptions.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Yavsc.Models.Access +{ + public class AccountOptions + { + public static bool AllowLocalLogin = true; + public static bool AllowRememberLogin = true; + public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); + + public static bool ShowLogoutPrompt = true; + public static bool AutomaticRedirectAfterSignOut = false; + + public static string InvalidCredentialsErrorMessage = "Invalid username or password"; + } +} diff --git a/src/Yavsc/Models/Access/ConsentInputModel.cs b/src/Yavsc/Models/Access/ConsentInputModel.cs new file mode 100644 index 00000000..b5015bdd --- /dev/null +++ b/src/Yavsc/Models/Access/ConsentInputModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Collections.Generic; + +namespace Yavsc.Models.Access +{ + public class ConsentInputModel + { + public string Button { get; set; } + public IEnumerable ScopesConsented { get; set; } + public bool RememberConsent { get; set; } + public string ReturnUrl { get; set; } + public string Description { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/ConsentViewModel.cs b/src/Yavsc/Models/Access/ConsentViewModel.cs new file mode 100644 index 00000000..f94204ff --- /dev/null +++ b/src/Yavsc/Models/Access/ConsentViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Collections.Generic; + +namespace Yavsc.Models.Access +{ + public class ConsentViewModel : ConsentInputModel + { + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/DeviceAuthorizationInputModel.cs b/src/Yavsc/Models/Access/DeviceAuthorizationInputModel.cs new file mode 100644 index 00000000..3dd6b50d --- /dev/null +++ b/src/Yavsc/Models/Access/DeviceAuthorizationInputModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class DeviceAuthorizationInputModel : ConsentInputModel + { + public string UserCode { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/DeviceAuthorizationViewModel.cs b/src/Yavsc/Models/Access/DeviceAuthorizationViewModel.cs new file mode 100644 index 00000000..1fb27c21 --- /dev/null +++ b/src/Yavsc/Models/Access/DeviceAuthorizationViewModel.cs @@ -0,0 +1,13 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + + public class DeviceAuthorizationViewModel : ConsentViewModel + { + public string UserCode { get; set; } + public bool ConfirmUserCode { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/GrantViewModel.cs b/src/Yavsc/Models/Access/GrantViewModel.cs new file mode 100644 index 00000000..d9d0e156 --- /dev/null +++ b/src/Yavsc/Models/Access/GrantViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class GrantViewModel + { + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public string Description { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/GrantsViewModel.cs b/src/Yavsc/Models/Access/GrantsViewModel.cs new file mode 100644 index 00000000..21aa859e --- /dev/null +++ b/src/Yavsc/Models/Access/GrantsViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class GrantsViewModel + { + public IEnumerable Grants { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/LoggedOutViewModel.cs b/src/Yavsc/Models/Access/LoggedOutViewModel.cs new file mode 100644 index 00000000..1fbbbb59 --- /dev/null +++ b/src/Yavsc/Models/Access/LoggedOutViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class LoggedOutViewModel + { + public string PostLogoutRedirectUri { get; set; } + public string ClientName { get; set; } + public string SignOutIframeUrl { get; set; } + + public bool AutomaticRedirectAfterSignOut { get; set; } + + public string LogoutId { get; set; } + public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; + public string ExternalAuthenticationScheme { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/LogoutInputModel.cs b/src/Yavsc/Models/Access/LogoutInputModel.cs new file mode 100644 index 00000000..479d3384 --- /dev/null +++ b/src/Yavsc/Models/Access/LogoutInputModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class LogoutInputModel + { + public string LogoutId { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/ProcessConsentResult.cs b/src/Yavsc/Models/Access/ProcessConsentResult.cs new file mode 100644 index 00000000..b86eb9f8 --- /dev/null +++ b/src/Yavsc/Models/Access/ProcessConsentResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; + +namespace Yavsc.Models.Access +{ + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/RedirectViewModel.cs b/src/Yavsc/Models/Access/RedirectViewModel.cs new file mode 100644 index 00000000..2bece227 --- /dev/null +++ b/src/Yavsc/Models/Access/RedirectViewModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Yavsc.Models.Access +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} diff --git a/src/Yavsc/Models/Access/ScopeViewModel.cs b/src/Yavsc/Models/Access/ScopeViewModel.cs new file mode 100644 index 00000000..3e9bd135 --- /dev/null +++ b/src/Yavsc/Models/Access/ScopeViewModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc.Models.Access +{ + public class ScopeViewModel + { + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } +} diff --git a/src/Yavsc/Models/ApplicationDbContext.cs b/src/Yavsc/Models/ApplicationDbContext.cs index 0a5dfd6f..3a6c732e 100644 --- a/src/Yavsc/Models/ApplicationDbContext.cs +++ b/src/Yavsc/Models/ApplicationDbContext.cs @@ -72,7 +72,7 @@ namespace Yavsc.Models builder.Entity().HasKey(u => new { u.InstrumentId, u.UserId }); builder.Entity().HasKey(a => new { a.CircleId, a.BlogPostId }); builder.Entity().HasKey(c => new { c.MemberId, c.CircleId }); - builder.Entity().HasKey(c => new { uid = c.UserId, notid = c.NotificationId }); + builder.Entity().HasKey(c => new { uid = c.UserId, notid = c.NotificationId }); builder.Entity().HasKey(ti => new { ti.TaintId, ti.PrestationId }); builder.Entity().HasKey(l => new { l.HRef, l.Method }); builder.Entity().HasKey(l => new { l.Start, l.End }); @@ -108,15 +108,16 @@ namespace Yavsc.Models optionsBuilder.UseNpgsql(envSetup); } - public DbSet Applications { get; set; } public DbSet RefreshTokens { get; set; } + /// /// Activities referenced on this site /// /// public DbSet Activities { get; set; } + public DbSet UserActivities { get; set; } /// /// Users posts @@ -125,7 +126,7 @@ namespace Yavsc.Models public DbSet Blogspot { get; set; } /// - /// Skills propulsed by this site + /// Skills powered by this site /// /// public DbSet SiteSkills { get; set; } @@ -251,7 +252,7 @@ namespace Yavsc.Models public DbSet Notification { get; set; } - public DbSet DimissClicked { get; set; } + public DbSet DismissClicked { get; set; } [ActivitySettings] @@ -299,5 +300,7 @@ namespace Yavsc.Models public DbSet InstrumentRating { get; set; } + public DbSet Scopes { get; set; } + } } diff --git a/src/Yavsc/Models/ConsentOptions.cs b/src/Yavsc/Models/ConsentOptions.cs new file mode 100644 index 00000000..623f77bd --- /dev/null +++ b/src/Yavsc/Models/ConsentOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace Yavsc +{ + public class ConsentOptions + { + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; + } +} diff --git a/src/Yavsc/Pages/Account/AccessDenied.cshtml b/src/Yavsc/Pages/Account/AccessDenied.cshtml deleted file mode 100644 index 068c01d3..00000000 --- a/src/Yavsc/Pages/Account/AccessDenied.cshtml +++ /dev/null @@ -1,10 +0,0 @@ -@page -@model Yavsc.Pages.Account.AccessDeniedModel -@{ -} -
-
-

Access Denied

-

You do not have permission to access that resource.

-
-
diff --git a/src/Yavsc/Pages/Account/AccessDenied.cshtml.cs b/src/Yavsc/Pages/Account/AccessDenied.cshtml.cs deleted file mode 100644 index b8297157..00000000 --- a/src/Yavsc/Pages/Account/AccessDenied.cshtml.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Account; - -public class AccessDeniedModel : PageModel -{ - public void OnGet() - { - } -} diff --git a/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml b/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml deleted file mode 100644 index 28a185d5..00000000 --- a/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@page -@model Yavsc.Pages.ForgotPassword.Index - -

Forgot your password

- -
-

Enter your user name or e-mail.

-
-
-
- -
- - -
-
-
-
- -
-
-
- -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml.cs b/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml.cs deleted file mode 100644 index 092b8d2a..00000000 --- a/src/Yavsc/Pages/Account/ForgotPassword/Index.cshtml.cs +++ /dev/null @@ -1,113 +0,0 @@ - -using System.Web; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; -using Google.Apis.Calendar.v3.Data; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using Yavsc.Helpers; -using Yavsc.Interface; -using Yavsc.Models; -using Yavsc.ViewModels.Account; - -namespace Yavsc.Pages.ForgotPassword; - -[SecurityHeaders] -[AllowAnonymous] -public class Index : PageModel -{ - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IIdentityServerInteractionService _interaction; - private readonly IAuthenticationSchemeProvider _schemeProvider; - private readonly IIdentityProviderStore _identityProviderStore; - private readonly IEventService _events; - private readonly ApplicationDbContext _dbContext; - private readonly ILogger _logger; - private readonly SiteSettings _siteSettings; - private readonly ITrueEmailSender _emailSender; - private readonly IStringLocalizer _localizer; - - [BindProperty] - public ForgotPasswordViewModel Input { get; set; } - - public Index( - IIdentityServerInteractionService interaction, - IAuthenticationSchemeProvider schemeProvider, - IIdentityProviderStore identityProviderStore, - IEventService events, - UserManager userManager, - SignInManager signInManager, - ApplicationDbContext applicationDbContext, - ILoggerFactory loggerFactory, - ITrueEmailSender emailSender, - IStringLocalizer localizer, - IOptions siteSettings - ) - { - _userManager = userManager; - _signInManager = signInManager; - _interaction = interaction; - _schemeProvider = schemeProvider; - _identityProviderStore = identityProviderStore; - _events = events; - _dbContext = applicationDbContext; - _logger = loggerFactory.CreateLogger(); - _siteSettings = siteSettings.Value; - _emailSender = emailSender; - _localizer = localizer; - } - - public async Task OnGet() - { - return Page(); - } - - public async Task OnPost() - { - ApplicationUser user; - // Username should not contain any '@' - if (Input.LoginOrEmail.Contains('@')) - { - user = await _userManager.FindByEmailAsync(Input.LoginOrEmail); - } - else - { - user = await _dbContext.Users.FirstOrDefaultAsync(u => u.UserName == Input.LoginOrEmail); - } - - // Don't reveal that the user does not exist or is not confirmed - if (user == null) - { - _logger.LogWarning($"ForgotPassword: Email or User name {Input.LoginOrEmail} not found"); - return Redirect("ForgotPasswordConfirmation"); - } - // We cannot require the email to be confimed, - // or a lot of non confirmed email never be able to finalyze - // registration. - if (!await _userManager.IsEmailConfirmedAsync(user)) - { - _logger.LogWarning($"ForgotPassword: Email {Input.LoginOrEmail} not confirmed"); - // don't break this recovery process here ... - // or else e-mail won't ever be validated, since user lost his password. - // don't return View("ForgotPasswordConfirmation"); - } - - // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 - // Send an email with this link - var code = await _userManager.GeneratePasswordResetTokenAsync(user); - var callbackUrl = _siteSettings.Audience + "/Account/ResetPassword/" + - HttpUtility.UrlEncode(user.Id) + "/" + HttpUtility.UrlEncode(code); - - var sent = await _emailSender.SendEmailAsync(user.UserName, user.Email, _localizer["Reset Password"], - _localizer["Please reset your password by "] + " following this link"); - return Page(); - } -} diff --git a/src/Yavsc/Pages/Account/Login/Index.cshtml b/src/Yavsc/Pages/Account/Login/Index.cshtml deleted file mode 100644 index c8aa3034..00000000 --- a/src/Yavsc/Pages/Account/Login/Index.cshtml +++ /dev/null @@ -1,136 +0,0 @@ -@page -@model Yavsc.Pages.Login.Index - - diff --git a/src/Yavsc/Pages/Account/Login/Index.cshtml.cs b/src/Yavsc/Pages/Account/Login/Index.cshtml.cs deleted file mode 100644 index e0f0b3d5..00000000 --- a/src/Yavsc/Pages/Account/Login/Index.cshtml.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Yavsc.Models; - -namespace Yavsc.Pages.Login; - -[SecurityHeaders] -[AllowAnonymous] -public class Index : PageModel -{ - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - private readonly IAuthenticationSchemeProvider _schemeProvider; - private readonly IIdentityProviderStore _identityProviderStore; - - public ViewModel View { get; set; } = default!; - - [BindProperty] - public InputModel Input { get; set; } = default!; - - public Index( - IIdentityServerInteractionService interaction, - IAuthenticationSchemeProvider schemeProvider, - IIdentityProviderStore identityProviderStore, - IEventService events, - UserManager userManager, - SignInManager signInManager) - { - _userManager = userManager; - _signInManager = signInManager; - _interaction = interaction; - _schemeProvider = schemeProvider; - _identityProviderStore = identityProviderStore; - _events = events; - } - - public async Task OnGet(string? returnUrl) - { - await BuildModelAsync(returnUrl); - - if (View.IsExternalLoginOnly) - { - // we only have one option for logging in and it's an external provider - return RedirectToPage("/ExternalLogin/Challenge", new { scheme = View.ExternalLoginScheme, returnUrl }); - } - - return Page(); - } - - public async Task OnPost() - { - // check if we are in the context of an authorization request - var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); - - // the user clicked the "cancel" button - if (Input.Button != "login") - { - if (context != null) - { - // This "can't happen", because if the ReturnUrl was null, then the context would be null - ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl)); - - // if the user cancels, send a result back into IdentityServer as if they - // denied the consent (even if this client does not require consent). - // this will send back an access denied OIDC error response to the client. - await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); - - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - if (context.IsNativeClient()) - { - // The client is native, so this change in how to - // return the response is for better UX for the end user. - return this.LoadingPage(Input.ReturnUrl); - } - - return Redirect(Input.ReturnUrl ?? "~/"); - } - else - { - // since we don't have a valid context, then we just go back to the home page - return Redirect("~/"); - } - } - - if (ModelState.IsValid) - { - var result = await _signInManager.PasswordSignInAsync(Input.Username!, Input.Password!, Input.RememberLogin, lockoutOnFailure: true); - if (result.Succeeded) - { - var user = await _userManager.FindByNameAsync(Input.Username!); - await _events.RaiseAsync(new UserLoginSuccessEvent(user!.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); - Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider); - - if (context != null) - { - // This "can't happen", because if the ReturnUrl was null, then the context would be null - ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl)); - - if (context.IsNativeClient()) - { - // The client is native, so this change in how to - // return the response is for better UX for the end user. - return this.LoadingPage(Input.ReturnUrl); - } - - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return Redirect(Input.ReturnUrl ?? "~/"); - } - - // request for a local page - if (Url.IsLocalUrl(Input.ReturnUrl)) - { - return Redirect(Input.ReturnUrl); - } - else if (string.IsNullOrEmpty(Input.ReturnUrl)) - { - return Redirect("~/"); - } - else - { - // user might have clicked on a malicious link - should be logged - throw new ArgumentException("invalid return URL"); - } - } - - const string error = "invalid credentials"; - await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId)); - Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error); - ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage); - } - - // something went wrong, show form with error - await BuildModelAsync(Input.ReturnUrl); - return Page(); - } - - private async Task BuildModelAsync(string? returnUrl) - { - Input = new InputModel - { - ReturnUrl = returnUrl - }; - - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) - { - var local = context.IdP == Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider; - - // this is meant to short circuit the UI and only trigger the one external IdP - View = new ViewModel - { - EnableLocalLogin = local, - }; - - Input.Username = context.LoginHint; - - if (!local) - { - View.ExternalProviders = new[] { new ViewModel.ExternalProvider ( authenticationScheme: context.IdP ) }; - } - - return; - } - - var schemes = await _schemeProvider.GetAllSchemesAsync(); - - var providers = schemes - .Where(x => x.DisplayName != null) - .Select(x => new ViewModel.ExternalProvider - ( - authenticationScheme: x.Name, - displayName: x.DisplayName ?? x.Name - )).ToList(); - - var dynamicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync()) - .Where(x => x.Enabled) - .Select(x => new ViewModel.ExternalProvider - ( - authenticationScheme: x.Scheme, - displayName: x.DisplayName ?? x.Scheme - )); - providers.AddRange(dynamicSchemes); - - - var allowLocal = true; - var client = context?.Client; - if (client != null) - { - allowLocal = client.EnableLocalLogin; - if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Count != 0) - { - providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); - } - } - - View = new ViewModel - { - AllowRememberLogin = LoginOptions.AllowRememberLogin, - EnableLocalLogin = allowLocal && LoginOptions.AllowLocalLogin, - ExternalProviders = providers.ToArray() - }; - } -} diff --git a/src/Yavsc/Pages/Account/Login/InputModel.cs b/src/Yavsc/Pages/Account/Login/InputModel.cs deleted file mode 100644 index 29dca8ef..00000000 --- a/src/Yavsc/Pages/Account/Login/InputModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System.ComponentModel.DataAnnotations; - -namespace Yavsc.Pages.Login; - -public class InputModel -{ - [Required] - public string? Username { get; set; } - [Required] - public string? Password { get; set; } - public bool RememberLogin { get; set; } - public string? ReturnUrl { get; set; } - public string? Button { get; set; } -} diff --git a/src/Yavsc/Pages/Account/Login/LoginOptions.cs b/src/Yavsc/Pages/Account/Login/LoginOptions.cs deleted file mode 100644 index 698f5256..00000000 --- a/src/Yavsc/Pages/Account/Login/LoginOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Login; - -public static class LoginOptions -{ - public static readonly bool AllowLocalLogin = true; - public static readonly bool AllowRememberLogin = true; - public static readonly TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); - public static readonly string InvalidCredentialsErrorMessage = "Invalid username or password"; -} diff --git a/src/Yavsc/Pages/Account/Login/ViewModel.cs b/src/Yavsc/Pages/Account/Login/ViewModel.cs deleted file mode 100644 index f4f19a81..00000000 --- a/src/Yavsc/Pages/Account/Login/ViewModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Login; - -public class ViewModel -{ - public bool AllowRememberLogin { get; set; } = true; - public bool EnableLocalLogin { get; set; } = true; - - public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); - public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); - - public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; - public string? ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; - - public class ExternalProvider - { - public ExternalProvider(string authenticationScheme, string? displayName = null) - { - AuthenticationScheme = authenticationScheme; - DisplayName = displayName; - } - - public string? DisplayName { get; set; } - public string AuthenticationScheme { get; set; } - } -} diff --git a/src/Yavsc/Pages/Account/Logout/Index.cshtml.cs b/src/Yavsc/Pages/Account/Logout/Index.cshtml.cs deleted file mode 100644 index 2a34967c..00000000 --- a/src/Yavsc/Pages/Account/Logout/Index.cshtml.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Services; -using IdentityModel; -using Yavsc.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Logout; - -[SecurityHeaders] -[AllowAnonymous] -public class Index : PageModel -{ - private readonly SignInManager _signInManager; - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - - [BindProperty] - public string? LogoutId { get; set; } - - public Index(SignInManager signInManager, IIdentityServerInteractionService interaction, IEventService events) - { - _signInManager = signInManager; - _interaction = interaction; - _events = events; - } - - public async Task OnGet(string? logoutId) - { - LogoutId = logoutId; - - var showLogoutPrompt = LogoutOptions.ShowLogoutPrompt; - - if (User.Identity?.IsAuthenticated != true) - { - // if the user is not authenticated, then just show logged out page - showLogoutPrompt = false; - } - else - { - var context = await _interaction.GetLogoutContextAsync(LogoutId); - if (context?.ShowSignoutPrompt == false) - { - // it's safe to automatically sign-out - showLogoutPrompt = false; - } - } - - if (showLogoutPrompt == false) - { - // if the request for logout was properly authenticated from IdentityServer, then - // we don't need to show the prompt and can just log the user out directly. - return await OnPost(); - } - - return Page(); - } - - public async Task OnPost() - { - if (User.Identity?.IsAuthenticated == true) - { - // if there's no current logout context, we need to create one - // this captures necessary info from the current logged in user - // this can still return null if there is no context needed - LogoutId ??= await _interaction.CreateLogoutContextAsync(); - - // delete local authentication cookie - await _signInManager.SignOutAsync(); - - // see if we need to trigger federated logout - var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; - - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); - Telemetry.Metrics.UserLogout(idp); - - // if it's a local login we can ignore this workflow - if (idp != null && idp != Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider) - { - // we need to see if the provider supports external logout - if (await HttpContext.GetSchemeSupportsSignOutAsync(idp)) - { - // build a return URL so the upstream provider will redirect back - // to us after the user has logged out. this allows us to then - // complete our single sign-out processing. - var url = Url.Page("/Account/Logout/Loggedout", new { logoutId = LogoutId }); - - // this triggers a redirect to the external provider for sign-out - return SignOut(new AuthenticationProperties { RedirectUri = url }, idp); - } - } - } - - return RedirectToPage("/Account/Logout/LoggedOut", new { logoutId = LogoutId }); - } -} diff --git a/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml b/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml deleted file mode 100644 index 44d8e158..00000000 --- a/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@page -@model Yavsc.Pages.Logout.LoggedOut - -
-

- Logout - You are now logged out -

- - @if (Model.View.PostLogoutRedirectUri != null) - { -
- Click here to return to the - @Model.View.ClientName application. -
- } - - @if (Model.View.SignOutIframeUrl != null) - { - - } -
- -@section scripts -{ - @if (Model.View.AutomaticRedirectAfterSignOut) - { - - } -} diff --git a/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml.cs b/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml.cs deleted file mode 100644 index a15fb67e..00000000 --- a/src/Yavsc/Pages/Account/Logout/LoggedOut.cshtml.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Logout; - -[SecurityHeaders] -[AllowAnonymous] -public class LoggedOut : PageModel -{ - private readonly IIdentityServerInteractionService _interactionService; - - public LoggedOutViewModel View { get; set; } = default!; - - public LoggedOut(IIdentityServerInteractionService interactionService) - { - _interactionService = interactionService; - } - - public async Task OnGet(string? logoutId) - { - // get context information (client name, post logout redirect URI and iframe for federated signout) - var logout = await _interactionService.GetLogoutContextAsync(logoutId); - - View = new LoggedOutViewModel - { - AutomaticRedirectAfterSignOut = LogoutOptions.AutomaticRedirectAfterSignOut, - PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, - ClientName = String.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, - SignOutIframeUrl = logout?.SignOutIFrameUrl - }; - } -} diff --git a/src/Yavsc/Pages/Account/Logout/LoggedOutViewModel.cs b/src/Yavsc/Pages/Account/Logout/LoggedOutViewModel.cs deleted file mode 100644 index 87868902..00000000 --- a/src/Yavsc/Pages/Account/Logout/LoggedOutViewModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Logout; - -public class LoggedOutViewModel -{ - public string? PostLogoutRedirectUri { get; set; } - public string? ClientName { get; set; } - public string? SignOutIframeUrl { get; set; } - public bool AutomaticRedirectAfterSignOut { get; set; } -} diff --git a/src/Yavsc/Pages/Account/Logout/LogoutOptions.cs b/src/Yavsc/Pages/Account/Logout/LogoutOptions.cs deleted file mode 100644 index 8e34591d..00000000 --- a/src/Yavsc/Pages/Account/Logout/LogoutOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - - -namespace Yavsc.Pages.Logout; - -public static class LogoutOptions -{ - public static readonly bool ShowLogoutPrompt = true; - public static readonly bool AutomaticRedirectAfterSignOut = false; -} diff --git a/src/Yavsc/Pages/Activity/Create.cshtml b/src/Yavsc/Pages/Activity/Create.cshtml deleted file mode 100644 index 11217031..00000000 --- a/src/Yavsc/Pages/Activity/Create.cshtml +++ /dev/null @@ -1,83 +0,0 @@ -@model Activity - -@{ - ViewData["Title"] = "Create"; -} - -

Create

- -
-
-

Activity

-
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
- - - -
-
- -
-
-
-
- - - - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Pages/Activity/Delete.cshtml b/src/Yavsc/Pages/Activity/Delete.cshtml deleted file mode 100644 index baddea2c..00000000 --- a/src/Yavsc/Pages/Activity/Delete.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@model Activity - -@{ - ViewData["Title"] = "Delete"; -} - -

Delete

- -

AreYourSureYouWantToDeleteThis

-
-

Activity

-
-
-
- @Html.DisplayNameFor(model => model.Description) -
-
- @Html.DisplayFor(model => model.Description) -
-
- @Html.DisplayNameFor(model => model.Name) -
-
- @Html.DisplayFor(model => model.Name) -
-
- @Html.DisplayNameFor(model => model.Photo) -
-
- @Html.DisplayFor(model => model.Photo) -
-
- -
-
- - Back to List -
-
-
diff --git a/src/Yavsc/Pages/Activity/Details.cshtml b/src/Yavsc/Pages/Activity/Details.cshtml deleted file mode 100644 index c8703872..00000000 --- a/src/Yavsc/Pages/Activity/Details.cshtml +++ /dev/null @@ -1,37 +0,0 @@ -@model Activity - -@{ - ViewData["Title"] = "Details"; -} - -

Details

- -
-

Activity

-
-
-
- @Html.DisplayNameFor(model => model.Description) -
-
- @Html.DisplayFor(model => model.Description) -
-
- @Html.DisplayNameFor(model => model.Name) -
-
- @Html.DisplayFor(model => model.Name) -
-
- @Html.DisplayNameFor(model => model.Photo) -
-
- @Html.DisplayFor(model => model.Photo) - -
-
-
-

- Edit | - Back to List -

diff --git a/src/Yavsc/Pages/Activity/Edit.cshtml b/src/Yavsc/Pages/Activity/Edit.cshtml deleted file mode 100644 index cb56ede6..00000000 --- a/src/Yavsc/Pages/Activity/Edit.cshtml +++ /dev/null @@ -1,86 +0,0 @@ -@model Activity - -@{ - ViewData["Title"] = "Edit"; -} - -

Edit

- -
-
-

Activity @Model.Code

-
-
- -
- -
- - -
-
- -
- -
- - - -
-
- -
- -
- - - -
-
-
- -
- - - -
-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
-
-
- -
-
-
-
- - - - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Pages/Activity/Index.cshtml b/src/Yavsc/Pages/Activity/Index.cshtml deleted file mode 100644 index 25b99320..00000000 --- a/src/Yavsc/Pages/Activity/Index.cshtml +++ /dev/null @@ -1,89 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Index"; -} -@section scripts { - -} -

@ViewData["Title"]

- -

- Create New -

- - - - - - - - - - - - - -@foreach (var item in Model) { - - - - - - - - - - - -} -
- @Html.DisplayNameFor(model => model.Name) - - @Html.DisplayNameFor(model => model.Code) - - @Html.DisplayNameFor(model => model.Description) - - @Html.DisplayNameFor(model => model.Photo) - - @Html.DisplayNameFor(model => model.Parent) - - @Html.DisplayNameFor(model => model.SettingsClassName) - - @Html.DisplayNameFor(model => model.Children) - - @Html.DisplayNameFor(model => model.Rate) -
- @Html.DisplayFor(modelItem => item.Name) - - @Html.DisplayFor(modelItem => item.Code) - - @Html.DisplayFor(modelItem => item.Description) - @if (item.Photo!=null) { - - } - - @if (item.Parent!=null) { - - @Html.DisplayFor(modelItem => item.Parent) - - } - - @if (item.SettingsClassName!=null) { - - @item.SettingsClassName - - } - - @Html.DisplayFor(modelItem => item.Children) - - @Html.DisplayFor(modelItem => item.Rate) - - Edit - Details - Delete -
diff --git a/src/Yavsc/Pages/Ciba/All.cshtml b/src/Yavsc/Pages/Ciba/All.cshtml deleted file mode 100644 index 59f53037..00000000 --- a/src/Yavsc/Pages/Ciba/All.cshtml +++ /dev/null @@ -1,48 +0,0 @@ -@page -@model Yavsc.Pages.Ciba.AllModel -@{ -} - -
-
-
-
-
-

Pending Backchannel Login Requests

-
-
- @if (Model.Logins.Any()) - { - - - - - - - - - - - @foreach (var login in Model.Logins) - { - - - - - - - } - -
IdClient IdBinding Message
@login.InternalId@login.Client.ClientId@login.BindingMessage - Process -
- } - else - { -
No Pending Login Requests
- } -
-
-
-
-
diff --git a/src/Yavsc/Pages/Ciba/All.cshtml.cs b/src/Yavsc/Pages/Ciba/All.cshtml.cs deleted file mode 100644 index e065eb56..00000000 --- a/src/Yavsc/Pages/Ciba/All.cshtml.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Ciba; - -[SecurityHeaders] -[Authorize] -public class AllModel : PageModel -{ - public IEnumerable Logins { get; set; } = default!; - - private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction; - - public AllModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService) - { - _backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService; - } - - public async Task OnGet() - { - Logins = await _backchannelAuthenticationInteraction.GetPendingLoginRequestsForCurrentUserAsync(); - } -} diff --git a/src/Yavsc/Pages/Ciba/Consent.cshtml b/src/Yavsc/Pages/Ciba/Consent.cshtml deleted file mode 100644 index 6ebe44ab..00000000 --- a/src/Yavsc/Pages/Ciba/Consent.cshtml +++ /dev/null @@ -1,98 +0,0 @@ -@page -@model Yavsc.Pages.Ciba.Consent -@{ -} - - diff --git a/src/Yavsc/Pages/Ciba/Consent.cshtml.cs b/src/Yavsc/Pages/Ciba/Consent.cshtml.cs deleted file mode 100644 index cbfd1f3f..00000000 --- a/src/Yavsc/Pages/Ciba/Consent.cshtml.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Ciba; - -[Authorize] -[SecurityHeaders] -public class Consent : PageModel -{ - private readonly IBackchannelAuthenticationInteractionService _interaction; - private readonly IEventService _events; - private readonly ILogger _logger; - - public Consent( - IBackchannelAuthenticationInteractionService interaction, - IEventService events, - ILogger logger) - { - _interaction = interaction; - _events = events; - _logger = logger; - } - - public ViewModel View { get; set; } = default!; - - [BindProperty] - public InputModel Input { get; set; } = default!; - - public async Task OnGet(string? id) - { - if (!await SetViewModelAsync(id)) - { - return RedirectToPage("/Home/Error/Index"); - } - - Input = new InputModel - { - Id = id - }; - - return Page(); - } - - public async Task OnPost() - { - // validate return url is still valid - var request = await _interaction.GetLoginRequestByInternalIdAsync(Input.Id ?? throw new ArgumentNullException(nameof(Input.Id))); - if (request == null || request.Subject.GetSubjectId() != User.GetSubjectId()) - { - _logger.InvalidId(Input.Id); - return RedirectToPage("/Home/Error/Index"); - } - - CompleteBackchannelLoginRequest? result = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input.Button == "no") - { - result = new CompleteBackchannelLoginRequest(Input.Id); - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); - } - // user clicked 'yes' - validate the data - else if (Input.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - result = new CompleteBackchannelLoginRequest(Input.Id) - { - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false)); - Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false); - var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (result != null) - { - // communicate outcome of consent back to identityserver - await _interaction.CompleteLoginRequestAsync(result); - - return RedirectToPage("/Ciba/All"); - } - - // we need to redisplay the consent UI - if (!await SetViewModelAsync(Input.Id)) - { - return RedirectToPage("/Home/Error/Index"); - } - return Page(); - } - - private async Task SetViewModelAsync(string? id) - { - ArgumentNullException.ThrowIfNull(id); - - var request = await _interaction.GetLoginRequestByInternalIdAsync(id); - if (request != null && request.Subject.GetSubjectId() == User.GetSubjectId()) - { - View = CreateConsentViewModel(request); - return true; - } - else - { - _logger.NoMatchingBackchannelLoginRequest(id); - return false; - } - } - - private ViewModel CreateConsentViewModel(BackchannelUserLoginRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - BindingMessage = request.BindingMessage - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources - .Select(x => CreateScopeViewModel(x, Input == null || Input.ScopesConsented.Contains(x.Name))) - .ToArray(); - - var resourceIndicators = request.RequestedResourceIndicators ?? Enumerable.Empty(); - var apiResources = request.ValidatedResources.Resources.ApiResources.Where(x => resourceIndicators.Contains(x.Name)); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, Input == null || Input.ScopesConsented.Contains(parsedScope.RawValue)); - scopeVm.Resources = apiResources.Where(x => x.Scopes.Contains(parsedScope.ParsedName)) - .Select(x => new ResourceViewModel - { - Name = x.Name, - DisplayName = x.DisplayName ?? x.Name, - }).ToArray(); - apiScopes.Add(scopeVm); - } - } - if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(GetOfflineAccessScope(Input == null || Input.ScopesConsented.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess))); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private static ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Name = identity.Name, - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - private static ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - var displayName = apiScope.DisplayName ?? apiScope.Name; - if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) - { - displayName += ":" + parsedScopeValue.ParsedParameter; - } - - return new ScopeViewModel - { - Name = parsedScopeValue.ParsedName, - Value = parsedScopeValue.RawValue, - DisplayName = displayName, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private static ScopeViewModel GetOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = ConsentOptions.OfflineAccessDisplayName, - Description = ConsentOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} diff --git a/src/Yavsc/Pages/Ciba/ConsentOptions.cs b/src/Yavsc/Pages/Ciba/ConsentOptions.cs deleted file mode 100644 index 31a2db3e..00000000 --- a/src/Yavsc/Pages/Ciba/ConsentOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Ciba; - -public static class ConsentOptions -{ - public static readonly bool EnableOfflineAccess = true; - public static readonly string OfflineAccessDisplayName = "Offline Access"; - public static readonly string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} diff --git a/src/Yavsc/Pages/Ciba/Index.cshtml b/src/Yavsc/Pages/Ciba/Index.cshtml deleted file mode 100644 index 80ea17fe..00000000 --- a/src/Yavsc/Pages/Ciba/Index.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@page -@model Yavsc.Pages.Ciba.IndexModel -@{ -} - -
-
- @if (Model.LoginRequest.Client.LogoUri != null) - { - - } -

- @Model.LoginRequest.Client.ClientName - is requesting your permission -

- -

- Verify that this identifier matches what the client is displaying: - @Model.LoginRequest.BindingMessage -

- -

- Do you wish to continue? -

- - -
-
diff --git a/src/Yavsc/Pages/Ciba/Index.cshtml.cs b/src/Yavsc/Pages/Ciba/Index.cshtml.cs deleted file mode 100644 index e8b151fd..00000000 --- a/src/Yavsc/Pages/Ciba/Index.cshtml.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Ciba; - -[AllowAnonymous] -[SecurityHeaders] -public class IndexModel : PageModel -{ - public BackchannelUserLoginRequest LoginRequest { get; set; } = default!; - - private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction; - private readonly ILogger _logger; - - public IndexModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService, ILogger logger) - { - _backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService; - _logger = logger; - } - - public async Task OnGet(string id) - { - var result = await _backchannelAuthenticationInteraction.GetLoginRequestByInternalIdAsync(id); - if (result == null) - { - _logger.InvalidBackchannelLoginId(id); - return RedirectToPage("/Home/Error/Index"); - } - else - { - LoginRequest = result; - } - - return Page(); - } -} diff --git a/src/Yavsc/Pages/Ciba/InputModel.cs b/src/Yavsc/Pages/Ciba/InputModel.cs deleted file mode 100644 index 224b8131..00000000 --- a/src/Yavsc/Pages/Ciba/InputModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Ciba; - -public class InputModel -{ - public string? Button { get; set; } - public IEnumerable ScopesConsented { get; set; } = new List(); - public string? Id { get; set; } - public string? Description { get; set; } -} diff --git a/src/Yavsc/Pages/Ciba/ViewModel.cs b/src/Yavsc/Pages/Ciba/ViewModel.cs deleted file mode 100644 index baee1cdb..00000000 --- a/src/Yavsc/Pages/Ciba/ViewModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Ciba; - -public class ViewModel -{ - public string? ClientName { get; set; } - public string? ClientUrl { get; set; } - public string? ClientLogoUrl { get; set; } - - public string? BindingMessage { get; set; } - - public IEnumerable IdentityScopes { get; set; } = Enumerable.Empty(); - public IEnumerable ApiScopes { get; set; } = Enumerable.Empty(); -} - -public class ScopeViewModel -{ - public string? Name { get; set; } - public string? Value { get; set; } - public string? DisplayName { get; set; } - public string? Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } - public IEnumerable Resources { get; set; } = Enumerable.Empty(); -} - -public class ResourceViewModel -{ - public string? Name { get; set; } - public string? DisplayName { get; set; } -} diff --git a/src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml b/src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml deleted file mode 100644 index 91280078..00000000 --- a/src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml +++ /dev/null @@ -1,47 +0,0 @@ -@using Yavsc.Pages.Ciba -@model ScopeViewModel - -
  • - - @if (Model.Required) - { - (required) - } - @if (Model.Description != null) - { - - } - @if (Model.Resources?.Any() == true) - { - - } -
  • diff --git a/src/Yavsc/Pages/Consent/ConsentOptions.cs b/src/Yavsc/Pages/Consent/ConsentOptions.cs deleted file mode 100644 index b0bff1ab..00000000 --- a/src/Yavsc/Pages/Consent/ConsentOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Consent; - -public static class ConsentOptions -{ - public static readonly bool EnableOfflineAccess = true; - public static readonly string OfflineAccessDisplayName = "Offline Access"; - public static readonly string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} diff --git a/src/Yavsc/Pages/Consent/Index.cshtml.cs b/src/Yavsc/Pages/Consent/Index.cshtml.cs deleted file mode 100644 index 0d3c4029..00000000 --- a/src/Yavsc/Pages/Consent/Index.cshtml.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using IdentityModel; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Consent; - -[Authorize] -[SecurityHeaders] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - private readonly ILogger _logger; - - public Index( - IIdentityServerInteractionService interaction, - IEventService events, - ILogger logger) - { - _interaction = interaction; - _events = events; - _logger = logger; - } - - public ViewModel View { get; set; } = default!; - - [BindProperty] - public InputModel Input { get; set; } = default!; - - public async Task OnGet(string? returnUrl) - { - if (!await SetViewModelAsync(returnUrl)) - { - return RedirectToPage("/Home/Error/Index"); - } - - Input = new InputModel - { - ReturnUrl = returnUrl, - }; - - return Page(); - } - - public async Task OnPost() - { - // validate return url is still valid - var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); - if (request == null) return RedirectToPage("/Home/Error/Index"); - - ConsentResponse? grantedConsent = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input.Button == "no") - { - grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); - } - // user clicked 'yes' - validate the data - else if (Input.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - grantedConsent = new ConsentResponse - { - RememberConsent = Input.RememberConsent, - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); - var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (grantedConsent != null) - { - ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl)); - - // communicate outcome of consent back to identityserver - await _interaction.GrantConsentAsync(request, grantedConsent); - - // redirect back to authorization endpoint - if (request.IsNativeClient() == true) - { - // The client is native, so this change in how to - // return the response is for better UX for the end user. - return this.LoadingPage(Input.ReturnUrl); - } - - return Redirect(Input.ReturnUrl); - } - - // we need to redisplay the consent UI - if (!await SetViewModelAsync(Input.ReturnUrl)) - { - return RedirectToPage("/Home/Error/Index"); - } - return Page(); - } - - private async Task SetViewModelAsync(string? returnUrl) - { - ArgumentNullException.ThrowIfNull(returnUrl); - - var request = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (request != null) - { - View = CreateConsentViewModel(request); - return true; - } - else - { - _logger.NoConsentMatchingRequest(returnUrl); - return false; - } - } - - private ViewModel CreateConsentViewModel(AuthorizationRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - AllowRememberConsent = request.Client.AllowRememberConsent - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources - .Select(x => CreateScopeViewModel(x, Input == null || Input.ScopesConsented.Contains(x.Name))) - .ToArray(); - - var resourceIndicators = request.Parameters.GetValues(OidcConstants.AuthorizeRequest.Resource) ?? Enumerable.Empty(); - var apiResources = request.ValidatedResources.Resources.ApiResources.Where(x => resourceIndicators.Contains(x.Name)); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, Input == null || Input.ScopesConsented.Contains(parsedScope.RawValue)); - scopeVm.Resources = apiResources.Where(x => x.Scopes.Contains(parsedScope.ParsedName)) - .Select(x => new ResourceViewModel - { - Name = x.Name, - DisplayName = x.DisplayName ?? x.Name, - }).ToArray(); - apiScopes.Add(scopeVm); - } - } - if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(CreateOfflineAccessScope(Input == null || Input.ScopesConsented.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess))); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private static ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Name = identity.Name, - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - private static ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - var displayName = apiScope.DisplayName ?? apiScope.Name; - if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) - { - displayName += ":" + parsedScopeValue.ParsedParameter; - } - - return new ScopeViewModel - { - Name = parsedScopeValue.ParsedName, - Value = parsedScopeValue.RawValue, - DisplayName = displayName, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private static ScopeViewModel CreateOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = ConsentOptions.OfflineAccessDisplayName, - Description = ConsentOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} diff --git a/src/Yavsc/Pages/Consent/InputModel.cs b/src/Yavsc/Pages/Consent/InputModel.cs deleted file mode 100644 index 90c1f21c..00000000 --- a/src/Yavsc/Pages/Consent/InputModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Consent; - -public class InputModel -{ - public string? Button { get; set; } - public IEnumerable ScopesConsented { get; set; } = new List(); - public bool RememberConsent { get; set; } = true; - public string? ReturnUrl { get; set; } - public string? Description { get; set; } -} diff --git a/src/Yavsc/Pages/Consent/ViewModel.cs b/src/Yavsc/Pages/Consent/ViewModel.cs deleted file mode 100644 index 851f758b..00000000 --- a/src/Yavsc/Pages/Consent/ViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Consent; - -public class ViewModel -{ - public string? ClientName { get; set; } - public string? ClientUrl { get; set; } - public string? ClientLogoUrl { get; set; } - public bool AllowRememberConsent { get; set; } - - public IEnumerable IdentityScopes { get; set; } = Enumerable.Empty(); - public IEnumerable ApiScopes { get; set; } = Enumerable.Empty(); -} - -public class ScopeViewModel -{ - public string? Name { get; set; } - public string? Value { get; set; } - public string? DisplayName { get; set; } - public string? Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } - public IEnumerable Resources { get; set; } = Enumerable.Empty(); -} - -public class ResourceViewModel -{ - public string? Name { get; set; } - public string? DisplayName { get; set; } -} diff --git a/src/Yavsc/Pages/Consent/_ScopeListItem.cshtml b/src/Yavsc/Pages/Consent/_ScopeListItem.cshtml deleted file mode 100644 index df8e7983..00000000 --- a/src/Yavsc/Pages/Consent/_ScopeListItem.cshtml +++ /dev/null @@ -1,47 +0,0 @@ -@using Yavsc.Pages.Consent -@model ScopeViewModel - -
  • - - @if (Model.Required) - { - (required) - } - @if (Model.Description != null) - { - - } - @if (Model.Resources?.Any() == true) - { - - } -
  • diff --git a/src/Yavsc/Pages/Device/DeviceOptions.cs b/src/Yavsc/Pages/Device/DeviceOptions.cs deleted file mode 100644 index 7b56d5e1..00000000 --- a/src/Yavsc/Pages/Device/DeviceOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Device; - -public static class DeviceOptions -{ - public static readonly bool EnableOfflineAccess = true; - public static readonly string OfflineAccessDisplayName = "Offline Access"; - public static readonly string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; - - public static readonly string InvalidUserCode = "Invalid user code"; - public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; - public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; -} diff --git a/src/Yavsc/Pages/Device/Index.cshtml b/src/Yavsc/Pages/Device/Index.cshtml deleted file mode 100644 index 110b1e7a..00000000 --- a/src/Yavsc/Pages/Device/Index.cshtml +++ /dev/null @@ -1,141 +0,0 @@ -@page -@model Yavsc.Pages.Device.Index -@{ -} - -@if (Model.Input.UserCode == null) -{ - @*We need to collect the user code*@ -
    -
    -

    User Code

    -

    Please enter the code displayed on your device.

    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - - -
    - - -
    -
    -
    -
    -} -else -{ - @*collect consent for the user code provided*@ -
    -
    - @if (Model.View.ClientLogoUrl != null) - { - - } -

    - @Model.View.ClientName - is requesting your permission -

    -

    Please confirm that the authorization request matches the code: @Model.Input.UserCode.

    -

    Uncheck the permissions you do not wish to grant.

    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - @if (Model.View.IdentityScopes.Any()) - { -
    -
    -
    - - Personal Information -
    -
      - @foreach (var scope in Model.View.IdentityScopes) - { - - } -
    -
    -
    - } - - @if (Model.View.ApiScopes.Any()) - { -
    -
    -
    - - Application Access -
    -
      - @foreach (var scope in Model.View.ApiScopes) - { - - } -
    -
    -
    - } - -
    -
    -
    - - Description -
    -
    - -
    -
    -
    - - @if (Model.View.AllowRememberConsent) - { -
    -
    - - -
    -
    - } -
    -
    - -
    -
    - - -
    -
    - @if (Model.View.ClientUrl != null) - { - - - @Model.View.ClientName - - } -
    -
    -
    -
    -} diff --git a/src/Yavsc/Pages/Device/Index.cshtml.cs b/src/Yavsc/Pages/Device/Index.cshtml.cs deleted file mode 100644 index d5174b4a..00000000 --- a/src/Yavsc/Pages/Device/Index.cshtml.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Configuration; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Validation; -using Yavsc.Pages.Consent; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Options; - -namespace Yavsc.Pages.Device; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - private readonly IDeviceFlowInteractionService _interaction; - private readonly IEventService _events; - private readonly IOptions _options; - private readonly ILogger _logger; - - public Index( - IDeviceFlowInteractionService interaction, - IEventService eventService, - IOptions options, - ILogger logger) - { - _interaction = interaction; - _events = eventService; - _options = options; - _logger = logger; - } - - public ViewModel View { get; set; } = default!; - - [BindProperty] - public InputModel Input { get; set; } = default!; - - public async Task OnGet(string? userCode) - { - if (String.IsNullOrWhiteSpace(userCode)) - { - return Page(); - } - - if (!await SetViewModelAsync(userCode)) - { - ModelState.AddModelError("", DeviceOptions.InvalidUserCode); - return Page(); - } - - Input = new InputModel { - UserCode = userCode, - }; - - return Page(); - } - - public async Task OnPost() - { - var request = await _interaction.GetAuthorizationContextAsync(Input.UserCode ?? throw new ArgumentNullException(nameof(Input.UserCode))); - if (request == null) return RedirectToPage("/Home/Error/Index"); - - ConsentResponse? grantedConsent = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (Input.Button == "no") - { - grantedConsent = new ConsentResponse - { - Error = AuthorizationError.AccessDenied - }; - - // emit event - await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName)); - } - // user clicked 'yes' - validate the data - else if (Input.Button == "yes") - { - // if the user consented to some scope, build the response model - if (Input.ScopesConsented.Any()) - { - var scopes = Input.ScopesConsented; - if (ConsentOptions.EnableOfflineAccess == false) - { - scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); - } - - grantedConsent = new ConsentResponse - { - RememberConsent = Input.RememberConsent, - ScopesValuesConsented = scopes.ToArray(), - Description = Input.Description - }; - - // emit event - await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); - Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent); - var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented); - Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied); - } - else - { - ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage); - } - } - else - { - ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage); - } - - if (grantedConsent != null) - { - // communicate outcome of consent back to identityserver - await _interaction.HandleRequestAsync(Input.UserCode, grantedConsent); - - // indicate that's it ok to redirect back to authorization endpoint - return RedirectToPage("/Device/Success"); - } - - // we need to redisplay the consent UI - if (!await SetViewModelAsync(Input.UserCode)) - { - return RedirectToPage("/Home/Error/Index"); - } - return Page(); - } - - - private async Task SetViewModelAsync(string userCode) - { - var request = await _interaction.GetAuthorizationContextAsync(userCode); - if (request != null) - { - View = CreateConsentViewModel(request); - return true; - } - else - { - View = new ViewModel(); - return false; - } - } - - private ViewModel CreateConsentViewModel(DeviceFlowAuthorizationRequest request) - { - var vm = new ViewModel - { - ClientName = request.Client.ClientName ?? request.Client.ClientId, - ClientUrl = request.Client.ClientUri, - ClientLogoUrl = request.Client.LogoUri, - AllowRememberConsent = request.Client.AllowRememberConsent - }; - - vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, Input == null || Input.ScopesConsented.Contains(x.Name))).ToArray(); - - var apiScopes = new List(); - foreach (var parsedScope in request.ValidatedResources.ParsedScopes) - { - var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); - if (apiScope != null) - { - var scopeVm = CreateScopeViewModel(parsedScope, apiScope, Input == null || Input.ScopesConsented.Contains(parsedScope.RawValue)); - apiScopes.Add(scopeVm); - } - } - if (DeviceOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) - { - apiScopes.Add(GetOfflineAccessScope(Input == null || Input.ScopesConsented.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess))); - } - vm.ApiScopes = apiScopes; - - return vm; - } - - private static ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) - { - return new ScopeViewModel - { - Value = identity.Name, - DisplayName = identity.DisplayName ?? identity.Name, - Description = identity.Description, - Emphasize = identity.Emphasize, - Required = identity.Required, - Checked = check || identity.Required - }; - } - - private static ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) - { - return new ScopeViewModel - { - Value = parsedScopeValue.RawValue, - // todo: use the parsed scope value in the display? - DisplayName = apiScope.DisplayName ?? apiScope.Name, - Description = apiScope.Description, - Emphasize = apiScope.Emphasize, - Required = apiScope.Required, - Checked = check || apiScope.Required - }; - } - - private static ScopeViewModel GetOfflineAccessScope(bool check) - { - return new ScopeViewModel - { - Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, - DisplayName = DeviceOptions.OfflineAccessDisplayName, - Description = DeviceOptions.OfflineAccessDescription, - Emphasize = true, - Checked = check - }; - } -} diff --git a/src/Yavsc/Pages/Device/InputModel.cs b/src/Yavsc/Pages/Device/InputModel.cs deleted file mode 100644 index 7fe79e37..00000000 --- a/src/Yavsc/Pages/Device/InputModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Device; - -public class InputModel -{ - public string? Button { get; set; } - public IEnumerable ScopesConsented { get; set; } = new List(); - public bool RememberConsent { get; set; } = true; - public string? ReturnUrl { get; set; } - public string? Description { get; set; } - public string? UserCode { get; set; } -} diff --git a/src/Yavsc/Pages/Device/Success.cshtml.cs b/src/Yavsc/Pages/Device/Success.cshtml.cs deleted file mode 100644 index b7a1e18e..00000000 --- a/src/Yavsc/Pages/Device/Success.cshtml.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Device; - -[SecurityHeaders] -[Authorize] -public class SuccessModel : PageModel -{ - public void OnGet() - { - } -} diff --git a/src/Yavsc/Pages/Device/ViewModel.cs b/src/Yavsc/Pages/Device/ViewModel.cs deleted file mode 100644 index 11b8bc5d..00000000 --- a/src/Yavsc/Pages/Device/ViewModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Device; - -public class ViewModel -{ - public string? ClientName { get; set; } - public string? ClientUrl { get; set; } - public string? ClientLogoUrl { get; set; } - public bool AllowRememberConsent { get; set; } - - public IEnumerable IdentityScopes { get; set; } = Enumerable.Empty(); - public IEnumerable ApiScopes { get; set; } = Enumerable.Empty(); -} - -public class ScopeViewModel -{ - public string? Value { get; set; } - public string? DisplayName { get; set; } - public string? Description { get; set; } - public bool Emphasize { get; set; } - public bool Required { get; set; } - public bool Checked { get; set; } -} diff --git a/src/Yavsc/Pages/Diagnostics/Index.cshtml b/src/Yavsc/Pages/Diagnostics/Index.cshtml deleted file mode 100644 index f6407a45..00000000 --- a/src/Yavsc/Pages/Diagnostics/Index.cshtml +++ /dev/null @@ -1,67 +0,0 @@ -@page -@model Yavsc.Pages.Diagnostics.Index - -
    -
    -

    Authentication Cookie

    -
    - -
    -
    -
    -
    -

    Claims

    -
    -
    - @if(Model.View.AuthenticateResult.Principal != null) - { -
    - @foreach (var claim in Model.View.AuthenticateResult.Principal.Claims) - { -
    @claim.Type
    -
    @claim.Value
    - } -
    - } -
    -
    -
    - -
    -
    -
    -

    Properties

    -
    -
    -
    - @if (Model.View.AuthenticateResult.Properties != null) - { - @foreach (var prop in Model.View.AuthenticateResult.Properties.Items) - { -
    @prop.Key
    -
    @prop.Value
    - } - } - @if (Model.View.Clients.Any()) - { -
    Clients
    -
    - @{ - var clients = Model.View.Clients.ToArray(); - for(var i = 0; i < clients.Length; i++) - { - @clients[i] - if (i < clients.Length - 1) - { - , - } - } - } -
    - } -
    -
    -
    -
    -
    -
    diff --git a/src/Yavsc/Pages/Diagnostics/Index.cshtml.cs b/src/Yavsc/Pages/Diagnostics/Index.cshtml.cs deleted file mode 100644 index 602dde31..00000000 --- a/src/Yavsc/Pages/Diagnostics/Index.cshtml.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.AspNetCore.Authorization; - -namespace Yavsc.Pages.Diagnostics; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - public ViewModel View { get; set; } = default!; - - public async Task OnGet() - { - var localAddresses = new List { "127.0.0.1", "::1" }; - if(HttpContext.Connection.LocalIpAddress != null) - { - localAddresses.Add(HttpContext.Connection.LocalIpAddress.ToString()); - } - - if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress?.ToString())) - { - return NotFound(); - } - - View = new ViewModel(await HttpContext.AuthenticateAsync()); - - return Page(); - } -} diff --git a/src/Yavsc/Pages/Diagnostics/ViewModel.cs b/src/Yavsc/Pages/Diagnostics/ViewModel.cs deleted file mode 100644 index 85eaab03..00000000 --- a/src/Yavsc/Pages/Diagnostics/ViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using IdentityModel; -using Microsoft.AspNetCore.Authentication; -using System.Text; -using System.Text.Json; - -namespace Yavsc.Pages.Diagnostics; - -public class ViewModel -{ - public ViewModel(AuthenticateResult result) - { - AuthenticateResult = result; - - if (result?.Properties?.Items.TryGetValue("client_list", out var encoded) == true) - { - if (encoded != null) - { - var bytes = Base64Url.Decode(encoded); - var value = Encoding.UTF8.GetString(bytes); - Clients = JsonSerializer.Deserialize(value) ?? Enumerable.Empty(); - return; - } - } - Clients = Enumerable.Empty(); - } - - public AuthenticateResult AuthenticateResult { get; } - public IEnumerable Clients { get; } -} diff --git a/src/Yavsc/Pages/Extensions.cs b/src/Yavsc/Pages/Extensions.cs deleted file mode 100644 index bb2679ef..00000000 --- a/src/Yavsc/Pages/Extensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages; - -public static class Extensions -{ - /// - /// Determines if the authentication scheme support signout. - /// - internal static async Task GetSchemeSupportsSignOutAsync(this HttpContext context, string scheme) - { - var provider = context.RequestServices.GetRequiredService(); - var handler = await provider.GetHandlerAsync(context, scheme); - return (handler is IAuthenticationSignOutHandler); - } - - /// - /// Checks if the redirect URI is for a native client. - /// - internal static bool IsNativeClient(this AuthorizationRequest context) - { - return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) - && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); - } - - /// - /// Renders a loading page that is used to redirect back to the redirectUri. - /// - internal static IActionResult LoadingPage(this PageModel page, string? redirectUri) - { - page.HttpContext.Response.StatusCode = 200; - page.HttpContext.Response.Headers["Location"] = ""; - - return page.RedirectToPage("/Redirect/Index", new { RedirectUri = redirectUri }); - } -} diff --git a/src/Yavsc/Pages/ExternalLogin/Callback.cshtml b/src/Yavsc/Pages/ExternalLogin/Callback.cshtml deleted file mode 100644 index 78d997ec..00000000 --- a/src/Yavsc/Pages/ExternalLogin/Callback.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@page -@model Yavsc.Pages.ExternalLogin.Callback - -@{ - Layout = null; -} - - - - - - - - -
    - -
    - - diff --git a/src/Yavsc/Pages/ExternalLogin/Callback.cshtml.cs b/src/Yavsc/Pages/ExternalLogin/Callback.cshtml.cs deleted file mode 100644 index 3d104dec..00000000 --- a/src/Yavsc/Pages/ExternalLogin/Callback.cshtml.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System.Security.Claims; -using Duende.IdentityServer; -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Services; -using IdentityModel; -using Yavsc.Models; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.ExternalLogin; - -[AllowAnonymous] -[SecurityHeaders] -public class Callback : PageModel -{ - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IIdentityServerInteractionService _interaction; - private readonly ILogger _logger; - private readonly IEventService _events; - - public Callback( - IIdentityServerInteractionService interaction, - IEventService events, - ILogger logger, - UserManager userManager, - SignInManager signInManager) - { - _userManager = userManager; - _signInManager = signInManager; - _interaction = interaction; - _logger = logger; - _events = events; - } - - public async Task OnGet() - { - // read external identity from the temporary cookie - var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); - if (result.Succeeded != true) - { - throw new InvalidOperationException($"External authentication error: { result.Failure }"); - } - - var externalUser = result.Principal ?? - throw new InvalidOperationException("External authentication produced a null Principal"); - - if (_logger.IsEnabled(LogLevel.Debug)) - { - var externalClaims = externalUser.Claims.Select(c => $"{c.Type}: {c.Value}"); - _logger.ExternalClaims(externalClaims); - } - - // lookup our user and external provider info - // try to determine the unique id of the external user (issued by the provider) - // the most common claim type for that are the sub claim and the NameIdentifier - // depending on the external provider, some other claim type might be used - var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? - externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? - throw new InvalidOperationException("Unknown userid"); - - var provider = result.Properties.Items["scheme"] ?? throw new InvalidOperationException("Null scheme in authentiation properties"); - var providerUserId = userIdClaim.Value; - - // find external user - var user = await _userManager.FindByLoginAsync(provider, providerUserId); - if (user == null) - { - // this might be where you might initiate a custom workflow for user registration - // in this sample we don't show how that would be done, as our sample implementation - // simply auto-provisions new external user - user = await AutoProvisionUserAsync(provider, providerUserId, externalUser.Claims); - } - - // this allows us to collect any additional claims or properties - // for the specific protocols used and store them in the local auth cookie. - // this is typically used to store data needed for signout from those protocols. - var additionalLocalClaims = new List(); - var localSignInProps = new AuthenticationProperties(); - CaptureExternalLoginContext(result, additionalLocalClaims, localSignInProps); - - // issue authentication cookie for user - await _signInManager.SignInWithClaimsAsync(user, localSignInProps, additionalLocalClaims); - - // delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); - - // retrieve return URL - var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; - - // check if external login is in the context of an OIDC request - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, user.UserName, true, context?.Client.ClientId)); - Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); - - if (context != null) - { - if (context.IsNativeClient()) - { - // The client is native, so this change in how to - // return the response is for better UX for the end user. - return this.LoadingPage(returnUrl); - } - } - - return Redirect(returnUrl); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1851:Possible multiple enumerations of 'IEnumerable' collection", Justification = "")] - private async Task AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable claims) - { - var sub = Guid.NewGuid().ToString(); - - var user = new ApplicationUser - { - Id = sub, - UserName = sub, // don't need a username, since the user will be using an external provider to login - }; - - // email - var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ?? - claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; - if (email != null) - { - user.Email = email; - } - - // create a list of claims that we want to transfer into our store - var filtered = new List(); - - // user's display name - var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ?? - claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; - if (name != null) - { - filtered.Add(new Claim(JwtClaimTypes.Name, name)); - } - else - { - var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ?? - claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value; - var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ?? - claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value; - if (first != null && last != null) - { - filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last)); - } - else if (first != null) - { - filtered.Add(new Claim(JwtClaimTypes.Name, first)); - } - else if (last != null) - { - filtered.Add(new Claim(JwtClaimTypes.Name, last)); - } - } - - var identityResult = await _userManager.CreateAsync(user); - if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description); - - if (filtered.Count != 0) - { - identityResult = await _userManager.AddClaimsAsync(user, filtered); - if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description); - } - - identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider)); - if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description); - - return user; - } - - // if the external login is OIDC-based, there are certain things we need to preserve to make logout work - // this will be different for WS-Fed, SAML2p or other protocols - private static void CaptureExternalLoginContext(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) - { - ArgumentNullException.ThrowIfNull(externalResult.Principal, nameof(externalResult.Principal)); - - // capture the idp used to login, so the session knows where the user came from - localClaims.Add(new Claim(JwtClaimTypes.IdentityProvider, externalResult.Properties?.Items["scheme"] ?? "unknown identity provider")); - - // if the external system sent a session id claim, copy it over - // so we can use it for single sign-out - var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); - if (sid != null) - { - localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); - } - - // if the external provider issued an id_token, we'll keep it for signout - var idToken = externalResult.Properties?.GetTokenValue("id_token"); - if (idToken != null) - { - localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); - } - } -} diff --git a/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml b/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml deleted file mode 100644 index 7d17fe30..00000000 --- a/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@page -@model Yavsc.Pages.ExternalLogin.Challenge - -@{ - Layout = null; -} - - - - - - - - -
    - -
    - - diff --git a/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml.cs b/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml.cs deleted file mode 100644 index aa628b78..00000000 --- a/src/Yavsc/Pages/ExternalLogin/Challenge.cshtml.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.ExternalLogin; - -[AllowAnonymous] -[SecurityHeaders] -public class Challenge : PageModel -{ - private readonly IIdentityServerInteractionService _interactionService; - - public Challenge(IIdentityServerInteractionService interactionService) - { - _interactionService = interactionService; - } - - public IActionResult OnGet(string scheme, string? returnUrl) - { - if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; - - // validate returnUrl - either it is a valid OIDC URL or back to a local page - if (Url.IsLocalUrl(returnUrl) == false && _interactionService.IsValidReturnUrl(returnUrl) == false) - { - // user might have clicked on a malicious link - should be logged - throw new ArgumentException("invalid return URL"); - } - - // start challenge and roundtrip the return URL and scheme - var props = new AuthenticationProperties - { - RedirectUri = Url.Page("/externallogin/callback"), - - Items = - { - { "returnUrl", returnUrl }, - { "scheme", scheme }, - } - }; - - return Challenge(props, scheme); - } -} diff --git a/src/Yavsc/Pages/Grants/Index.cshtml.cs b/src/Yavsc/Pages/Grants/Index.cshtml.cs deleted file mode 100644 index 95dec497..00000000 --- a/src/Yavsc/Pages/Grants/Index.cshtml.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Events; -using Duende.IdentityServer.Extensions; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Grants; - -[SecurityHeaders] -[Authorize] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clients; - private readonly IResourceStore _resources; - private readonly IEventService _events; - - public Index(IIdentityServerInteractionService interaction, - IClientStore clients, - IResourceStore resources, - IEventService events) - { - _interaction = interaction; - _clients = clients; - _resources = resources; - _events = events; - } - - public ViewModel View { get; set; } = default!; - - public async Task OnGet() - { - var grants = await _interaction.GetAllUserGrantsAsync(); - - var list = new List(); - foreach (var grant in grants) - { - var client = await _clients.FindClientByIdAsync(grant.ClientId); - if (client != null) - { - var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); - - var item = new GrantViewModel() - { - ClientId = client.ClientId, - ClientName = client.ClientName ?? client.ClientId, - ClientLogoUrl = client.LogoUri, - ClientUrl = client.ClientUri, - Description = grant.Description, - Created = grant.CreationTime, - Expires = grant.Expiration, - IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), - ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() - }; - - list.Add(item); - } - } - - View = new ViewModel - { - Grants = list - }; - } - - [BindProperty] - public string? ClientId { get; set; } - - public async Task OnPost() - { - await _interaction.RevokeUserConsentAsync(ClientId); - await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId)); - Telemetry.Metrics.GrantsRevoked(ClientId); - - return RedirectToPage("/Grants/Index"); - } -} diff --git a/src/Yavsc/Pages/Grants/ViewModel.cs b/src/Yavsc/Pages/Grants/ViewModel.cs deleted file mode 100644 index 64ba1c5c..00000000 --- a/src/Yavsc/Pages/Grants/ViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages.Grants; - -public class ViewModel -{ - public IEnumerable Grants { get; set; } = Enumerable.Empty(); -} - -public class GrantViewModel -{ - public string? ClientId { get; set; } - public string? ClientName { get; set; } - public string? ClientUrl { get; set; } - public string? ClientLogoUrl { get; set; } - public string? Description { get; set; } - public DateTime Created { get; set; } - public DateTime? Expires { get; set; } - public IEnumerable IdentityGrantNames { get; set; } = Enumerable.Empty(); - public IEnumerable ApiGrantNames { get; set; } = Enumerable.Empty(); -} diff --git a/src/Yavsc/Pages/Home/Error/Index.cshtml b/src/Yavsc/Pages/Home/Error/Index.cshtml deleted file mode 100644 index 24932210..00000000 --- a/src/Yavsc/Pages/Home/Error/Index.cshtml +++ /dev/null @@ -1,35 +0,0 @@ -@page -@model Yavsc.Pages.Error.Index - -
    -
    -

    Error

    -
    - -
    -
    -
    - Sorry, there was an error - - @if (Model.View.Error != null) - { - - - : @Model.View.Error.Error - - - - if (Model.View.Error.ErrorDescription != null) - { -
    @Model.View.Error.ErrorDescription
    - } - } -
    - - @if (Model?.View?.Error?.RequestId != null) - { -
    Request Id: @Model.View.Error.RequestId
    - } -
    -
    -
    diff --git a/src/Yavsc/Pages/Home/Error/Index.cshtml.cs b/src/Yavsc/Pages/Home/Error/Index.cshtml.cs deleted file mode 100644 index ee770434..00000000 --- a/src/Yavsc/Pages/Home/Error/Index.cshtml.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Error; - -[AllowAnonymous] -[SecurityHeaders] -public class Index : PageModel -{ - private readonly IIdentityServerInteractionService _interaction; - private readonly IWebHostEnvironment _environment; - - public ViewModel View { get; set; } = new(); - - public Index(IIdentityServerInteractionService interaction, IWebHostEnvironment environment) - { - _interaction = interaction; - _environment = environment; - } - - public async Task OnGet(string? errorId) - { - // retrieve error details from identityserver - var message = await _interaction.GetErrorContextAsync(errorId); - if (message != null) - { - View.Error = message; - - if (!_environment.IsDevelopment()) - { - // only show in development - message.ErrorDescription = null; - } - } - } -} diff --git a/src/Yavsc/Pages/Home/Error/ViewModel.cs b/src/Yavsc/Pages/Home/Error/ViewModel.cs deleted file mode 100644 index 02879c34..00000000 --- a/src/Yavsc/Pages/Home/Error/ViewModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; - -namespace Yavsc.Pages.Error; - -public class ViewModel -{ - public ViewModel() - { - } - - public ViewModel(string error) - { - Error = new ErrorMessage { Error = error }; - } - - public ErrorMessage? Error { get; set; } -} diff --git a/src/Yavsc/Pages/IdentityServerSuppressions.cs b/src/Yavsc/Pages/IdentityServerSuppressions.cs deleted file mode 100644 index 3d69e2c7..00000000 --- a/src/Yavsc/Pages/IdentityServerSuppressions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -// global/shared -[assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "Consistent with the IdentityServer APIs")] -[assembly: SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = "Consistent with the IdentityServer APIs")] -[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "No need for ConfigureAwait in ASP.NET Core application code, as there is no SynchronizationContext.")] - -// page specific -[assembly: SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "TestUsers are not designed to be extended", Scope = "member", Target = "~P:Yavsc.TestUsers.Users")] -[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "ExternalProvider is nested by design", Scope = "type", Target = "~T:Yavsc.Pages.Login.ViewModel.ExternalProvider")] -[assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "This namespace is just for organization, and won't be referenced elsewhere", Scope = "namespace", Target = "~N:Yavsc.Pages.Error")] -[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Namespaces of pages are not likely to be used elsewhere, so there is little chance of confusion", Scope = "type", Target = "~T:Yavsc.Pages.Ciba.Consent")] -[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Namespaces of pages are not likely to be used elsewhere, so there is little chance of confusion", Scope = "type", Target = "~T:Yavsc.Pages.Extensions")] -[assembly: SuppressMessage("Performance", "CA1805:Do not initialize unnecessarily", Justification = "This is for clarity and consistency with the surrounding code", Scope = "member", Target = "~F:Yavsc.Pages.Logout.LogoutOptions.AutomaticRedirectAfterSignOut")] diff --git a/src/Yavsc/Pages/Index.cshtml b/src/Yavsc/Pages/Index.cshtml deleted file mode 100644 index c45bd3f0..00000000 --- a/src/Yavsc/Pages/Index.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@page -@model Yavsc.Pages.Home.Index - -
    -

    - - Welcome to Duende IdentityServer - (version @Model.Version) -

    - -
      -
    • - IdentityServer publishes a - discovery document - where you can find metadata and links to all the endpoints, key material, etc. -
    • -
    • - Click here to see the claims for your current session. -
    • -
    • - Click here to manage your stored grants. -
    • -
    • - Click here to view the server side sessions. -
    • -
    • - Click here to view your pending CIBA login requests. -
    • -
    • - Here are links to the - source code repository, - and ready to use samples. -
    • -
    - - @if(Model.License != null) - { -

    License

    -
    -
    Serial Number
    -
    @Model.License.SerialNumber
    -
    Expiration
    -
    @Model.License.Expiration!.Value.ToLongDateString()
    -
    - } -
    diff --git a/src/Yavsc/Pages/Index.cshtml.cs b/src/Yavsc/Pages/Index.cshtml.cs deleted file mode 100644 index d6a8361a..00000000 --- a/src/Yavsc/Pages/Index.cshtml.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer; -using System.Reflection; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Home; - -[AllowAnonymous] -public class Index : PageModel -{ - public Index(IdentityServerLicense? license = null) - { - License = license; - } - - public string Version - { - get => typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly - .GetCustomAttribute() - ?.InformationalVersion.Split('+').First() - ?? "unavailable"; - } - public IdentityServerLicense? License { get; } -} diff --git a/src/Yavsc/Pages/Log.cs b/src/Yavsc/Pages/Log.cs deleted file mode 100644 index ac1bbbd0..00000000 --- a/src/Yavsc/Pages/Log.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -namespace Yavsc.Pages; - -internal static class Log -{ - private static readonly Action _invalidId = LoggerMessage.Define( - LogLevel.Error, - EventIds.InvalidId, - "Invalid id {Id}"); - - public static void InvalidId(this ILogger logger, string? id) - { - _invalidId(logger, id, null); - } - - private static readonly Action _invalidBackchannelLoginId = LoggerMessage.Define( - LogLevel.Warning, - EventIds.InvalidBackchannelLoginId, - "Invalid backchannel login id {Id}"); - - public static void InvalidBackchannelLoginId(this ILogger logger, string? id) - { - _invalidBackchannelLoginId(logger, id, null); - } - - private static Action, Exception?> _externalClaims = LoggerMessage.Define>( - LogLevel.Debug, - EventIds.ExternalClaims, - "External claims: {Claims}"); - - public static void ExternalClaims(this ILogger logger, IEnumerable claims) - { - _externalClaims(logger, claims, null); - } - - private static Action _noMatchingBackchannelLoginRequest = LoggerMessage.Define( - LogLevel.Error, - EventIds.NoMatchingBackchannelLoginRequest, - "No backchannel login request matching id: {Id}"); - - public static void NoMatchingBackchannelLoginRequest(this ILogger logger, string id) - { - _noMatchingBackchannelLoginRequest(logger, id, null); - } - - private static Action _noConsentMatchingRequest = LoggerMessage.Define( - LogLevel.Error, - EventIds.NoConsentMatchingRequest, - "No consent request matching request: {ReturnUrl}"); - - public static void NoConsentMatchingRequest(this ILogger logger, string returnUrl) - { - _noConsentMatchingRequest(logger, returnUrl, null); - } - - -} - -internal static class EventIds -{ - private const int UIEventsStart = 10000; - - ////////////////////////////// - // Consent - ////////////////////////////// - private const int ConsentEventsStart = UIEventsStart + 1000; - public const int InvalidId = ConsentEventsStart + 0; - public const int NoConsentMatchingRequest = ConsentEventsStart + 1; - - ////////////////////////////// - // External Login - ////////////////////////////// - private const int ExternalLoginEventsStart = UIEventsStart + 2000; - public const int ExternalClaims = ExternalLoginEventsStart + 0; - - ////////////////////////////// - // CIBA - ////////////////////////////// - private const int CibaEventsStart = UIEventsStart + 3000; - public const int InvalidBackchannelLoginId = CibaEventsStart + 0; - public const int NoMatchingBackchannelLoginRequest = CibaEventsStart + 1; - - - -} diff --git a/src/Yavsc/Pages/Redirect/Index.cshtml b/src/Yavsc/Pages/Redirect/Index.cshtml deleted file mode 100644 index e6271a3a..00000000 --- a/src/Yavsc/Pages/Redirect/Index.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@page -@model Yavsc.Pages.Redirect.IndexModel -@{ -} - -
    -
    -

    You are now being returned to the application

    -

    Once complete, you may close this tab.

    -
    -
    - - - diff --git a/src/Yavsc/Pages/Redirect/Index.cshtml.cs b/src/Yavsc/Pages/Redirect/Index.cshtml.cs deleted file mode 100644 index 3e1b52b4..00000000 --- a/src/Yavsc/Pages/Redirect/Index.cshtml.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.Redirect; - -[AllowAnonymous] -public class IndexModel : PageModel -{ - public string? RedirectUri { get; set; } - - public IActionResult OnGet(string? redirectUri) - { - if (!Url.IsLocalUrl(redirectUri)) - { - return RedirectToPage("/Home/Error/Index"); - } - - RedirectUri = redirectUri; - return Page(); - } -} diff --git a/src/Yavsc/Pages/SecurityHeadersAttribute.cs b/src/Yavsc/Pages/SecurityHeadersAttribute.cs deleted file mode 100644 index 60ac1040..00000000 --- a/src/Yavsc/Pages/SecurityHeadersAttribute.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages; - -public sealed class SecurityHeadersAttribute : ActionFilterAttribute -{ - public override void OnResultExecuting(ResultExecutingContext context) - { - ArgumentNullException.ThrowIfNull(context, nameof(context)); - - var result = context.Result; - if (result is PageResult) - { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) - { - context.HttpContext.Response.Headers.Append("X-Content-Type-Options", "nosniff"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) - { - context.HttpContext.Response.Headers.Append("X-Frame-Options", "DENY"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy - //var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - // also consider adding upgrade-insecure-requests once you have HTTPS in place for production - //csp += "upgrade-insecure-requests;"; - // also an example if you need client images to be displayed from twitter - // csp += "img-src 'self' https://pbs.twimg.com;"; - - // once for standards compliant browsers - if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Append("Content-Security-Policy", csp); - } - // and once again for IE - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Append("X-Content-Security-Policy", csp); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy - var referrer_policy = "no-referrer"; - if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) - { - context.HttpContext.Response.Headers.Append("Referrer-Policy", referrer_policy); - } - } - } -} diff --git a/src/Yavsc/Pages/ServerSideSessions/Index.cshtml b/src/Yavsc/Pages/ServerSideSessions/Index.cshtml deleted file mode 100644 index 7a51c857..00000000 --- a/src/Yavsc/Pages/ServerSideSessions/Index.cshtml +++ /dev/null @@ -1,147 +0,0 @@ -@page -@model Yavsc.Pages.ServerSideSessions.IndexModel - -
    -
    -
    -
    -
    -

    User Sessions

    -
    - -
    - - @if (Model.UserSessions != null) - { -
    -
    - @if (Model.UserSessions.HasPrevResults) - { - Prev - } -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    -
    - @if (Model.UserSessions.HasNextResults) - { - Next - } -
    -
    - - @if (Model.UserSessions.TotalCount.HasValue) - { -
    - @if (Model.UserSessions.CurrentPage.HasValue && Model.UserSessions.TotalPages.HasValue) - { - - Total Results: @Model.UserSessions.TotalCount, - Page @Model.UserSessions.CurrentPage of @Model.UserSessions.TotalPages - - } - else - { - - Total Results: @Model.UserSessions.TotalCount - - } -
    - } - -
    - - @if (Model.UserSessions.Results.Any()) - { -
    - - - - - - - - - - - - - @foreach (var session in Model.UserSessions.Results) - { - - - - - - - - - - } - -
    Subject IdSession IdDisplay NameCreatedExpires
    @session.SubjectId@session.SessionId@session.DisplayName@session.Created@session.Expires -
    - - -
    -
    - Clients: - @if (session.ClientIds?.Any() == true) - { - @(session.ClientIds.Aggregate((x, y) => $"{x}, {y}")) - } - else - { - @("None") - } -
    -
    - } - else - { -
    No User Sessions
    - } - } - else - { -
    -
    - You do not have server-side sessions enabled. - To do so, use AddServerSideSessions on your IdentityServer configuration. - See the documentation for more information. -
    -
    - } -
    -
    -
    -
    -
    diff --git a/src/Yavsc/Pages/ServerSideSessions/Index.cshtml.cs b/src/Yavsc/Pages/ServerSideSessions/Index.cshtml.cs deleted file mode 100644 index 43d1e15c..00000000 --- a/src/Yavsc/Pages/ServerSideSessions/Index.cshtml.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Duende.IdentityServer.Models; -using Duende.IdentityServer.Services; -using Duende.IdentityServer.Stores; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Yavsc.Pages.ServerSideSessions -{ - public class IndexModel : PageModel - { - private readonly ISessionManagementService? _sessionManagementService; - - public IndexModel(ISessionManagementService? sessionManagementService = null) - { - _sessionManagementService = sessionManagementService; - } - - public QueryResult? UserSessions { get; set; } - - [BindProperty(SupportsGet = true)] - public string? DisplayNameFilter { get; set; } - - [BindProperty(SupportsGet = true)] - public string? SessionIdFilter { get; set; } - - [BindProperty(SupportsGet = true)] - public string? SubjectIdFilter { get; set; } - - [BindProperty(SupportsGet = true)] - public string? Token { get; set; } - - [BindProperty(SupportsGet = true)] - public string? Prev { get; set; } - - public async Task OnGet() - { - if (_sessionManagementService != null) - { - UserSessions = await _sessionManagementService.QuerySessionsAsync(new SessionQuery - { - ResultsToken = Token, - RequestPriorResults = Prev == "true", - DisplayName = DisplayNameFilter, - SessionId = SessionIdFilter, - SubjectId = SubjectIdFilter - }); - } - } - - [BindProperty] - public string? SessionId { get; set; } - - public async Task OnPost() - { - ArgumentNullException.ThrowIfNull(_sessionManagementService); - - await _sessionManagementService.RemoveSessionsAsync(new RemoveSessionsContext { - SessionId = SessionId, - }); - return RedirectToPage("/ServerSideSessions/Index", new { Token, DisplayNameFilter, SessionIdFilter, SubjectIdFilter, Prev }); - } - } -} - diff --git a/src/Yavsc/Pages/Shared/_Layout.cshtml b/src/Yavsc/Pages/Shared/_Layout.cshtml deleted file mode 100644 index c2a97694..00000000 --- a/src/Yavsc/Pages/Shared/_Layout.cshtml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - Yavsc - - - - - - - - -
    - - - -
    - @RenderBody() -
    - -
    - - @RenderSection("scripts", required: false) - - diff --git a/src/Yavsc/Pages/Shared/_Nav.cshtml b/src/Yavsc/Pages/Shared/_Nav.cshtml deleted file mode 100644 index d4a147c1..00000000 --- a/src/Yavsc/Pages/Shared/_Nav.cshtml +++ /dev/null @@ -1,45 +0,0 @@ -@using Duende.IdentityServer.Extensions -@{ - #nullable enable - string? name = null; - if (Context.User!=null) - { - name = Context.User.GetDisplayName(); - } -} - - diff --git a/src/Yavsc/Pages/Telemetry.cs b/src/Yavsc/Pages/Telemetry.cs deleted file mode 100644 index a826a379..00000000 --- a/src/Yavsc/Pages/Telemetry.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System.Diagnostics.Metrics; - -namespace Yavsc.Pages; - -#pragma warning disable CA1034 // Nested types should not be visible -#pragma warning disable CA1724 // Type names should not match namespaces - -/// -/// Telemetry helpers for the UI -/// -public static class Telemetry -{ - private static readonly string ServiceVersion = typeof(Telemetry).Assembly.GetName().Version!.ToString(); - - /// - /// Service name for telemetry. - /// - public static readonly string ServiceName = typeof(Telemetry).Assembly.GetName().Name!; - - /// - /// Metrics configuration - /// - public static class Metrics - { -#pragma warning disable 1591 - - /// - /// Name of Counters - /// - public static class Counters - { - public const string Consent = "tokenservice.consent"; - public const string GrantsRevoked = "tokenservice.grants_revoked"; - public const string UserLogin = "tokenservice.user_login"; - public const string UserLogout = "tokenservice.user_logout"; - } - - /// - /// Name of tags - /// - public static class Tags - { - public const string Client = "client"; - public const string Error = "error"; - public const string Idp = "idp"; - public const string Remember = "remember"; - public const string Scope = "scope"; - public const string Consent = "consent"; - } - - /// - /// Values of tags - /// - public static class TagValues - { - public const string Granted = "granted"; - public const string Denied = "denied"; - } - -#pragma warning restore 1591 - - /// - /// Meter for the IdentityServer host project - /// - private static readonly Meter Meter = new Meter(ServiceName, ServiceVersion); - - private static Counter ConsentCounter = Meter.CreateCounter(Counters.Consent); - - /// - /// Helper method to increase counter. The scopes - /// are expanded and called one by one to not cause a combinatory explosion of scopes. - /// - /// Client id - /// Scope names. Each element is added on it's own to the counter - public static void ConsentGranted(string clientId, IEnumerable scopes, bool remember) - { - ArgumentNullException.ThrowIfNull(scopes); - - foreach (var scope in scopes) - { - ConsentCounter.Add(1, - new(Tags.Client, clientId), - new(Tags.Scope, scope), - new(Tags.Remember, remember), - new(Tags.Consent, TagValues.Granted)); - } - } - - /// - /// Helper method to increase counter. The scopes - /// are expanded and called one by one to not cause a combinatory explosion of scopes. - /// - /// Client id - /// Scope names. Each element is added on it's own to the counter - public static void ConsentDenied(string clientId, IEnumerable scopes) - { - ArgumentNullException.ThrowIfNull(scopes); - foreach (var scope in scopes) - { - ConsentCounter.Add(1, new(Tags.Client, clientId), new(Tags.Scope, scope), new(Tags.Consent, TagValues.Denied)); - } - } - - private static Counter GrantsRevokedCounter = Meter.CreateCounter(Counters.GrantsRevoked); - - /// - /// Helper method to increase the counter. - /// - /// Client id to revoke for, or null for all. - public static void GrantsRevoked(string? clientId) - => GrantsRevokedCounter.Add(1, tag: new(Tags.Client, clientId)); - - private static Counter UserLoginCounter = Meter.CreateCounter(Counters.UserLogin); - - /// - /// Helper method to increase counter. - /// - /// Client Id, if available - public static void UserLogin(string? clientId, string idp) - => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp)); - - /// - /// Helper method to increase - /// Client Id, if available - /// Error message - public static void UserLoginFailure(string? clientId, string idp, string error) - => UserLoginCounter.Add(1, new(Tags.Client, clientId), new(Tags.Idp, idp), new(Tags.Error, error)); - - private static Counter UserLogoutCounter = Meter.CreateCounter(Counters.UserLogout); - - /// - /// Helper method to increase the counter. - /// - /// Idp/authentication scheme for external authentication, or "local" for built in. - public static void UserLogout(string? idp) - => UserLogoutCounter.Add(1, tag: new(Tags.Idp, idp)); - } -} diff --git a/src/Yavsc/Pages/_ViewImports.cshtml b/src/Yavsc/Pages/_ViewImports.cshtml deleted file mode 100644 index e995e50a..00000000 --- a/src/Yavsc/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@using Yavsc -@using Yavsc.Pages -@using Yavsc.Models -@using Yavsc.Models.Musical -@using Yavsc.Models.Drawing -@using Yavsc.Models.Haircut -@using Yavsc.Models.Blog -@using Yavsc.Models.Billing -@using Yavsc.Models.Workflow -@using Yavsc.Models.Relationship -@using Yavsc.Models.Musical.Profiles -@using Yavsc.Models.Auth -@using Yavsc.Models.Identity -@using Yavsc.Models.Access -@using Yavsc.Billing -@using Yavsc.Server.Models.Calendar -@using Yavsc.ViewModels.Haircut -@using Yavsc.ViewModels.Administration -@using Yavsc.ViewModels.Account -@using Yavsc.ViewModels.Manage -@using Yavsc.ViewModels.FrontOffice -@using Yavsc.ViewModels.Calendar -@using Yavsc.ViewModels.Relationship -@using Yavsc.ViewModels.Workflow -@using Yavsc.ViewModels.Auth -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Mvc -@using Microsoft.AspNetCore.Html -@using Microsoft.AspNetCore.Identity -@using Yavsc.Helpers - -@using PayPal.PayPalAPIInterfaceService.Model -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Yavsc - -@inject IAuthorizationService AuthorizationService -@inject Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer SR -@inject Microsoft.Extensions.Options.IOptions SiteSettings -@inject SignInManager SignInManager -@inject UserManager UserManager diff --git a/src/Yavsc/Pages/_ViewStart.cshtml b/src/Yavsc/Pages/_ViewStart.cshtml deleted file mode 100644 index a5f10045..00000000 --- a/src/Yavsc/Pages/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/Yavsc/Program.cs b/src/Yavsc/Program.cs index 4294a230..58893f8a 100644 --- a/src/Yavsc/Program.cs +++ b/src/Yavsc/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore; +using Yavsc.Extensions; namespace Yavsc { @@ -8,9 +9,13 @@ namespace Yavsc public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); - var app = builder - .ConfigureServices() - .ConfigurePipeline(); + builder.Configuration + .AddEnvironmentVariables() + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables().Build(); + var app = builder.ConfigureServices().ConfigurePipeline(); + app.UseSession(); app.Run(); } } diff --git a/src/Yavsc/Services/YavscClientStore.cs b/src/Yavsc/Services/YavscClientStore.cs new file mode 100644 index 00000000..7accd231 --- /dev/null +++ b/src/Yavsc/Services/YavscClientStore.cs @@ -0,0 +1,66 @@ + +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.EntityFrameworkCore; +using Yavsc.Models; + +namespace Yavsc.Services; + +public class YavscClientStore : IClientStore +{ + ApplicationDbContext _context=null; + public YavscClientStore(ApplicationDbContext context) + { + _context = context; + } + + + async Task IClientStore.FindClientByIdAsync(string clientId) + { + var app = await _context.Applications.FirstOrDefaultAsync(c=>c.Id == clientId); + + if (app == null) return null; + + Client client = new() + { + ClientId = app.Id, + ClientName = app.DisplayName, + AbsoluteRefreshTokenLifetime = app.RefreshTokenLifeTime, + AccessTokenLifetime = app.AccessTokenLifetime, + AllowedGrantTypes = + [ + GrantType.AuthorizationCode, + GrantType.DeviceFlow, + GrantType.ClientCredentials + ], + ClientSecrets = [ + new Secret(app.Secret), + ] + }; + + + switch(app.Type) + { + case Models.Auth.ApplicationTypes.NativeConfidential: + client.AccessTokenType = AccessTokenType.Reference; + client.AllowedGrantTypes = + [ + GrantType.DeviceFlow + ]; + client.AllowedScopes = [] ; + break; + case Models.Auth.ApplicationTypes.JavaScript: + default: + client.AccessTokenType = AccessTokenType.Jwt; + client.AllowedGrantTypes = + [ + GrantType.AuthorizationCode, + GrantType.ClientCredentials + ]; + client.AllowedScopes = ["openid", "profile"]; + break; + + } + return client; + } +} diff --git a/src/Yavsc/Startup.cs b/src/Yavsc/Startup.cs index 7ad9cb82..1625debf 100644 --- a/src/Yavsc/Startup.cs +++ b/src/Yavsc/Startup.cs @@ -61,14 +61,13 @@ public class Startup Config.SiteSetup.DataDir = "AppData"+environmentName; logger.LogInformation("Using: "+Config.SiteSetup.DataDir); } else logger.LogInformation("Using value from settings: "+Config.SiteSetup.DataDir); - DirectoryInfo di = new DirectoryInfo(Config.SiteSetup.DataDir); + DirectoryInfo di = new DirectoryInfo(Config.SiteSetup.DataDir); if (!di.Exists) { di.Create(); logger.LogWarning("Created dir : "+di.FullName); } - else logger.LogInformation("Using existing directory: "+di.Name); - Config.SiteSetup.DataDir = Path.Combine(Directory.GetCurrentDirectory(),di.Name); + logger.LogInformation("Using existing directory: "+di.FullName); Environment.SetEnvironmentVariable("APPDATA", Config.SiteSetup.DataDir); logger.LogWarning("It has been set to : "+Environment.GetEnvironmentVariable("APPDATA")); } diff --git a/src/Yavsc/ViewComponents/DirectoryViewComponent.cs b/src/Yavsc/ViewComponents/DirectoryViewComponent.cs index 82099a14..b5f5b902 100644 --- a/src/Yavsc/ViewComponents/DirectoryViewComponent.cs +++ b/src/Yavsc/ViewComponents/DirectoryViewComponent.cs @@ -1,6 +1,8 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Yavsc.Helpers; +using Yavsc.Models; using Yavsc.ViewModels.UserFiles; namespace Yavsc.ViewComponents @@ -8,13 +10,25 @@ namespace Yavsc.ViewComponents public class DirectoryViewComponent : ViewComponent { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + public DirectoryViewComponent(UserManager userManager, + SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + public async Task InvokeAsync(string dirname) { + string uid = ViewContext.HttpContext.User.GetUserId(); + IViewComponentResult result = null; - await Task.Run(() => - { - result = View(new UserDirectoryInfo(AbstractFileSystemHelpers.UserFilesDirName, User.Identity.Name, dirname)); - }); + + result = View(new UserDirectoryInfo( + AbstractFileSystemHelpers.UserFilesDirName, + uid, dirname)); + return result; } } diff --git a/src/Yavsc/ViewModels/Account/ExternalProvider.cs b/src/Yavsc/ViewModels/Account/ExternalProvider.cs new file mode 100644 index 00000000..37feb97d --- /dev/null +++ b/src/Yavsc/ViewModels/Account/ExternalProvider.cs @@ -0,0 +1,8 @@ +namespace Yavsc.ViewModels.Account +{ + public class ExternalProvider + { + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } + } +} diff --git a/src/Yavsc/ViewModels/Account/LoginInputModel.cs b/src/Yavsc/ViewModels/Account/LoginInputModel.cs new file mode 100644 index 00000000..7ac21ab6 --- /dev/null +++ b/src/Yavsc/ViewModels/Account/LoginInputModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Yavsc.ViewModels.Account +{ + public class LoginInputModel + { + [Required] + public string Username { get; set; } + [Required] + public string Password { get; set; } + public bool RememberLogin { get; set; } + public string ReturnUrl { get; set; } + } +} diff --git a/src/Yavsc/ViewModels/Account/LoginViewModel.cs b/src/Yavsc/ViewModels/Account/LoginViewModel.cs index 39e96f40..9dcaa925 100644 --- a/src/Yavsc/ViewModels/Account/LoginViewModel.cs +++ b/src/Yavsc/ViewModels/Account/LoginViewModel.cs @@ -1,15 +1,14 @@ namespace Yavsc.ViewModels.Account { - public class LoginViewModel + public class LoginViewModel : LoginInputModel { - public string UserName { get; set; } + public bool AllowRememberLogin { get; set; } = true; + public bool EnableLocalLogin { get; set; } = true; - public string Password { get; set; } - - public bool RememberMe { get; set; } + public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); - public string ReturnUrl { get; set; } - - public string Provider { get; set; } + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; } } diff --git a/src/Yavsc/ViewModels/Account/RedirectViewModel.cs b/src/Yavsc/ViewModels/Account/RedirectViewModel.cs new file mode 100644 index 00000000..7ef6c83d --- /dev/null +++ b/src/Yavsc/ViewModels/Account/RedirectViewModel.cs @@ -0,0 +1,7 @@ +namespace Yavsc.ViewModels.Account +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} diff --git a/src/Yavsc/Views/Account/AccessDenied.cshtml b/src/Yavsc/Views/Account/AccessDenied.cshtml index 6d3c1c60..0745189e 100644 --- a/src/Yavsc/Views/Account/AccessDenied.cshtml +++ b/src/Yavsc/Views/Account/AccessDenied.cshtml @@ -1,18 +1,7 @@ -@{ - ViewData["Title"] = "Forbidden"; -} -

    @ViewData["Title"]

    - -L'accès à cette ressource est protégé. - -@if (ViewBag.UserIsSignedIn) { - -@:Vous ne possédez pas les droits suffisants pour y acceder. - -Login avec un autre compte - -} -else { - - Login -} + +
    +
    +

    Access Denied

    +

    You do not have access to that resource.

    +
    +
    \ No newline at end of file diff --git a/src/Yavsc/Views/Account/AccountCreated.cshtml b/src/Yavsc/Views/Account/AccountCreated.cshtml deleted file mode 100644 index b2d5b8f6..00000000 --- a/src/Yavsc/Views/Account/AccountCreated.cshtml +++ /dev/null @@ -1,10 +0,0 @@ -@{ - ViewData["Title"] = "Account creation success"; - -} - -

    @ViewData["Title"]

    - -Your account has successfully been created. - -Return to home diff --git a/src/Yavsc/Views/Account/AccountCreated.fr.cshtml b/src/Yavsc/Views/Account/AccountCreated.fr.cshtml deleted file mode 100644 index 9c2a3499..00000000 --- a/src/Yavsc/Views/Account/AccountCreated.fr.cshtml +++ /dev/null @@ -1,11 +0,0 @@ -@{ - ViewData["Title"] = "Succès de la création du compte"; - -} - -

    @ViewData["Title"]

    - -Votre compte a été créé. -Vous devrez confirmer votre addresse e-mail. - -Retourner à l'accueil diff --git a/src/Yavsc/Views/Account/AdminDelete.cshtml b/src/Yavsc/Views/Account/AdminDelete.cshtml deleted file mode 100644 index d2f3c2ac..00000000 --- a/src/Yavsc/Views/Account/AdminDelete.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@model UnregisterViewModel -@{ - ViewData["Title"] = "Unregister"; -} - -

    @ViewData["Title"].

    - -
    - - @Html.Hidden("UserId") - @Html.Hidden("ReturnUrl") - -
    - diff --git a/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml b/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml deleted file mode 100755 index 3b11a55f..00000000 --- a/src/Yavsc/Views/Account/AdminSendConfirationEmail.cshtml +++ /dev/null @@ -1,12 +0,0 @@ -@model Yavsc.Abstract.Manage.EmailSentViewModel - -@{ - ViewData["Title"] = "S'il vous plait, veuillez confirmer votre adresse e-mail"; -} - -

    @ViewData["Title"].

    -
    -

    - Un message vient d' être envoyé à l'adresse e-mail ( @Model.EMail , id:@Model.MessageId ). -

    -
    diff --git a/src/Yavsc/Views/Account/Delete.cshtml b/src/Yavsc/Views/Account/Delete.cshtml deleted file mode 100644 index aa192767..00000000 --- a/src/Yavsc/Views/Account/Delete.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@model UnregisterViewModel -@{ - ViewData["Title"] = "Unregister"; -} - -

    @ViewData["Title"].

    - -
    - - @Html.Hidden("ReturnUrl") - @Html.Hidden("UserId") - -
    - diff --git a/src/Yavsc/Views/Account/EmailConfirmed.cshtml b/src/Yavsc/Views/Account/EmailConfirmed.cshtml deleted file mode 100755 index 95b67828..00000000 --- a/src/Yavsc/Views/Account/EmailConfirmed.cshtml +++ /dev/null @@ -1,15 +0,0 @@ -@{ - ViewData["Title"] = "Confirmation de votre adresse e-mail"; -} - -

    @ViewData["Title"].

    -
    -

    - Merci d’avoir confirmé votre e-mail. - @if (User.GetUserId()==null) { - S’il vous plait, - Cliquez ici pour vous connecter. - - } -

    -
    diff --git a/src/Yavsc/Views/Account/ExternalLoginConfirmation.en.cshtml b/src/Yavsc/Views/Account/ExternalLoginConfirmation.en.cshtml deleted file mode 100755 index dc020b1c..00000000 --- a/src/Yavsc/Views/Account/ExternalLoginConfirmation.en.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@model ExternalLoginConfirmationViewModel -@{ - ViewData["Title"] = "Register"; -} - -

    @ViewData["Title"].

    -

    Assoiciez votre compte @ViewData["LoginProvider"].

    - -
    -

    Formulaire d'Association

    -
    -
    - -

    - You've successfully authenticated with @ViewData["LoginProvider"]. - Please enter a user name for this site below and click the Register button to finish - logging in. -

    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/ExternalLoginConfirmation.fr.cshtml b/src/Yavsc/Views/Account/ExternalLoginConfirmation.fr.cshtml deleted file mode 100755 index 4c6dd45c..00000000 --- a/src/Yavsc/Views/Account/ExternalLoginConfirmation.fr.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@model ExternalLoginConfirmationViewModel -@{ - ViewData["Title"] = "Register"; -} - -

    @ViewData["Title"].

    -

    Associez votre compte @ViewData["LoginProvider"].

    - -
    -

    Formulaire d'Association

    -
    -
    - -

    - Vous vous êtes authentifié avec succès auprès de @ViewData["LoginProvider"]. - Veuillez s'il vous plait, saisir un nom d'utilisateur ci-dessous - et activer le boutton "Enregister" pour terminer votre inscription. -

    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/ExternalLoginFailure.cshtml b/src/Yavsc/Views/Account/ExternalLoginFailure.cshtml deleted file mode 100755 index 26cbebdf..00000000 --- a/src/Yavsc/Views/Account/ExternalLoginFailure.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@{ - ViewData["Title"] = "Login Failure"; -} - -
    -

    @ViewData["Title"].

    -

    Unsuccessful login with service.

    -
    diff --git a/src/Yavsc/Views/Account/ForgotPassword.cshtml b/src/Yavsc/Views/Account/ForgotPassword.cshtml old mode 100755 new mode 100644 index 5dc36da5..e2d7a02d --- a/src/Yavsc/Views/Account/ForgotPassword.cshtml +++ b/src/Yavsc/Views/Account/ForgotPassword.cshtml @@ -1,34 +1,9 @@ @model ForgotPasswordViewModel -@{ - ViewData["Title"] = "Forgot your password?"; -} -

    @ViewData["Title"]

    - -
    -

    Enter your user name or e-mail.

    -
    -
    +
    - -
    - @if (User.Identity.IsAuthenticated) { - - @ViewBag.UserEmail - - } else { - - } - -
    -
    -
    -
    - -
    + +
    +
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/ForgotPasswordConfirmation.cshtml b/src/Yavsc/Views/Account/ForgotPasswordConfirmation.cshtml old mode 100755 new mode 100644 index cf7805e0..d1e50615 --- a/src/Yavsc/Views/Account/ForgotPasswordConfirmation.cshtml +++ b/src/Yavsc/Views/Account/ForgotPasswordConfirmation.cshtml @@ -1,8 +1,5 @@ -@{ - ViewData["Title"] = "Forgot Password Confirmation"; -} +@model string + + +

    Check your mail box!

    -

    @ViewData["Title"].

    -

    - PleaseCheckYourEmail -

    diff --git a/src/Yavsc/Views/Account/Index.cshtml b/src/Yavsc/Views/Account/Index.cshtml deleted file mode 100644 index cbd1aab8..00000000 --- a/src/Yavsc/Views/Account/Index.cshtml +++ /dev/null @@ -1,6 +0,0 @@ - -@{ - ViewData["Title"] = "Index"; -} - -UserList diff --git a/src/Yavsc/Views/Account/Lockout.cshtml b/src/Yavsc/Views/Account/Lockout.cshtml deleted file mode 100755 index 48334089..00000000 --- a/src/Yavsc/Views/Account/Lockout.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@{ - ViewData["Title"] = "Locked out"; -} - -
    -

    Locked out.

    -

    This account has been locked out, please try again later.

    -
    diff --git a/src/Yavsc/Views/Account/LoggedOut.cshtml b/src/Yavsc/Views/Account/LoggedOut.cshtml new file mode 100644 index 00000000..dc9fbf7a --- /dev/null +++ b/src/Yavsc/Views/Account/LoggedOut.cshtml @@ -0,0 +1,34 @@ +@model LoggedOutViewModel + +@{ + // set this so the layout rendering sees an anonymous user + ViewData["signed-out"] = true; +} + +
    +

    + Logout + You are now logged out +

    + + @if (Model.PostLogoutRedirectUri != null) + { +
    + Click here to return to the + @Model.ClientName application. +
    + } + + @if (Model.SignOutIframeUrl != null) + { + + } +
    + +@section scripts +{ + @if (Model.AutomaticRedirectAfterSignOut) + { + + } +} diff --git a/src/Yavsc/Views/Account/Login.cshtml b/src/Yavsc/Views/Account/Login.cshtml old mode 100755 new mode 100644 index 9f331405..e4ccb1d8 --- a/src/Yavsc/Views/Account/Login.cshtml +++ b/src/Yavsc/Views/Account/Login.cshtml @@ -1,64 +1,87 @@ +@model LoginViewModel -@model Yavsc.ViewModels.Account.LoginViewModel -@{ - ViewData["Title"] = "Log in"; -} - -
    -

    @ViewData["Title"]

    -
    - -

    Use a local account to log in

    -
    - -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -

    - Register as a new user? -

    -

    - Forgot your password? -

    - - - - @Html.AntiForgeryToken() -
    - -
    -

    Use another service to log in:

    - -
    -

    - There are no external authentication services configured. See this article - for details on setting up this ASP.NET application to support logging in via external services. -

    + + + + +
    + + @if (Model.EnableLocalLogin) + { +
    +
    +
    +

    Local Account

    +
    + +
    +
    + + +
    + + +
    +
    + + +
    + @if (Model.AllowRememberLogin) + { +
    +
    + + +
    +
    + } + + +
    +
    +
    +
    + } + + @if (Model.VisibleExternalProviders.Any()) + { +
    +
    +
    +

    External Account

    +
    +
    + +
    +
    +
    + } + + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
    + Invalid login request + There are no login schemes configured for this request. +
    + } +
    +
    \ No newline at end of file diff --git a/src/Yavsc/Pages/Account/Logout/Index.cshtml b/src/Yavsc/Views/Account/Logout.cshtml similarity index 65% rename from src/Yavsc/Pages/Account/Logout/Index.cshtml rename to src/Yavsc/Views/Account/Logout.cshtml index 8a31356d..b49dee09 100644 --- a/src/Yavsc/Pages/Account/Logout/Index.cshtml +++ b/src/Yavsc/Views/Account/Logout.cshtml @@ -1,15 +1,13 @@ -@page -@model Yavsc.Pages.Logout.Index +@model LogoutViewModel

    Logout

    -

    Would you like to logout of IdentityServer?

    +

    Would you like to logut of IdentityServer?

    -
    + -
    diff --git a/src/Yavsc/Views/Account/Register.cshtml b/src/Yavsc/Views/Account/Register.cshtml deleted file mode 100755 index d5d8fd50..00000000 --- a/src/Yavsc/Views/Account/Register.cshtml +++ /dev/null @@ -1,54 +0,0 @@ -@model RegisterModel -@{ - ViewData["Title"] = "Register"; -} - -@section header{ - - -} - -

    @ViewData["Title"].

    - - -

    Create a new account.

    -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/Register.fr.cshtml b/src/Yavsc/Views/Account/Register.fr.cshtml deleted file mode 100755 index 5870cedc..00000000 --- a/src/Yavsc/Views/Account/Register.fr.cshtml +++ /dev/null @@ -1,54 +0,0 @@ -@model RegisterModel -@{ - ViewData["Title"] = "Register"; -} - -@section header{ - - -} - -

    @ViewData["Title"].

    - -
    -

    Create a new account.

    -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/ResetPassword.cshtml b/src/Yavsc/Views/Account/ResetPassword.cshtml old mode 100755 new mode 100644 index 1a96997a..ef1c8ce4 --- a/src/Yavsc/Views/Account/ResetPassword.cshtml +++ b/src/Yavsc/Views/Account/ResetPassword.cshtml @@ -1,42 +1,15 @@ @model ResetPasswordViewModel -@{ - ViewData["Title"] = "Reset password"; -} -

    @ViewData["Title"].

    +
    +

    @Model.Email

    +
    + + +
    +
    + + +
    + - -

    Reset your password.

    -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/ResetPasswordConfirmation.cshtml b/src/Yavsc/Views/Account/ResetPasswordConfirmation.cshtml deleted file mode 100755 index bedabfcd..00000000 --- a/src/Yavsc/Views/Account/ResetPasswordConfirmation.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@{ - ViewData["Title"] = "Reset password confirmation"; -} - -

    @ViewData["Title"].

    -

    - Your password has been reset."] Please"] Click here to log in. -

    diff --git a/src/Yavsc/Views/Account/SendCode.cshtml b/src/Yavsc/Views/Account/SendCode.cshtml deleted file mode 100755 index 0f0e4628..00000000 --- a/src/Yavsc/Views/Account/SendCode.cshtml +++ /dev/null @@ -1,21 +0,0 @@ -@model SendCodeViewModel -@{ - ViewData["Title"] = "Send Verification Code"; -} - -

    @ViewData["Title"].

    - -
    - -
    -
    - Select Two-Factor Authentication Provider: - - -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Account/SendConfirationEmail.cshtml b/src/Yavsc/Views/Account/SendConfirationEmail.cshtml deleted file mode 100755 index adee0f55..00000000 --- a/src/Yavsc/Views/Account/SendConfirationEmail.cshtml +++ /dev/null @@ -1,13 +0,0 @@ -@model Yavsc.Abstract.Manage.EmailSentViewModel - -@{ - ViewData["Title"] = "S'il vous plait, veuillez confirmer votre adresse e-mail"; -} - -

    @ViewData["Title"].

    -
    -

    - Un message vient de vous être envoyé, à votre adresse e-mail ( @Model.EMail , id:@Model.MessageId ), - Veuillez s’il vous plait confirmer cette adresse de courrier en utilisant le lien hyper-texte qui s'y trouve. -

    -
    diff --git a/src/Yavsc/Views/Account/UserList.cshtml b/src/Yavsc/Views/Account/UserList.cshtml deleted file mode 100644 index 258e3db7..00000000 --- a/src/Yavsc/Views/Account/UserList.cshtml +++ /dev/null @@ -1,68 +0,0 @@ -@model ApplicationUser[] -@{ - ViewData["Title"] = "Liste des utilisateurs"; -} - -

    @ViewData["Title"].

    - - - - - - -@foreach (var user in Model) -{ - - - - -} -
    - Public info"] - - AdminOnly"] -
    - @Html.DisplayFor(m=>user) - -
    -
    - UserName -
    -
    - @Html.DisplayFor(m=>user.UserName) -
    -
    -
    -
    - FullName -
    -
    - @Html.DisplayFor(m=>user.FullName) -
    -
    -
    -
    - PostalAddress"] -
    -
    - @Html.DisplayFor(m=>user.PostalAddress) -
    -
    -
    -
    - Email -
    -
    - @Html.DisplayFor(m=>user.Email) - @if (!user.EmailConfirmed) { - Envoyer une demande de confirmation - } - Supprimer -
    -
    -
    - -@if (ViewBag.hasNext) -{ - Next page -} diff --git a/src/Yavsc/Views/Account/VerifyCode.cshtml b/src/Yavsc/Views/Account/VerifyCode.cshtml deleted file mode 100755 index a1d42e4d..00000000 --- a/src/Yavsc/Views/Account/VerifyCode.cshtml +++ /dev/null @@ -1,39 +0,0 @@ -@model VerifyCodeViewModel -@{ - ViewData["Title"] = "Verify"; -} - -

    @ViewData["Title"].

    - -
    -
    - - - @Html.Hidden("ReturnUrl") -

    @ViewData["Status"]

    -
    -
    - -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    - -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} diff --git a/src/Yavsc/Views/Administration/Index.cshtml b/src/Yavsc/Views/Administration/Index.cshtml index a201dcd2..b0c5edfd 100644 --- a/src/Yavsc/Views/Administration/Index.cshtml +++ b/src/Yavsc/Views/Administration/Index.cshtml @@ -8,7 +8,7 @@

    Rôles

      @foreach (var role in Model.Roles) { -
    • @Html.ActionLink(role.Name,"Role",new { id=role.Id }) (@role.Users.Length membres)
    • +
    • @Html.ActionLink(role.Name,"Role",new { id=role.Id }) (@role.UserCount membres)
    • }

    Administrateurs

    diff --git a/src/Yavsc/Pages/Device/Success.cshtml b/src/Yavsc/Views/Device/Success.cshtml similarity index 75% rename from src/Yavsc/Pages/Device/Success.cshtml rename to src/Yavsc/Views/Device/Success.cshtml index ff4688d2..050dd91c 100644 --- a/src/Yavsc/Pages/Device/Success.cshtml +++ b/src/Yavsc/Views/Device/Success.cshtml @@ -1,8 +1,3 @@ -@page -@model Yavsc.Pages.Device.SuccessModel -@{ -} -
    diff --git a/src/Yavsc/Views/Device/UserCodeCapture.cshtml b/src/Yavsc/Views/Device/UserCodeCapture.cshtml new file mode 100644 index 00000000..6d412613 --- /dev/null +++ b/src/Yavsc/Views/Device/UserCodeCapture.cshtml @@ -0,0 +1,23 @@ +@model string + +
    +
    +

    User Code

    +

    Please enter the code displayed on your device.

    +
    + + + +
    +
    +
    +
    + + +
    + + +
    +
    +
    +
    diff --git a/src/Yavsc/Pages/Consent/Index.cshtml b/src/Yavsc/Views/Device/UserCodeConfirmation.cshtml similarity index 69% rename from src/Yavsc/Pages/Consent/Index.cshtml rename to src/Yavsc/Views/Device/UserCodeConfirmation.cshtml index 526a3a5f..e1d3b196 100644 --- a/src/Yavsc/Pages/Consent/Index.cshtml +++ b/src/Yavsc/Views/Device/UserCodeConfirmation.cshtml @@ -1,18 +1,19 @@ -@page -@model Yavsc.Pages.Consent.Index -@{ -} +@model DeviceAuthorizationViewModel - -
    - + +
    - @if (Model.View.IdentityScopes.Any()) + @if (Model.IdentityScopes.Any()) {
    @@ -35,7 +36,7 @@ Personal Information
      - @foreach (var scope in Model.View.IdentityScopes) + @foreach (var scope in Model.IdentityScopes) { } @@ -44,7 +45,7 @@
    } - @if (Model.View.ApiScopes.Any()) + @if (Model.ApiScopes.Any()) {
    @@ -53,7 +54,7 @@ Application Access
      - @foreach (var scope in Model.View.ApiScopes) + @foreach (var scope in Model.ApiScopes) { } @@ -65,21 +66,21 @@
      - + Description
      - +
      - @if (Model.View.AllowRememberConsent) + @if (Model.AllowRememberConsent) {
      - -
      @@ -90,15 +91,15 @@
      - - + +
      - @if (Model.View.ClientUrl != null) + @if (Model.ClientUrl != null) { - + - @Model.View.ClientName + @Model.ClientName }
      diff --git a/src/Yavsc/Views/Devices/Delete.cshtml b/src/Yavsc/Views/Devices/Delete.cshtml deleted file mode 100644 index 279c6abb..00000000 --- a/src/Yavsc/Views/Devices/Delete.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model Yavsc.Models.Identity.DeviceDeclaration - -@{ - ViewData["Title"] = "Delete"; -} - -

      Delete

      - -

      AreYourSureYouWantToDeleteThis

      -
      -

      DeviceDeclaration

      -
      -
      -
      - @Html.DisplayNameFor(model => model.DeclarationDate) -
      -
      - @Html.DisplayFor(model => model.DeclarationDate) -
      -
      - @Html.DisplayNameFor(model => model.Model) -
      -
      - @Html.DisplayFor(model => model.Model) -
      -
      - @Html.DisplayNameFor(model => model.Platform) -
      -
      - @Html.DisplayFor(model => model.Platform) -
      -
      - @Html.DisplayNameFor(model => model.Version) -
      -
      - @Html.DisplayFor(model => model.Version) -
      -
      - - -
      - | - Back to List -
      - -
      diff --git a/src/Yavsc/Views/Devices/Details.cshtml b/src/Yavsc/Views/Devices/Details.cshtml deleted file mode 100644 index df5423ad..00000000 --- a/src/Yavsc/Views/Devices/Details.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@model Yavsc.Models.Identity.DeviceDeclaration - -@{ - ViewData["Title"] = "Details"; -} - -

      Details

      - -
      -

      DeviceDeclaration

      -
      -
      -
      - @Html.DisplayNameFor(model => model.DeclarationDate) -
      -
      - @Html.DisplayFor(model => model.DeclarationDate) -
      -
      - @Html.DisplayNameFor(model => model.Model) -
      -
      - @Html.DisplayFor(model => model.Model) -
      -
      - @Html.DisplayNameFor(model => model.Platform) -
      -
      - @Html.DisplayFor(model => model.Platform) -
      -
      - @Html.DisplayNameFor(model => model.Version) -
      -
      - @Html.DisplayFor(model => model.Version) -
      -
      -
      -

      - Edit | - Back to List -

      diff --git a/src/Yavsc/Views/Devices/Index.cshtml b/src/Yavsc/Views/Devices/Index.cshtml deleted file mode 100644 index 41c5ae98..00000000 --- a/src/Yavsc/Views/Devices/Index.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Index"; -} - -

      Index

      - - - - - - - - - - -@foreach (var item in Model) { - - - - - - - -} -
      - @Html.DisplayNameFor(model => model.DeclarationDate) - - @Html.DisplayNameFor(model => model.Model) - - @Html.DisplayNameFor(model => model.Platform) - - @Html.DisplayNameFor(model => model.Version) -
      - @Html.DisplayFor(modelItem => item.DeclarationDate) - - @Html.DisplayFor(modelItem => item.Model) - - @Html.DisplayFor(modelItem => item.Platform) - - @Html.DisplayFor(modelItem => item.Version) - - Details | - Delete -
      diff --git a/src/Yavsc/Views/Diagnostics/Index.cshtml b/src/Yavsc/Views/Diagnostics/Index.cshtml new file mode 100644 index 00000000..e939c0d8 --- /dev/null +++ b/src/Yavsc/Views/Diagnostics/Index.cshtml @@ -0,0 +1,64 @@ +@model DiagnosticsViewModel + +
      +
      +

      Authentication Cookie

      +
      + +
      +
      +
      +
      +

      Claims

      +
      +
      +
      + @foreach (var claim in Model.AuthenticateResult.Principal.Claims) + { +
      @claim.Type
      +
      @claim.Value
      + } +
      +
      +
      +
      + +
      +
      +
      +

      Properties

      +
      +
      +
      + @foreach (var prop in Model.AuthenticateResult.Properties.Items) + { +
      @prop.Key
      +
      @prop.Value
      + } + @if (Model.Clients.Any()) + { +
      Clients
      +
      + @{ + var clients = Model.Clients.ToArray(); + for(var i = 0; i < clients.Length; i++) + { + @clients[i] + if (i < clients.Length - 1) + { + , + } + } + } +
      + } +
      +
      +
      +
      +
      +
      + + + + diff --git a/src/Yavsc/Pages/Grants/Index.cshtml b/src/Yavsc/Views/Grants/Index.cshtml similarity index 94% rename from src/Yavsc/Pages/Grants/Index.cshtml rename to src/Yavsc/Views/Grants/Index.cshtml index 50b76485..a60f2261 100644 --- a/src/Yavsc/Pages/Grants/Index.cshtml +++ b/src/Yavsc/Views/Grants/Index.cshtml @@ -1,7 +1,4 @@ -@page -@model Yavsc.Pages.Grants.Index -@{ -} +@model GrantsViewModel
      @@ -9,7 +6,7 @@

      Below is the list of applications you have given permission to and the resources they have access to.

      - @if (!Model.View.Grants.Any()) + @if (Model.Grants.Any() == false) {
      @@ -21,7 +18,7 @@ } else { - foreach (var grant in Model.View.Grants) + foreach (var grant in Model.Grants) {
      @@ -35,7 +32,7 @@
      -
      +
      @@ -87,4 +84,4 @@
      } } -
      +
      \ No newline at end of file diff --git a/src/Yavsc/Views/Home/About.cshtml b/src/Yavsc/Views/Home/About.cshtml index fe2f2436..264cf39a 100755 --- a/src/Yavsc/Views/Home/About.cshtml +++ b/src/Yavsc/Views/Home/About.cshtml @@ -1,3 +1,4 @@ +@using System.Diagnostics @{ ViewData["Title"] = SR["About"] + " " + SiteSettings.Value.Title; } @@ -93,3 +94,35 @@ L'opération est annulable, jusqu'à deux semaines après sa programmation.

      @Model

      + +@{ + var version = FileVersionInfo.GetVersionInfo(typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First(); +} + +
      +

      + + Welcome to IdentityServer4 + (version @version) +

      + + +
      + diff --git a/src/Yavsc/Views/Home/AboutAccess.cshtml b/src/Yavsc/Views/Home/AboutAccess.cshtml index a7ae8fa2..06b16e7a 100644 --- a/src/Yavsc/Views/Home/AboutAccess.cshtml +++ b/src/Yavsc/Views/Home/AboutAccess.cshtml @@ -36,3 +36,5 @@ Et ils possèdent une addresse permanente de la forme : + + diff --git a/src/Yavsc/Views/Home/Index.cshtml b/src/Yavsc/Views/Home/Index.cshtml index aa918379..e7f45d1d 100755 --- a/src/Yavsc/Views/Home/Index.cshtml +++ b/src/Yavsc/Views/Home/Index.cshtml @@ -127,3 +127,4 @@ et sans lui, la procédure en place semble cassée. https://forgejo.pschneider.fr/[Mon nouveau SI de tickets, je l´adopte, il fonctionne, les inscriptions sont ouvertes, et libres.] + diff --git a/src/Yavsc/Views/Shared/_Layout.cshtml b/src/Yavsc/Views/Shared/_Layout.cshtml index 68435765..eec40733 100644 --- a/src/Yavsc/Views/Shared/_Layout.cshtml +++ b/src/Yavsc/Views/Shared/_Layout.cshtml @@ -7,7 +7,7 @@ - + @await RenderSectionAsync("header", false) @@ -38,9 +38,11 @@ © 2024 - Yavsc - Privacy
      - - + + + + @await RenderSectionAsync("scripts", false) diff --git a/src/Yavsc/Views/Shared/_LoginPartial.cshtml b/src/Yavsc/Views/Shared/_LoginPartial.cshtml index 50ddb69c..ad05de95 100644 --- a/src/Yavsc/Views/Shared/_LoginPartial.cshtml +++ b/src/Yavsc/Views/Shared/_LoginPartial.cshtml @@ -41,21 +41,21 @@ } else { } diff --git a/src/Yavsc/Views/Shared/_Nav.cshtml b/src/Yavsc/Views/Shared/_Nav.cshtml index 962d74cf..a3b64533 100644 --- a/src/Yavsc/Views/Shared/_Nav.cshtml +++ b/src/Yavsc/Views/Shared/_Nav.cshtml @@ -1,5 +1,5 @@ -@using Duende.IdentityServer.Extensions -
    diff --git a/src/Yavsc/Pages/Device/_ScopeListItem.cshtml b/src/Yavsc/Views/Shared/_ScopeListItem.cshtml similarity index 85% rename from src/Yavsc/Pages/Device/_ScopeListItem.cshtml rename to src/Yavsc/Views/Shared/_ScopeListItem.cshtml index 2f12a173..15eebcbd 100644 --- a/src/Yavsc/Pages/Device/_ScopeListItem.cshtml +++ b/src/Yavsc/Views/Shared/_ScopeListItem.cshtml @@ -1,11 +1,10 @@ -@using Yavsc.Pages.Device -@model ScopeViewModel +@model ScopeViewModel
  • } - + \ No newline at end of file diff --git a/src/Yavsc/Pages/Shared/_ValidationSummary.cshtml b/src/Yavsc/Views/Shared/_ValidationSummary.cshtml similarity index 100% rename from src/Yavsc/Pages/Shared/_ValidationSummary.cshtml rename to src/Yavsc/Views/Shared/_ValidationSummary.cshtml diff --git a/src/Yavsc/Views/_ViewImports.cshtml b/src/Yavsc/Views/_ViewImports.cshtml index 9f8bcede..fb3c305e 100755 --- a/src/Yavsc/Views/_ViewImports.cshtml +++ b/src/Yavsc/Views/_ViewImports.cshtml @@ -29,6 +29,8 @@ @using Yavsc.Helpers; @using PayPal.PayPalAPIInterfaceService.Model; +@using Yavsc.Models.Access; + @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Yavsc @@ -37,3 +39,4 @@ @inject Microsoft.Extensions.Options.IOptions SiteSettings @inject SignInManager SignInManager @inject UserManager UserManager + diff --git a/src/Yavsc/Yavsc.csproj b/src/Yavsc/Yavsc.csproj index 958fb79f..51a096ab 100644 --- a/src/Yavsc/Yavsc.csproj +++ b/src/Yavsc/Yavsc.csproj @@ -12,8 +12,10 @@ all + + + - diff --git a/src/Yavsc/wwwroot/css/site.scss b/src/Yavsc/wwwroot/css/site.scss index 68718246..56280459 100644 --- a/src/Yavsc/wwwroot/css/site.scss +++ b/src/Yavsc/wwwroot/css/site.scss @@ -23,3 +23,20 @@ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } + +/* bootstrap.css | http://localhost:5000/lib/bootstrap/css/bootstrap.css */ + +.nav-link { + /* background-color: var(--bs-nav-tabs-link-active-bg); */ + background-color: black; +} + +.dropdown-item { + /* background-color: transparent; */ + background-color: black; +} + +.dropdown-menu { + /* background-color: var(--bs-dropdown-bg); */ + background-color: black; +} diff --git a/src/Yavsc/wwwroot/js/signin-redirect.js b/src/Yavsc/wwwroot/js/signin-redirect js similarity index 100% rename from src/Yavsc/wwwroot/js/signin-redirect.js rename to src/Yavsc/wwwroot/js/signin-redirect js