From c902852a247d7a0e83bb509bd933229501af375a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 11:54:48 +0100 Subject: [PATCH 01/34] Secret scan --- .forgejo/workflows/buildAndTest.yml | 5 ++++- contrib/Makefile | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index ea58d2fef..786bccf4c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,7 +40,10 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - + - name: Secret scan + uses: actions/secret-scan-action@v1 + with: + install_scanners: true - name: Test run: | echo "🚀 Lancement des tests..." diff --git a/contrib/Makefile b/contrib/Makefile index 79145668f..f893d1e50 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,6 +1,8 @@ APP_PROJECT_NAMES=Org Blogs Api SLNDIR=.. +MAIN_SECRET="4QSFD923c65sdf-654" + include $(SLNDIR)/.env include .env From 7a4ca76957b65373c08c57a7032008ee4c4e3082 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 12:08:57 +0100 Subject: [PATCH 02/34] picket scan --- .forgejo/workflows/buildAndTest.yml | 7 ++++--- dotnet-tools.json | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 786bccf4c..2d135474a 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -41,9 +41,10 @@ jobs: git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - name: Secret scan - uses: actions/secret-scan-action@v1 - with: - install_scanners: true + run: | + echo "🔍 Scanning for secrets..." + cd /src/_src && dotnet tool restore && dotnet picket protect --verbose --redact --exit-code 1 \ + && echo "✅ Success !" || echo "❌ Fail ($?)!" - name: Test run: | echo "🚀 Lancement des tests..." diff --git a/dotnet-tools.json b/dotnet-tools.json index b0e38abda..1762a89de 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -1,5 +1,13 @@ { "version": 1, "isRoot": true, - "tools": {} + "tools": { + "picket": { + "version": "0.2.12", + "commands": [ + "picket" + ], + "rollForward": false + } + } } \ No newline at end of file From d65f78d9850272348b07271f82bb6d71c697493f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 12:20:55 +0100 Subject: [PATCH 03/34] test --- .pre-commit-config.yaml | 6 ++++++ contrib/Makefile | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..168ff1424 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks + diff --git a/contrib/Makefile b/contrib/Makefile index f893d1e50..bff2690d2 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,7 +1,7 @@ APP_PROJECT_NAMES=Org Blogs Api SLNDIR=.. -MAIN_SECRET="4QSFD923c65sdf-654" +MAIN_SECRET="4QqkjsfS345FD923c65sdf-654" include $(SLNDIR)/.env include .env From e79fba7b66338d7d98b3874340ecb81f0efbff82 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 12:21:38 +0100 Subject: [PATCH 04/34] retest --- contrib/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/Makefile b/contrib/Makefile index bff2690d2..0a61eedc3 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,7 +1,7 @@ APP_PROJECT_NAMES=Org Blogs Api SLNDIR=.. -MAIN_SECRET="4QqkjsfS345FD923c65sdf-654" +MAIN_SECRET="4QqkjsfS34qlmskjfd5FD923c65sdf-654" include $(SLNDIR)/.env include .env From f7d5b21d66e608b6c668a80f7338de8ebb83da9c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 12:27:47 +0100 Subject: [PATCH 05/34] cleanup --- .pre-commit-config.yaml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 168ff1424..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -repos: - - repo: https://github.com/gitleaks/gitleaks - rev: v8.24.2 - hooks: - - id: gitleaks - From bb4792d8ae8cc39481d2ec79503e2e604a88da40 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 12:56:48 +0100 Subject: [PATCH 06/34] Gitleaks integration --- .gitleaksignore | 45 +++++++++++++++++++++++++++++++++++++++++++++ contrib/Makefile | 2 -- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .gitleaksignore diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 000000000..056cf69d2 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,45 @@ +# Exclure uniquement les dossiers de sortie de compilation +bin/ +obj/ +src/*/bin/ +src/*/obj/ +test/*/bin/ +test/*/obj/ + +# Toolchain front (Node / esbuild) +node_modules/ +build/ +package-lock.json + +# Exclure les caches lourds +.git/ +.vs/ + +.env + +.*.env + +*.csproj.lscache +data/ +appsettings.*.json +appsettings-*.*.json + +# Exception: the Testing-environment override for Yavsc.Org is a tracked +# configuration source, not a secrets file. TestWebApplicationFactory +# (Yavsc.Org.Tests) flips ASPNETCORE_ENVIRONMENT to "Testing" so +# AddConfiguration("org") in Program.Main loads this file as the +# last in the chain (it is optional). It overrides the connection +# string and SMTP section for the in-memory test host and contains +# no production secrets. +!src/Yavsc.Org/appsettings-org.Testing.json + +generated/ +*.tmp +tmp/ +DataDir/ + +*.tests.trx +*.tests.html + +*.log + diff --git a/contrib/Makefile b/contrib/Makefile index 0a61eedc3..79145668f 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,8 +1,6 @@ APP_PROJECT_NAMES=Org Blogs Api SLNDIR=.. -MAIN_SECRET="4QqkjsfS34qlmskjfd5FD923c65sdf-654" - include $(SLNDIR)/.env include .env From ec87e8db15aebeb68786b4ab9a8243ad6f39ae6a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 13:19:39 +0100 Subject: [PATCH 07/34] scan the commit --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 2d135474a..010a269f8 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -43,7 +43,7 @@ jobs: - name: Secret scan run: | echo "🔍 Scanning for secrets..." - cd /src/_src && dotnet tool restore && dotnet picket protect --verbose --redact --exit-code 1 \ + cd /src/_src && dotnet tool restore && dotnet picket git --verbose --redact --exit-code 1 --log-opts -n1 \ && echo "✅ Success !" || echo "❌ Fail ($?)!" - name: Test run: | From 93426986f65bce36571fcef08257f1ea2941c20e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 14:12:34 +0100 Subject: [PATCH 08/34] code format --- src/Yavsc.Abstract/IT/CodeFromChars.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Yavsc.Abstract/IT/CodeFromChars.cs b/src/Yavsc.Abstract/IT/CodeFromChars.cs index ff441568e..e0e31ce28 100644 --- a/src/Yavsc.Abstract/IT/CodeFromChars.cs +++ b/src/Yavsc.Abstract/IT/CodeFromChars.cs @@ -44,7 +44,6 @@ namespace Yavsc.Abstract.IT public bool Validate() { // this is a n*n task - throw new NotImplementedException(); } From d8c0678462038bc2645c17d77c63f13336650a3b Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Sep 2026 14:39:53 +0100 Subject: [PATCH 09/34] code reorg --- src/PostIt/PostIt/App.axaml.cs | 2 +- .../Helpers/ServiceCollectionHelpers.cs | 5 +- src/PostIt/PostIt/ViewLocator.cs | 3 +- .../BlogsViewModel.cs} | 6 +- src/PostIt/PostIt/Views/Blogs/MainPage.axaml | 8 +- .../PostIt/Views/Blogs/MainPage.axaml.cs | 8 +- src/PostIt/PostIt/Views/Layout/MainView.axaml | 4 +- .../PostIt/Views/Layout/MainView.axaml.cs | 2 +- src/Yavsc.Org.Tests/Directory.Packages.props | 6 +- .../Services/FileSystemAuthManagerTests.cs | 129 ++++++++++++++++++ src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 3 +- 11 files changed, 154 insertions(+), 22 deletions(-) rename src/PostIt/PostIt/ViewModels/{Layout/MainViewModel.cs => Blogs/BlogsViewModel.cs} (98%) create mode 100644 src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 59d5e412c..9f0e8f76b 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -148,7 +148,7 @@ private void ConfigureRootView(MainView rootView) public static async Task PushBlogsPageAsync() { var app = (App)Current!; - var mainVm = app.ServiceProvider!.GetRequiredService(); + var mainVm = app.ServiceProvider!.GetRequiredService(); await mainVm.InitializeAsync(); await app.PushPageAsync(mainVm); } diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index c0831b290..c87eb9286 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using PostIt.Services; using PostIt.ViewModels; using PostIt.Views; +using PostIt.Views.Blogs; using PostIt.Views.Commands; using Yavsc.Api.Client; @@ -34,7 +35,7 @@ public static class ServiceCollectionHelpers // Vues services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); // SettingsPage is a singleton: there must be one and only one @@ -89,7 +90,7 @@ public static class ServiceCollectionHelpers sessionStatus.Refresh(); services.AddSingleton(sessionStatus); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); return services.BuildServiceProvider(); } } diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index f866e1bac..97538abbb 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using PostIt.ViewModels; using PostIt.ViewModels.Commands; using PostIt.Views; +using PostIt.Views.Blogs; using PostIt.Views.Commands; namespace PostIt; @@ -38,7 +39,7 @@ public class ViewLocator : IDataTemplate var services = app!.ServiceProvider!; return data switch { - MainViewModel => services.GetRequiredService(), + BlogsViewModel => services.GetRequiredService(), Settings => services.GetRequiredService(), HomePageViewModel => services.GetRequiredService(), ActivitiesPageViewModel => services.GetRequiredService(), diff --git a/src/PostIt/PostIt/ViewModels/Layout/MainViewModel.cs b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs similarity index 98% rename from src/PostIt/PostIt/ViewModels/Layout/MainViewModel.cs rename to src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs index d2667c012..94a143aa4 100644 --- a/src/PostIt/PostIt/ViewModels/Layout/MainViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs @@ -12,7 +12,7 @@ using PostIt.Helpers; namespace PostIt.ViewModels; -public partial class MainViewModel : ViewModelBase, IActionStatusViewModel +public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel { /// Window/tab title. Cosmetic — bound by /// MainPage.axaml if at all. Not the post title. @@ -336,7 +336,7 @@ public partial class MainViewModel : ViewModelBase, IActionStatusViewModel } - public MainViewModel() + public BlogsViewModel() { SettingsModel = new Settings(); Init(SettingsModel); @@ -396,7 +396,7 @@ public partial class MainViewModel : ViewModelBase, IActionStatusViewModel /// . Production code uses the /// (Settings, BlogApiClient) overload below. /// - public MainViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null) + public BlogsViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null) { SettingsModel = new Settings(); BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ; diff --git a/src/PostIt/PostIt/Views/Blogs/MainPage.axaml b/src/PostIt/PostIt/Views/Blogs/MainPage.axaml index 871a2f541..c93aef2c2 100644 --- a/src/PostIt/PostIt/Views/Blogs/MainPage.axaml +++ b/src/PostIt/PostIt/Views/Blogs/MainPage.axaml @@ -5,15 +5,15 @@ xmlns:vm="using:PostIt.ViewModels" xmlns:postitControls="using:PostIt.Controls" xmlns:models="using:Yavsc.Blogspot" - xmlns:views="using:PostIt.Views" + xmlns:views="using:PostIt.Views.Blogs" xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit" mc:Ignorable="d" - x:Class="PostIt.Views.MainPage" - x:DataType="vm:MainViewModel" + x:Class="PostIt.Views.Blogs.BlogsPage" + x:DataType="vm:BlogsViewModel" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - + + x:DataType="vm:HomePageViewModel"> - + diff --git a/src/PostIt/PostIt/Views/Layout/MainView.axaml.cs b/src/PostIt/PostIt/Views/Layout/MainView.axaml.cs index 654b90e2d..22bf537c6 100644 --- a/src/PostIt/PostIt/Views/Layout/MainView.axaml.cs +++ b/src/PostIt/PostIt/Views/Layout/MainView.axaml.cs @@ -13,7 +13,7 @@ public partial class MainView : UserControl protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - if (DataContext is ViewModels.MainViewModel vm) + if (DataContext is ViewModels.BlogsViewModel vm) { if (!vm.IsLoaded) { diff --git a/src/Yavsc.Org.Tests/Directory.Packages.props b/src/Yavsc.Org.Tests/Directory.Packages.props index 5927d2b5b..af2ef948c 100644 --- a/src/Yavsc.Org.Tests/Directory.Packages.props +++ b/src/Yavsc.Org.Tests/Directory.Packages.props @@ -4,8 +4,8 @@ - - - + + + diff --git a/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs b/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs new file mode 100644 index 000000000..8a796e4b5 --- /dev/null +++ b/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs @@ -0,0 +1,129 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Yavsc.Models; +using Yavsc.Models.Access; +using Yavsc.Models.Relationship; +using Yavsc.Services; + +namespace Yavsc.Org.Tests.Services; + +public class FileSystemAuthManagerTests +{ + [Fact] + public void SetAccess_creates_acl_row_with_owner_path_and_flags() + { + using var scope = CreateScope(); + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read | FileAccessRight.Write); + + var row = scope.Db.CircleAuthorizationToFile.Single(); + + Assert.Equal(scope.Circle.Id, row.CircleId); + Assert.Equal("alice/documents/report.txt", row.Path); + Assert.Equal("alice", row.OwnerId); + Assert.Equal(FileAccessRight.Read | FileAccessRight.Write, row.Access); + } + + [Fact] + public void SetAccess_updates_existing_acl_row_without_duplicates() + { + using var scope = CreateScope(); + + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Write); + + var rows = scope.Db.CircleAuthorizationToFile.ToList(); + + Assert.Single(rows); + Assert.Equal(FileAccessRight.Write, rows[0].Access); + } + + [Fact] + public void SetAccess_none_removes_existing_acl_row() + { + using var scope = CreateScope(); + + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.None); + + Assert.Empty(scope.Db.CircleAuthorizationToFile); + } + + [Fact] + public void SetAccess_ignores_unknown_owner_prefix() + { + using var scope = CreateScope(); + + scope.Service.SetAccess(scope.Circle.Id, "unknown/documents/report.txt", FileAccessRight.Read); + + Assert.Empty(scope.Db.CircleAuthorizationToFile); + } + + [Fact] + public void Deleting_circle_cascades_file_acl_rows() + { + using var scope = CreateScope(); + + scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); + scope.Db.Circle.Remove(scope.Circle); + scope.Db.SaveChanges(); + + Assert.Empty(scope.Db.CircleAuthorizationToFile); + } + + private static TestScope CreateScope() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + var db = new ApplicationDbContext(options); + db.Database.EnsureCreated(); + + db.Users.Add(new ApplicationUser + { + Id = "alice", + UserName = "alice", + Email = "alice@example.test" + }); + db.SaveChanges(); + + var circle = new Circle + { + OwnerId = "alice", + Name = "shared", + Public = false + }; + + db.Circle.Add(circle); + db.SaveChanges(); + + var service = new FileSystemAuthManager(db, Options.Create(new SiteSettings())); + return new TestScope(connection, db, service, circle); + } + + private sealed class TestScope : IDisposable + { + public TestScope(SqliteConnection connection, ApplicationDbContext db, FileSystemAuthManager service, Circle circle) + { + Connection = connection; + Db = db; + Service = service; + Circle = circle; + } + + public SqliteConnection Connection { get; } + public ApplicationDbContext Db { get; } + public FileSystemAuthManager Service { get; } + public Circle Circle { get; } + + public void Dispose() + { + Db.Dispose(); + Connection.Dispose(); + } + } +} diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 106079202..0d18d1e0f 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -53,6 +53,7 @@ + - - -