From c351d71129923ee47f9bf3aa73d01b2e80c57d91 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 2 Mar 2025 16:53:36 +0000 Subject: [PATCH] Track vscode setup --- .gitignore | 1 - .vscode/launch.json | 88 ++++++++++ .vscode/settings.json | 21 +++ .vscode/tasks.json | 96 +++++++++++ src/Yavsc.Server/Services/MailSender.cs | 43 ++++- test/yavscTests/Startup.cs | 206 ++---------------------- test/yavscTests/yavscTests.csproj | 23 +++ 7 files changed, 279 insertions(+), 199 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 test/yavscTests/yavscTests.csproj diff --git a/.gitignore b/.gitignore index e2dd9c69..21d65adc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ .gitignore .paket/ -.vscode/ .vs/ .sass-cache/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..32f539bd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,88 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + /* { + "name": ".NET Core Launch (web-client)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/sampleWebAsWebApiClient/bin/Debug/net8.0/sampleWebAsWebApiClient.dll>", + "args": [], + "cwd": "${workspaceFolder}/src/sampleWebAsWebApiClient", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Launch (Api)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-webapi", + "program": "${workspaceFolder}/src/Api/bin/Debug/net8.0/Api.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Api", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-web", + "program": "${workspaceFolder}/src/Yavsc/bin/Debug/net8.0/Yavsc.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Yavsc", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + },*/ + + { + "name": "webApi", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Api/Api.csproj" + }, + { + "name": "webClient", + "type": "dotnet", + "request": "launch", + //"projectPath": "${workspaceFolder}/src/Yavsc/Yavsc.csproj", + "projectPath": "${workspaceFolder}/src/sampleWebAsWebApiClient/sampleWebAsWebApiClient.csproj", + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + } + }, + { + "name": "web", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Yavsc/Yavsc.csproj", + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8bf0c416 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "sqltools.connections": [ + { + "previewLimit": 50, + "server": "localhost", + "port": 5432, + "driver": "PostgreSQL", + "name": "yavscdev", + "group": "yavsc", + "database": "YavscDev", + "username": "yavscdev", + "password": "admin" + } + ], + "cSpell.words": [ + "appsettings", + "Newtonsoft", + "Npgsql", + "Yavsc" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..ac0bb937 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,96 @@ +{ + "version": "2.0.0", + "tasks": [ + + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-web", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": [ + "build" + ], + "options": { + "cwd": "src/Yavsc" + }, + "group": { + "kind": "build" + }, + "isBackground": true + }, + { + "label": "build-webapi", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": [ + "build" + ], + "options": { + "cwd": "src/Api" + }, + "group": { + "kind": "build" + }, + "isBackground": true + }, + { + "label": "build-webclient", + "type": "process", + "problemMatcher": ["$msCompile"], + "command": "dotnet", + "args": [ + "build" + ], + "runOptions": { + }, + "options": { + "cwd": "src/sampleWebAsWebApiClient" + }, + "group": { + "kind": "build" + }, + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + } + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/src/Yavsc.Server/Services/MailSender.cs b/src/Yavsc.Server/Services/MailSender.cs index 200f0a61..8f2c2052 100644 --- a/src/Yavsc.Server/Services/MailSender.cs +++ b/src/Yavsc.Server/Services/MailSender.cs @@ -8,29 +8,40 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; using Yavsc.Abstract.Manage; -using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Identity; using Yavsc.Interface; using Yavsc.Settings; +using Yavsc.Models; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.Extensions.Localization; +using System.Web; namespace Yavsc.Services { - public class MailSender : IEmailSender, ITrueEmailSender + public class MailSender : IEmailSender, IEmailSender, ITrueEmailSender { + private readonly IStringLocalizer localizer; readonly SiteSettings siteSettings; readonly SmtpSettings smtpSettings; private readonly ILogger logger; - public MailSender( IOptions sitesOptions, IOptions smtpOptions, - ILoggerFactory loggerFactory + ILoggerFactory loggerFactory, + IStringLocalizer localizer ) { + this.localizer = localizer; siteSettings = sitesOptions.Value; smtpSettings = smtpOptions.Value; logger = loggerFactory.CreateLogger(); } - + + public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) + { + throw new NotImplementedException(); + } + /// /// /// @@ -40,7 +51,7 @@ namespace Yavsc.Services /// a MessageWithPayloadResponse, /// bool somethingsent = (response.failure == 0 && response.success > 0) /// - + public async Task SendEmailAsync(string email, string subject, string htmlMessage) { @@ -79,5 +90,25 @@ namespace Yavsc.Services } return msg.MessageId; } + + public async Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) + { + var callbackUrl = siteSettings.Audience + "/Account/ResetPassword/" + + HttpUtility.UrlEncode(user.Id) + "/" + HttpUtility.UrlEncode(resetCode); + + await SendEmailAsync(user.UserName, user.Email, + localizer["Reset Password"], + localizer["Please reset your password by "] + " following this link"); + throw new NotImplementedException(); + } + + public async Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) + { + await SendEmailAsync(user.UserName, user.Email, + localizer["Reset Password"], + localizer["Please reset your password by "] + " following this link"); + } } } diff --git a/test/yavscTests/Startup.cs b/test/yavscTests/Startup.cs index c37acbba..24f01beb 100644 --- a/test/yavscTests/Startup.cs +++ b/test/yavscTests/Startup.cs @@ -2,15 +2,12 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.OptionsModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Razor; -using Microsoft.Extensions.PlatformAbstractions; using Yavsc; using Yavsc.Models; using Yavsc.Services; -using Microsoft.Data.Entity; using Microsoft.Extensions.WebEncoders; using yavscTests.Settings; using Microsoft.AspNetCore.Diagnostics; @@ -18,7 +15,6 @@ using System.Net; using Yavsc.Extensions; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; -using OAuth.AspNet.Tokens; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OAuth; @@ -27,7 +23,6 @@ using Google.Apis.Util.Store; using System.Security.Claims; using Google.Apis.Auth.OAuth2.Responses; using Constants = Yavsc.Constants; -using OAuth.AspNet.AuthServer; using Yavsc.Models.Auth; using Microsoft.AspNetCore.Identity; using System.Collections.Concurrent; @@ -39,15 +34,14 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Yavsc.AuthorizationHandlers; -using Yavsc.Formatters; using Microsoft.Net.Http.Headers; using static Yavsc.Startup; using Microsoft.AspNetCore.DataProtection.Infrastructure; using System.IO; -using Microsoft.AspNetCore.Identity.EntityFramework; -using Yavsc.Auth; using Yavsc.Lib; +using Yavsc.Settings; +using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Mvc.Authorization; namespace yavscTests { @@ -65,10 +59,7 @@ namespace yavscTests public static IConfigurationRoot GoogleWebClientConfiguration { get; set; } public static CookieAuthenticationOptions ExternalCookieAppOptions { get; private set; } - public MonoDataProtectionProvider ProtectionProvider { get; private set; } - public static OAuth.AspNet.AuthServer.OAuthAuthorizationServerOptions OAuthServerAppOptions { get; private set; } - public Yavsc.Auth.YavscGoogleOptions YavscGoogleAppOptions { get; private set; } - private static ILogger logger; + private static ILogger logger; public static string ApiKey { get; private set; } @@ -112,10 +103,8 @@ namespace yavscTests var testingconf = Configuration.GetSection("Testing"); services.Configure(testingconf); - services.AddInstance(typeof(ILoggerFactory), new LoggerFactory()); - services.AddTransient(typeof(IEmailSender), typeof(MailSender)); + services.AddTransient(typeof(IEmailSender), typeof(MailSender)); services.AddEntityFramework().AddNpgsql().AddDbContext(); - services.AddTransient((s) => new RazorTemplateEngine(s.GetService())); services.AddLogging(); services.AddTransient(); services.AddTransient(); @@ -135,22 +124,16 @@ namespace yavscTests // Add the system clock service services.AddSingleton(); - services.AddAuthorization(options => - { - - options.AddPolicy("AdministratorOnly", policy => + services.AddAuthorizationBuilder() + .AddPolicy("AdministratorOnly", policy => { policy.RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Constants.AdminGroupName); - }); - - options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)); - options.AddPolicy("Bearer", new AuthorizationPolicyBuilder() + }) + .AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)) + .AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes("yavsc") - .RequireAuthenticatedUser().Build()); - // options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId", "123", "456")); - // options.AddPolicy("BuildingEntry", policy => policy.Requirements.Add(new OfficeEntryRequirement())); - options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser()); - }); + .RequireAuthenticatedUser().Build()) + .AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser()); services.AddDataProtection(); services.ConfigureDataProtection(configure => @@ -181,9 +164,7 @@ namespace yavscTests services.AddIdentity( option => { - IdentityAppOptions = option; option.User.RequireUniqueEmail = true; - option.Cookies.ApplicationCookie.LoginPath = "/signin"; } ).AddEntityFrameworkStores() .AddTokenProvider>(Constants.DefaultFactor) @@ -221,22 +202,18 @@ namespace yavscTests // Inject ticket formatting services.AddTransient(typeof(ISecureDataFormat<>), typeof(SecureDataFormat<>)); - services.AddTransient, Microsoft.AspNet.Authentication.SecureDataFormat>(); services.AddTransient, TicketDataFormat>(); // Add application services. - services.AddTransient(); + services.AddTransient, MailSender>(); services.AddTransient(); services.AddTransient(); services.AddTransient((sp) => new FileDataStore("googledatastore", false)); services.AddTransient(); - services.AddTransient(); - + } - #region OAuth Methods - private Client GetApplication(string clientId) { @@ -250,159 +227,6 @@ namespace yavscTests return app; } - private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) - { - if (context == null) throw new InvalidOperationException("context == null"); - var app = GetApplication(context.ClientId); - if (app == null) return Task.FromResult(0); - Startup.logger.LogInformation($"ValidateClientRedirectUri: Validated ({app.RedirectUri})"); - context.Validated(app.RedirectUri); - return Task.FromResult(0); - } - - private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) - { - string clientId, clientSecret; - - if (context.TryGetBasicCredentials(out clientId, out clientSecret) || - context.TryGetFormCredentials(out clientId, out clientSecret)) - { - logger.LogInformation($"ValidateClientAuthentication: Got id: ({clientId} secret: {clientSecret})"); - var client = GetApplication(clientId); - if (client == null) - { - context.SetError("invalid_clientId", "Client secret is invalid."); - return Task.FromResult(null); - } - else - if (client.Type == ApplicationTypes.NativeConfidential) - { - logger.LogInformation($"NativeConfidential key"); - if (string.IsNullOrWhiteSpace(clientSecret)) - { - logger.LogInformation($"invalid_clientId: Client secret should be sent."); - context.SetError("invalid_clientId", "Client secret should be sent."); - return Task.FromResult(null); - } - else - { - // if (client.Secret != Helper.GetHash(clientSecret)) - // TODO store a hash in db, not the pass - if (client.Secret != clientSecret) - { - context.SetError("invalid_clientId", "Client secret is invalid."); - logger.LogInformation($"invalid_clientId: Client secret is invalid."); - return Task.FromResult(null); - } - } - } - - if (!client.Active) - { - context.SetError("invalid_clientId", "Client is inactive."); - logger.LogInformation($"invalid_clientId: Client is inactive."); - return Task.FromResult(null); - } - - if (client != null && client.Secret == clientSecret) - { - logger.LogInformation($"\\o/ ValidateClientAuthentication: Validated ({clientId})"); - context.Validated(); - } - else logger.LogInformation($":'( ValidateClientAuthentication: KO ({clientId})"); - } - else logger.LogWarning($"ValidateClientAuthentication: neither Basic nor Form credential were found"); - return Task.FromResult(0); - } - - - private async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) - { - logger.LogWarning($"GrantResourceOwnerCredentials task ... {context.UserName}"); - - ApplicationUser user = null; - user = DbContext.Users.Include(u => u.Membership).First(u => u.UserName == context.UserName); - - - if (await _usermanager.CheckPasswordAsync(user, context.Password)) - { - - var claims = new List( - context.Scope.Select(x => new Claim("urn:oauth:scope", x)) - ) - { - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Email, user.Email) - }; - claims.AddRange((await _usermanager.GetRolesAsync(user)).Select( - r => new Claim(ClaimTypes.Role, r) - )); - claims.AddRange(user.Membership.Select( - m => new Claim(YavscClaimTypes.CircleMembership, m.CircleId.ToString()) - )); - ClaimsPrincipal principal = new ClaimsPrincipal( - new ClaimsIdentity( - new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), - claims) - ); - context.HttpContext.User = principal; - context.Validated(principal); - } -#if USERMANAGER -#endif - return Task.FromResult(0); - } - private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context) - { - var id = new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType); - var claims = context.Scope.Select(x => new Claim("urn:oauth:scope", x)); - var cid = new ClaimsIdentity(id, claims); - ClaimsPrincipal principal = new ClaimsPrincipal(cid); - - context.Validated(principal); - - return Task.FromResult(0); - } - private readonly ConcurrentDictionary _authenticationCodes = new ConcurrentDictionary(StringComparer.Ordinal); - - private void CreateAuthenticationCode(AuthenticationTokenCreateContext context) - { - logger.LogInformation("CreateAuthenticationCode"); - context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); - _authenticationCodes[context.Token] = context.SerializeTicket(); - } - - private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context) - { - string value; - if (_authenticationCodes.TryRemove(context.Token, out value)) - { - context.DeserializeTicket(value); - logger.LogInformation("ReceiveAuthenticationCode: Success"); - } - } - - private void CreateRefreshToken(AuthenticationTokenCreateContext context) - { - var uid = context.Ticket.Principal.GetUserId(); - logger.LogInformation($"CreateRefreshToken for {uid}"); - foreach (var c in context.Ticket.Principal.Claims) - logger.LogInformation($"| User claim: {c.Type} {c.Value}"); - - context.SetToken(context.SerializeTicket()); - } - - private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context) - { - var uid = context.Ticket.Principal.GetUserId(); - logger.LogInformation($"ReceiveRefreshToken for {uid}"); - foreach (var c in context.Ticket.Principal.Claims) - logger.LogInformation($"| User claim: {c.Type} {c.Value}"); - context.DeserializeTicket(context.Token); - } - - #endregion - UserManager _usermanager; @@ -415,8 +239,6 @@ namespace yavscTests ILoggerFactory loggerFactory ) { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - loggerFactory.AddDebug(); logger = loggerFactory.CreateLogger(); logger.LogInformation(env.EnvironmentName); this.DbContext = dbContext; diff --git a/test/yavscTests/yavscTests.csproj b/test/yavscTests/yavscTests.csproj new file mode 100644 index 00000000..b80b61b8 --- /dev/null +++ b/test/yavscTests/yavscTests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + +