From 04388dd146f419fe9a229a28369b2e8d4f883a99 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sun, 13 Apr 2025 16:35:32 +0200 Subject: [PATCH 01/20] =?UTF-8?q?M=C3=A0J=20des=20d=C3=A9pendances=20de=20?= =?UTF-8?q?test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tests/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tests/requirements.txt b/.tests/requirements.txt index 5a07a18..b42b996 100644 --- a/.tests/requirements.txt +++ b/.tests/requirements.txt @@ -1,6 +1,6 @@ -certifi==2023.5.7 +certifi==2025.1.31 charset-normalizer==3.1.0 -idna==3.4 +idna==3.10 psutil==5.9.5 PyJWT==2.8.0 requests==2.32.3 From ed9dcacb3c2c6a9d856a5e6fed9b65f53583da18 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Wed, 16 Apr 2025 23:21:08 +0200 Subject: [PATCH 02/20] =?UTF-8?q?M=C3=A0J=20du=20copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gallium+ API.sln | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gallium+ API.sln b/Gallium+ API.sln index b6b0bdd..2988055 100644 --- a/Gallium+ API.sln +++ b/Gallium+ API.sln @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitignore = .gitignore .github\workflows\Vérifications.yml = .github\workflows\Vérifications.yml + README.md = README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "External", "External\External.csproj", "{0091163D-F909-442C-9397-859D8E069322}" diff --git a/README.md b/README.md index 2a812fc..319d571 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ L'API web de Gallium+, le nouveau logiciel de vente de l'[ETIQ](https://etiq-dijon.fr). -© 2023 L'ETIQ - Tous droits réservés. +© 2023-1025 L'ETIQ - Tous droits réservés. From 71c57737f4533534dba793b2a03f180226c114f6 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Wed, 16 Apr 2025 23:22:11 +0200 Subject: [PATCH 03/20] : --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 319d571..e8b2181 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ L'API web de Gallium+, le nouveau logiciel de vente de l'[ETIQ](https://etiq-dijon.fr). -© 2023-1025 L'ETIQ - Tous droits réservés. +© 2023-2025 L'ETIQ - Tous droits réservés. From 8a6a002654c0cdb7dd758acb448a23c4e47c4a42 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Thu, 17 Apr 2025 18:11:32 +0200 Subject: [PATCH 04/20] HashAndSaltMapper plus simple --- .../Implementations/HashAndSaltMapper.cs | 86 ++++++------------- 1 file changed, 28 insertions(+), 58 deletions(-) diff --git a/External/Data/MariaDb/Implementations/HashAndSaltMapper.cs b/External/Data/MariaDb/Implementations/HashAndSaltMapper.cs index d06aa12..a28ee5b 100644 --- a/External/Data/MariaDb/Implementations/HashAndSaltMapper.cs +++ b/External/Data/MariaDb/Implementations/HashAndSaltMapper.cs @@ -3,82 +3,52 @@ using KiwiQuery.Mapped.Extension; using KiwiQuery.Mapped.Mappers.Fields; -namespace GalliumPlus.Data.MariaDb; +namespace GalliumPlus.Data.MariaDb.Implementations; -[SharedMapper] -public class HashAndSaltMapper : IFieldMapper +public abstract class HashAndSaltMapper : IFieldMapper { - public bool CanHandle(Type fieldType) => fieldType == typeof(PasswordInformation) || fieldType == typeof(OneTimeSecret); + public bool CanHandle(Type fieldType) => fieldType == typeof(T); + + public IFieldMapper SpecializeFor(Type fieldType, IColumnInfo info, IFieldMapperCollection collection) => this; - public IFieldMapper SpecializeFor(Type fieldType, IColumnInfo info, IFieldMapperCollection collection) + public object? ReadValue(IDataRecord record, int offset) { - if (fieldType == typeof(PasswordInformation)) - { - return new SpecializedForPasswordInformation(); - } - else if (fieldType == typeof(OneTimeSecret)) - { - return new SpecializedForOneTimeSecret(); - } - else - { - throw new InvalidOperationException($"Les champs de type {fieldType.FullName} ne sont pas pris en charge."); - } + byte[] hash = new byte[32]; + record.GetBytes(offset, 0, hash, 0, 32); + string salt = record.GetString(offset + 1); + return this.FromHashAndSalt(hash, salt); } - public object? ReadValue(IDataRecord record, int offset) => - throw new InvalidOperationException("Ce mapper ne peut pas être utilisé sans spécialisation."); - public IEnumerable WriteValue(object? fieldValue) => - throw new InvalidOperationException("Ce mapper ne peut pas être utilisé sans spécialisation."); + [this.GetHash((T)fieldValue!), this.GetSalt((T)fieldValue!)]; public IEnumerable MetaColumns => ["salt"]; public bool CanMapIntegerKey => false; - private abstract class Specialized : IFieldMapper - { - public bool CanHandle(Type fieldType) => fieldType == typeof(T); - - public IFieldMapper SpecializeFor(Type fieldType, IColumnInfo info, IFieldMapperCollection collection) => this; + protected abstract T FromHashAndSalt(byte[] hash, string salt); - public object? ReadValue(IDataRecord record, int offset) - { - byte[] hash = new byte[32]; - record.GetBytes(offset, 0, hash, 0, 32); - string salt = record.GetString(offset + 1); - return this.FromHashAndSalt(hash, salt); - } + protected abstract byte[] GetHash(T value); - public IEnumerable WriteValue(object? fieldValue) => - [this.GetHash((T)fieldValue!), this.GetSalt((T)fieldValue!)]; + protected abstract string GetSalt(T value); +} - public IEnumerable MetaColumns => ["salt"]; - - public bool CanMapIntegerKey => false; - - protected abstract T FromHashAndSalt(byte[] hash, string salt); - - protected abstract byte[] GetHash(T value); - - protected abstract string GetSalt(T value); - } - - private class SpecializedForOneTimeSecret : Specialized - { - protected override OneTimeSecret FromHashAndSalt(byte[] hash, string salt) => new(hash, salt); +[SharedMapper] +public class OneTimeSecretMapper : HashAndSaltMapper +{ + protected override OneTimeSecret FromHashAndSalt(byte[] hash, string salt) => new(hash, salt); - protected override byte[] GetHash(OneTimeSecret value) => value.Hash; + protected override byte[] GetHash(OneTimeSecret value) => value.Hash; - protected override string GetSalt(OneTimeSecret value) => value.Salt; - } + protected override string GetSalt(OneTimeSecret value) => value.Salt; +} - private class SpecializedForPasswordInformation : Specialized - { - protected override PasswordInformation FromHashAndSalt(byte[] hash, string salt) => new(hash, salt); +[SharedMapper] +public class PasswordInformationMapper : HashAndSaltMapper +{ + protected override PasswordInformation FromHashAndSalt(byte[] hash, string salt) => new(hash, salt); - protected override byte[] GetHash(PasswordInformation value) => value.Hash; + protected override byte[] GetHash(PasswordInformation value) => value.Hash; - protected override string GetSalt(PasswordInformation value) => value.Salt; - } + protected override string GetSalt(PasswordInformation value) => value.Salt; } \ No newline at end of file From 3002b15f5869914464c03b102a23a4b10eaf2e94 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Thu, 17 Apr 2025 22:35:16 +0200 Subject: [PATCH 05/20] =?UTF-8?q?chore:=20M=C3=A0J=20de=20KiwiQuery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/Applications/AppAccess.cs | 2 +- Core/Applications/Client.cs | 2 +- Core/Applications/SameSignOn.cs | 2 +- Core/Logs/AuditLog.cs | 2 +- KiwiQuery | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/Applications/AppAccess.cs b/Core/Applications/AppAccess.cs index a11a4be..eda90fd 100644 --- a/Core/Applications/AppAccess.cs +++ b/Core/Applications/AppAccess.cs @@ -8,7 +8,7 @@ namespace GalliumPlus.Core.Applications; /// public class AppAccess { - [Key] + [PrimaryKey] private readonly int id; private readonly OneTimeSecret secret; diff --git a/Core/Applications/Client.cs b/Core/Applications/Client.cs index 2f386b5..7dd2f16 100644 --- a/Core/Applications/Client.cs +++ b/Core/Applications/Client.cs @@ -10,7 +10,7 @@ namespace GalliumPlus.Core.Applications; /// public class Client { - [Key] + [PrimaryKey] private int id; private string apiKey; private string name; diff --git a/Core/Applications/SameSignOn.cs b/Core/Applications/SameSignOn.cs index 7f42103..58360e8 100644 --- a/Core/Applications/SameSignOn.cs +++ b/Core/Applications/SameSignOn.cs @@ -10,7 +10,7 @@ namespace GalliumPlus.Core.Applications; /// public class SameSignOn { - [Key] + [PrimaryKey] private readonly int id; private string secret; private SignatureType signatureType; diff --git a/Core/Logs/AuditLog.cs b/Core/Logs/AuditLog.cs index 1b84ef8..9c3e8da 100644 --- a/Core/Logs/AuditLog.cs +++ b/Core/Logs/AuditLog.cs @@ -9,7 +9,7 @@ namespace GalliumPlus.Core.Logs; /// public class AuditLog { - [Key] + [PrimaryKey] private readonly int id; private readonly LoggedAction action; diff --git a/KiwiQuery b/KiwiQuery index 8d43eb2..84e712a 160000 --- a/KiwiQuery +++ b/KiwiQuery @@ -1 +1 @@ -Subproject commit 8d43eb21ab7ec728c318732cbd2d5d94e947613e +Subproject commit 84e712a6b25930dfe1e77ddd68fbeeef8ac845bf From f967c31adb445e30f096b6c7a9708b0ba6f52ba4 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Thu, 17 Apr 2025 22:36:39 +0200 Subject: [PATCH 06/20] fix: correction des validateurs pour `PartialClient` et `PartialSameSignOn` --- Core/Applications/SameSignOnScopes.cs | 2 +- WebService/Dto/Applications/PartialClient.cs | 2 +- WebService/Dto/Applications/PartialSameSignOn.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Applications/SameSignOnScopes.cs b/Core/Applications/SameSignOnScopes.cs index f02729e..b73153c 100644 --- a/Core/Applications/SameSignOnScopes.cs +++ b/Core/Applications/SameSignOnScopes.cs @@ -40,7 +40,7 @@ public enum SameSignOnScope /// /// La portée de l'accès Same Sign-On. /// -public class SameSignOnScopes +public static class SameSignOnScopes { /// public static readonly FlagEnum Identity = new(SameSignOnScope.Identity); diff --git a/WebService/Dto/Applications/PartialClient.cs b/WebService/Dto/Applications/PartialClient.cs index cea69d3..c601873 100644 --- a/WebService/Dto/Applications/PartialClient.cs +++ b/WebService/Dto/Applications/PartialClient.cs @@ -16,7 +16,7 @@ public class Validator : AbstractValidator { public Validator() { - this.RuleFor(client => client.Name).NotNull().NotEmpty().MaxLength(50); + this.RuleFor(client => client.Name).NotEmpty().MaxLength(50); this.RuleFor(client => client.SameSignOn).SetValidator(new PartialSameSignOn.Validator()!); } } diff --git a/WebService/Dto/Applications/PartialSameSignOn.cs b/WebService/Dto/Applications/PartialSameSignOn.cs index 45740f0..bc56bb1 100644 --- a/WebService/Dto/Applications/PartialSameSignOn.cs +++ b/WebService/Dto/Applications/PartialSameSignOn.cs @@ -14,8 +14,8 @@ public class Validator : AbstractValidator public Validator() { this.RuleFor(sso => sso.DisplayName).MaxLength(50); - this.RuleFor(sso => sso.LogoUrl).MaxLength(120); - this.RuleFor(sso => sso.RedirectUrl).NotNull().NotEmpty().MustBeAValidUrl().MaxLength(120); + this.RuleFor(sso => sso.LogoUrl).MustBeAValidUrl().MaxLength(120); + this.RuleFor(sso => sso.RedirectUrl).NotEmpty().MustBeAValidUrl().MaxLength(120); } } From d8a1fd5dccd51f3d4b217e5ea9fffd0cec65d377 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Thu, 17 Apr 2025 22:37:45 +0200 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20d=C3=A9finition=20compl=C3=A8te?= =?UTF-8?q?=20de=20la=20classe=20`PricingType`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/Items/PricingType.cs | 53 +++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/Core/Items/PricingType.cs b/Core/Items/PricingType.cs index 2a08b7d..6fef399 100644 --- a/Core/Items/PricingType.cs +++ b/Core/Items/PricingType.cs @@ -1,12 +1,55 @@ +using System.ComponentModel.DataAnnotations; +using KiwiQuery.Mapped; + namespace GalliumPlus.WebApi.Core.Items; -public class PricingType(int id, string shortLabel, string longLabel, bool requiresMembership) +/// +/// Représente un tarif, comme le tarif adhérent ou le tarif non-adhérent. +/// +public class PricingType { - public int Id => id; + [PrimaryKey(AutoIncrement = true)] + private readonly long id; + private readonly string shortName; + private readonly string longName; + private readonly bool requiresMembership; + + /// + /// Le code du tarif. + /// + public long Id => this.id; - public string ShortLabel => shortLabel; + /// + /// Le nom du tarif abrégé en un seul mot. + /// + [Required] + [MaxLength(16)] + public string ShortName => this.shortName; - public string LongLabel => longLabel; + /// + /// Le nom complet du tarif. + /// + [Required] + [MaxLength(50)] + public string LongName => this.longName; - public bool RequiresMembership => requiresMembership; + /// + /// Une valeur de true indique que ce tarif est applicable uniquement aux adhérents. + /// + public bool RequiresMembership => this.requiresMembership; + + /// + /// Instancie un code tarif. + /// + /// Le code du tarif. + /// Le nom abrégé du tarif. + /// Le nom complet du tarif. + /// Si le tarif est applicable uniquement aux adhérents. + public PricingType(long id, string shortName, string longName, bool requiresMembership) + { + this.id = id; + this.shortName = shortName; + this.longName = longName; + this.requiresMembership = requiresMembership; + } } \ No newline at end of file From 67c458b216a308035fd298a0985453a49cf02976 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Thu, 17 Apr 2025 22:40:34 +0200 Subject: [PATCH 08/20] style: ajustement automatic des namespaces --- Core/Applications/Client.cs | 2 +- Core/Exceptions/PermissionDeniedException.cs | 2 +- Core/Items/PricingType.cs | 2 +- .../Builders/AuditLogEntryBuilder.Generic.cs | 3 - Core/Security/Permissions.cs | 2 +- Core/Users/Role.cs | 2 + Core/Users/Session.cs | 1 + CoreTests/Applications/ClientTest.cs | 2 +- CoreTests/Security/OneTimeSecretTest.cs | 1 - CoreTests/Users/PermissionsTest.cs | 4 +- CoreTests/Users/RoleTest.cs | 4 +- CoreTests/Users/SessionTest.cs | 1 + .../Data/MariaDb/Implementations/ClientDao.cs | 1 - .../Data/MariaDb/Implementations/RoleDao.cs | 1 + Fake/Data/Fake/ClientDao.cs | 1 - Fake/Data/Fake/RoleDao.cs | 1 + WebService/Controllers/CategoryController.cs | 2 +- WebService/Controllers/ClientController.cs | 2 +- WebService/Controllers/GalliumController.cs | 1 + WebService/Controllers/LogsController.cs | 2 +- WebService/Controllers/OrderController.cs | 2 +- WebService/Controllers/ProductController.cs | 2 +- WebService/Controllers/RoleController.cs | 1 + WebService/Dto/Legacy/OrderSummary.cs | 1 + WebService/Dto/Legacy/RoleDetails.cs | 1 + WebService/Dto/PermissionsCodeConverter.cs | 2 +- .../BasicAuthenticationHandler.cs | 1 - .../Authorization/PermissionsFilter.cs | 1 + .../RequiresPermissionsAttribute.cs | 2 +- WebService/Middleware/ServerInfoMiddleware.cs | 93 +++++++++---------- WebService/Services/AccessService.cs | 1 - WebService/Services/ClientService.cs | 2 +- WebService/Services/PricingService.cs | 2 +- 33 files changed, 77 insertions(+), 71 deletions(-) diff --git a/Core/Applications/Client.cs b/Core/Applications/Client.cs index 7dd2f16..3191c0b 100644 --- a/Core/Applications/Client.cs +++ b/Core/Applications/Client.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; using GalliumPlus.Core.Random; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; using KiwiQuery.Mapped; namespace GalliumPlus.Core.Applications; diff --git a/Core/Exceptions/PermissionDeniedException.cs b/Core/Exceptions/PermissionDeniedException.cs index 8631643..9664bb0 100644 --- a/Core/Exceptions/PermissionDeniedException.cs +++ b/Core/Exceptions/PermissionDeniedException.cs @@ -1,4 +1,4 @@ -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; namespace GalliumPlus.Core.Exceptions; diff --git a/Core/Items/PricingType.cs b/Core/Items/PricingType.cs index 6fef399..0f06b55 100644 --- a/Core/Items/PricingType.cs +++ b/Core/Items/PricingType.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using KiwiQuery.Mapped; -namespace GalliumPlus.WebApi.Core.Items; +namespace GalliumPlus.Core.Items; /// /// Représente un tarif, comme le tarif adhérent ou le tarif non-adhérent. diff --git a/Core/Logs/Builders/AuditLogEntryBuilder.Generic.cs b/Core/Logs/Builders/AuditLogEntryBuilder.Generic.cs index fd1c3af..54d648e 100644 --- a/Core/Logs/Builders/AuditLogEntryBuilder.Generic.cs +++ b/Core/Logs/Builders/AuditLogEntryBuilder.Generic.cs @@ -1,6 +1,3 @@ -using GalliumPlus.Core.Applications; -using GalliumPlus.Core.Stocks; - namespace GalliumPlus.Core.Logs.Builders; public partial class AuditLogEntryBuilder diff --git a/Core/Security/Permissions.cs b/Core/Security/Permissions.cs index 23e9f19..412aed6 100644 --- a/Core/Security/Permissions.cs +++ b/Core/Security/Permissions.cs @@ -7,7 +7,7 @@ * RÉFLÉCHISSEZ AUSSI AVANT DE RENOMMER/CHANGER LA SIGNIFICATION D'UNE PERMISSION */ -namespace GalliumPlus.Core.Users; +namespace GalliumPlus.Core.Security; /// /// Permissions spéciales attribuées aux rôles. diff --git a/Core/Users/Role.cs b/Core/Users/Role.cs index 94700d4..2e3b0d5 100644 --- a/Core/Users/Role.cs +++ b/Core/Users/Role.cs @@ -1,3 +1,5 @@ +using GalliumPlus.Core.Security; + namespace GalliumPlus.Core.Users; /// diff --git a/Core/Users/Session.cs b/Core/Users/Session.cs index 9582d44..c97f6fd 100644 --- a/Core/Users/Session.cs +++ b/Core/Users/Session.cs @@ -1,5 +1,6 @@ using GalliumPlus.Core.Applications; using GalliumPlus.Core.Random; +using GalliumPlus.Core.Security; namespace GalliumPlus.Core.Users; diff --git a/CoreTests/Applications/ClientTest.cs b/CoreTests/Applications/ClientTest.cs index 66ec263..8461044 100644 --- a/CoreTests/Applications/ClientTest.cs +++ b/CoreTests/Applications/ClientTest.cs @@ -1,4 +1,4 @@ -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; namespace GalliumPlus.Core.Applications; diff --git a/CoreTests/Security/OneTimeSecretTest.cs b/CoreTests/Security/OneTimeSecretTest.cs index 898afe9..d442867 100644 --- a/CoreTests/Security/OneTimeSecretTest.cs +++ b/CoreTests/Security/OneTimeSecretTest.cs @@ -1,5 +1,4 @@ using GalliumPlus.Core.Exceptions; -using GalliumPlus.Core.Random; namespace GalliumPlus.Core.Security; diff --git a/CoreTests/Users/PermissionsTest.cs b/CoreTests/Users/PermissionsTest.cs index aaba700..cfff409 100644 --- a/CoreTests/Users/PermissionsTest.cs +++ b/CoreTests/Users/PermissionsTest.cs @@ -1,4 +1,6 @@ -namespace GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; + +namespace GalliumPlus.Core.Users; using P = Permissions; diff --git a/CoreTests/Users/RoleTest.cs b/CoreTests/Users/RoleTest.cs index e5bf377..0fa5673 100644 --- a/CoreTests/Users/RoleTest.cs +++ b/CoreTests/Users/RoleTest.cs @@ -1,4 +1,6 @@ -namespace GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; + +namespace GalliumPlus.Core.Users; public class RoleTest { diff --git a/CoreTests/Users/SessionTest.cs b/CoreTests/Users/SessionTest.cs index 8bc0c16..d9b991e 100644 --- a/CoreTests/Users/SessionTest.cs +++ b/CoreTests/Users/SessionTest.cs @@ -1,4 +1,5 @@ using GalliumPlus.Core.Applications; +using GalliumPlus.Core.Security; namespace GalliumPlus.Core.Users; diff --git a/External/Data/MariaDb/Implementations/ClientDao.cs b/External/Data/MariaDb/Implementations/ClientDao.cs index 2ce3da1..e2a7890 100644 --- a/External/Data/MariaDb/Implementations/ClientDao.cs +++ b/External/Data/MariaDb/Implementations/ClientDao.cs @@ -3,7 +3,6 @@ using GalliumPlus.Core.Exceptions; using KiwiQuery; using KiwiQuery.Mapped; -using KiwiQuery.Mapped.Exceptions; using MySqlConnector; namespace GalliumPlus.Data.MariaDb.Implementations diff --git a/External/Data/MariaDb/Implementations/RoleDao.cs b/External/Data/MariaDb/Implementations/RoleDao.cs index 878374d..988df79 100644 --- a/External/Data/MariaDb/Implementations/RoleDao.cs +++ b/External/Data/MariaDb/Implementations/RoleDao.cs @@ -1,5 +1,6 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Exceptions; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; using KiwiQuery; using MySqlConnector; diff --git a/Fake/Data/Fake/ClientDao.cs b/Fake/Data/Fake/ClientDao.cs index 8028d63..37f24cb 100644 --- a/Fake/Data/Fake/ClientDao.cs +++ b/Fake/Data/Fake/ClientDao.cs @@ -2,7 +2,6 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Exceptions; using GalliumPlus.Core.Security; -using GalliumPlus.Core.Users; namespace GalliumPlus.Data.Fake { diff --git a/Fake/Data/Fake/RoleDao.cs b/Fake/Data/Fake/RoleDao.cs index 97e9998..42e8c88 100644 --- a/Fake/Data/Fake/RoleDao.cs +++ b/Fake/Data/Fake/RoleDao.cs @@ -1,4 +1,5 @@ using GalliumPlus.Core.Data; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; namespace GalliumPlus.Data.Fake diff --git a/WebService/Controllers/CategoryController.cs b/WebService/Controllers/CategoryController.cs index e0238de..c9ada6b 100644 --- a/WebService/Controllers/CategoryController.cs +++ b/WebService/Controllers/CategoryController.cs @@ -1,7 +1,7 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Logs; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Stocks; -using GalliumPlus.Core.Users; using GalliumPlus.WebService.Dto.Legacy; using GalliumPlus.WebService.Middleware.Authorization; using GalliumPlus.WebService.Services; diff --git a/WebService/Controllers/ClientController.cs b/WebService/Controllers/ClientController.cs index 462aed0..8561082 100644 --- a/WebService/Controllers/ClientController.cs +++ b/WebService/Controllers/ClientController.cs @@ -1,6 +1,6 @@ using FluentValidation; using GalliumPlus.Core.Applications; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; using GalliumPlus.WebService.Dto.Applications; using GalliumPlus.WebService.Middleware.Authorization; using GalliumPlus.WebService.Services; diff --git a/WebService/Controllers/GalliumController.cs b/WebService/Controllers/GalliumController.cs index f878787..d84e066 100644 --- a/WebService/Controllers/GalliumController.cs +++ b/WebService/Controllers/GalliumController.cs @@ -1,5 +1,6 @@ using GalliumPlus.Core.Applications; using GalliumPlus.Core.Exceptions; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; using Microsoft.AspNetCore.Mvc; diff --git a/WebService/Controllers/LogsController.cs b/WebService/Controllers/LogsController.cs index 0bee13d..c4228cd 100644 --- a/WebService/Controllers/LogsController.cs +++ b/WebService/Controllers/LogsController.cs @@ -1,7 +1,7 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Data.LogsSearch; using GalliumPlus.Core.Exceptions; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; using GalliumPlus.WebService.Dto.Legacy; using GalliumPlus.WebService.Middleware.Authorization; using Microsoft.AspNetCore.Authorization; diff --git a/WebService/Controllers/OrderController.cs b/WebService/Controllers/OrderController.cs index 4f3aad5..e6f12f1 100644 --- a/WebService/Controllers/OrderController.cs +++ b/WebService/Controllers/OrderController.cs @@ -1,7 +1,7 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Logs; using GalliumPlus.Core.Orders; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; using GalliumPlus.WebService.Dto.Legacy; using GalliumPlus.WebService.Middleware.Authorization; using Microsoft.AspNetCore.Authorization; diff --git a/WebService/Controllers/ProductController.cs b/WebService/Controllers/ProductController.cs index 48ec195..b1df9b8 100644 --- a/WebService/Controllers/ProductController.cs +++ b/WebService/Controllers/ProductController.cs @@ -1,7 +1,7 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Logs; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Stocks; -using GalliumPlus.Core.Users; using GalliumPlus.WebService.Dto.Legacy; using GalliumPlus.WebService.Middleware; using GalliumPlus.WebService.Middleware.Authorization; diff --git a/WebService/Controllers/RoleController.cs b/WebService/Controllers/RoleController.cs index de9372d..3d2d9d2 100644 --- a/WebService/Controllers/RoleController.cs +++ b/WebService/Controllers/RoleController.cs @@ -1,5 +1,6 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Logs; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; using GalliumPlus.WebService.Dto.Legacy; using GalliumPlus.WebService.Middleware.Authorization; diff --git a/WebService/Dto/Legacy/OrderSummary.cs b/WebService/Dto/Legacy/OrderSummary.cs index ab05b07..4d69141 100644 --- a/WebService/Dto/Legacy/OrderSummary.cs +++ b/WebService/Dto/Legacy/OrderSummary.cs @@ -2,6 +2,7 @@ using GalliumPlus.Core.Data; using GalliumPlus.Core.Exceptions; using GalliumPlus.Core.Orders; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; namespace GalliumPlus.WebService.Dto.Legacy diff --git a/WebService/Dto/Legacy/RoleDetails.cs b/WebService/Dto/Legacy/RoleDetails.cs index 9c278de..43bda96 100644 --- a/WebService/Dto/Legacy/RoleDetails.cs +++ b/WebService/Dto/Legacy/RoleDetails.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; namespace GalliumPlus.WebService.Dto.Legacy diff --git a/WebService/Dto/PermissionsCodeConverter.cs b/WebService/Dto/PermissionsCodeConverter.cs index 3431175..a35cafd 100644 --- a/WebService/Dto/PermissionsCodeConverter.cs +++ b/WebService/Dto/PermissionsCodeConverter.cs @@ -1,6 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; namespace GalliumPlus.WebService.Dto; diff --git a/WebService/Middleware/Authentication/BasicAuthenticationHandler.cs b/WebService/Middleware/Authentication/BasicAuthenticationHandler.cs index 0d86f03..c2c8bf6 100644 --- a/WebService/Middleware/Authentication/BasicAuthenticationHandler.cs +++ b/WebService/Middleware/Authentication/BasicAuthenticationHandler.cs @@ -2,7 +2,6 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; -using System.Text.Json.Serialization; using GalliumPlus.Core.Applications; using GalliumPlus.Core.Data; using GalliumPlus.Core.Exceptions; diff --git a/WebService/Middleware/Authorization/PermissionsFilter.cs b/WebService/Middleware/Authorization/PermissionsFilter.cs index 2aad7f4..01d440a 100644 --- a/WebService/Middleware/Authorization/PermissionsFilter.cs +++ b/WebService/Middleware/Authorization/PermissionsFilter.cs @@ -1,4 +1,5 @@ using GalliumPlus.Core.Exceptions; +using GalliumPlus.Core.Security; using GalliumPlus.Core.Users; using GalliumPlus.WebService.Middleware.ErrorHandling; using Microsoft.AspNetCore.Mvc.Abstractions; diff --git a/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs b/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs index 04aeb41..3a2ab23 100644 --- a/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs +++ b/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs @@ -1,4 +1,4 @@ -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; namespace GalliumPlus.WebService.Middleware.Authorization { diff --git a/WebService/Middleware/ServerInfoMiddleware.cs b/WebService/Middleware/ServerInfoMiddleware.cs index 11893df..c551485 100644 --- a/WebService/Middleware/ServerInfoMiddleware.cs +++ b/WebService/Middleware/ServerInfoMiddleware.cs @@ -1,73 +1,72 @@ -namespace GalliumPlus.WebService.Middleware +namespace GalliumPlus.WebService.Middleware; + +public class ServerInfoMiddleware : IMiddleware { - public class ServerInfoMiddleware : IMiddleware + public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - context.Response.Headers.Append("X-Gallium-Version", ServerInfo.Current.PrettyVersion); - await next.Invoke(context); - } + context.Response.Headers.Append("X-Gallium-Version", ServerInfo.Current.PrettyVersion); + await next.Invoke(context); } +} - public static class ServerInfoMiddlewareExtensions - { - public static IServiceCollection AddServerInfo(this IServiceCollection services) => - services.AddSingleton(); +public static class ServerInfoMiddlewareExtensions +{ + public static IServiceCollection AddServerInfo(this IServiceCollection services) => + services.AddSingleton(); - public static IApplicationBuilder UseServerInfo(this IApplicationBuilder app) => - app.UseMiddleware(); - } + public static IApplicationBuilder UseServerInfo(this IApplicationBuilder app) => + app.UseMiddleware(); +} - public class ServerInfo - { - private static ServerInfo? current = null; +public class ServerInfo +{ + private static ServerInfo? current = null; - public static ServerInfo Current + public static ServerInfo Current + { + get { - get + if (current == null) { - if (current == null) - { - current = new ServerInfo(); - } - - return current; + current = new ServerInfo(); } + + return current; } + } - private string prettyVersion = $"unknown (unknown/{CONFIGURATION})"; - private string compactVersion = "unknown"; + private string prettyVersion = $"unknown (unknown/{CONFIGURATION})"; + private string compactVersion = "unknown"; #if TEST private const string CONFIGURATION = "test"; #elif DEBUG - private const string CONFIGURATION = "debug"; + private const string CONFIGURATION = "debug"; #else private const string CONFIGURATION = "release"; #endif - public string PrettyVersion => this.prettyVersion; + public string PrettyVersion => this.prettyVersion; - public string CompactVersion => this.compactVersion; + public string CompactVersion => this.compactVersion; - private ServerInfo() { } + private ServerInfo() { } - public void SetVersion(int major, int minor, int patch, string stage = "unknown") - { - string build = Builtins.CompileDateTime.ToString("yyMMddHHmm"); - this.prettyVersion = $"{major}.{minor}.{patch}.{build} ({stage}/{CONFIGURATION})"; - this.compactVersion = $"{major}.{minor}.{patch}-{CONFIGURATION[0]}"; - } + public void SetVersion(int major, int minor, int patch, string stage = "unknown") + { + string build = Builtins.CompileDateTime.ToString("yyMMddHHmm"); + this.prettyVersion = $"{major}.{minor}.{patch}.{build} ({stage}/{CONFIGURATION})"; + this.compactVersion = $"{major}.{minor}.{patch}-{CONFIGURATION[0]}"; + } - public override string ToString() - { - return " ___ _ _ _ _ \n" - + " / __| __ _| | (_)_ _ _ __ ___ _| |_ \n" - + " | | _ / _` | | | | | | | '_ ` _ \\'_ _|\n" - + " | |_| | (_| | | | | |_| | | | | | | |_| \n" - + " \\____|\\__,_|_|_|_|\\__,_|_| |_| |_| \n" - + $"\n Gallium+ Web API Server v{this.PrettyVersion}\n"; - } + public override string ToString() + { + return " ___ _ _ _ _ \n" + + " / __| __ _| | (_)_ _ _ __ ___ _| |_ \n" + + " | | _ / _` | | | | | | | '_ ` _ \\'_ _|\n" + + " | |_| | (_| | | | | |_| | | | | | | |_| \n" + + " \\____|\\__,_|_|_|_|\\__,_|_| |_| |_| \n" + + $"\n Gallium+ Web API Server v{this.PrettyVersion}\n"; } } @@ -80,4 +79,4 @@ public static partial class Builtins public static DateTime CompileDateTime => new(CompileTime, DateTimeKind.Utc); } -#pragma warning restore CA1050 +#pragma warning restore CA1050 \ No newline at end of file diff --git a/WebService/Services/AccessService.cs b/WebService/Services/AccessService.cs index 6d75e53..8882cdf 100644 --- a/WebService/Services/AccessService.cs +++ b/WebService/Services/AccessService.cs @@ -8,7 +8,6 @@ using JWT.Builder; using Microsoft.AspNetCore.WebUtilities; using Multiflag; -using SessionOptions = GalliumPlus.Core.Users.SessionOptions; namespace GalliumPlus.WebService.Services; diff --git a/WebService/Services/ClientService.cs b/WebService/Services/ClientService.cs index dc45c36..2f9dac4 100644 --- a/WebService/Services/ClientService.cs +++ b/WebService/Services/ClientService.cs @@ -1,7 +1,7 @@ using GalliumPlus.Core.Applications; using GalliumPlus.Core.Data; using GalliumPlus.Core.Exceptions; -using GalliumPlus.Core.Users; +using GalliumPlus.Core.Security; using GalliumPlus.WebService.Dto.Access; using GalliumPlus.WebService.Dto.Applications; diff --git a/WebService/Services/PricingService.cs b/WebService/Services/PricingService.cs index fc55b59..f787ac3 100644 --- a/WebService/Services/PricingService.cs +++ b/WebService/Services/PricingService.cs @@ -1,4 +1,4 @@ -using GalliumPlus.WebApi.Core.Items; +using GalliumPlus.Core.Items; namespace GalliumPlus.WebService.Services; From 92d14c7dc970f87d6ea104c3d2b45d9f6f09f89a Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Fri, 18 Apr 2025 20:09:37 +0200 Subject: [PATCH 09/20] fix: sorti la classe `Builtins` de son namespace --- WebService/Middleware/ServerInfoMiddleware.cs | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/WebService/Middleware/ServerInfoMiddleware.cs b/WebService/Middleware/ServerInfoMiddleware.cs index c551485..53228fe 100644 --- a/WebService/Middleware/ServerInfoMiddleware.cs +++ b/WebService/Middleware/ServerInfoMiddleware.cs @@ -1,72 +1,74 @@ -namespace GalliumPlus.WebService.Middleware; - -public class ServerInfoMiddleware : IMiddleware +namespace GalliumPlus.WebService.Middleware { - public async Task InvokeAsync(HttpContext context, RequestDelegate next) + + public class ServerInfoMiddleware : IMiddleware { - context.Response.Headers.Append("X-Gallium-Version", ServerInfo.Current.PrettyVersion); - await next.Invoke(context); + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + context.Response.Headers.Append("X-Gallium-Version", ServerInfo.Current.PrettyVersion); + await next.Invoke(context); + } } -} - -public static class ServerInfoMiddlewareExtensions -{ - public static IServiceCollection AddServerInfo(this IServiceCollection services) => - services.AddSingleton(); - public static IApplicationBuilder UseServerInfo(this IApplicationBuilder app) => - app.UseMiddleware(); -} + public static class ServerInfoMiddlewareExtensions + { + public static IServiceCollection AddServerInfo(this IServiceCollection services) => + services.AddSingleton(); -public class ServerInfo -{ - private static ServerInfo? current = null; + public static IApplicationBuilder UseServerInfo(this IApplicationBuilder app) => + app.UseMiddleware(); + } - public static ServerInfo Current + public class ServerInfo { - get + private static ServerInfo? current = null; + + public static ServerInfo Current { - if (current == null) + get { - current = new ServerInfo(); - } + if (current == null) + { + current = new ServerInfo(); + } - return current; + return current; + } } - } - private string prettyVersion = $"unknown (unknown/{CONFIGURATION})"; - private string compactVersion = "unknown"; + private string prettyVersion = $"unknown (unknown/{CONFIGURATION})"; + private string compactVersion = "unknown"; #if TEST private const string CONFIGURATION = "test"; #elif DEBUG - private const string CONFIGURATION = "debug"; + private const string CONFIGURATION = "debug"; #else private const string CONFIGURATION = "release"; #endif - public string PrettyVersion => this.prettyVersion; - - public string CompactVersion => this.compactVersion; + public string PrettyVersion => this.prettyVersion; - private ServerInfo() { } + public string CompactVersion => this.compactVersion; - public void SetVersion(int major, int minor, int patch, string stage = "unknown") - { - string build = Builtins.CompileDateTime.ToString("yyMMddHHmm"); - this.prettyVersion = $"{major}.{minor}.{patch}.{build} ({stage}/{CONFIGURATION})"; - this.compactVersion = $"{major}.{minor}.{patch}-{CONFIGURATION[0]}"; - } + private ServerInfo() { } - public override string ToString() - { - return " ___ _ _ _ _ \n" - + " / __| __ _| | (_)_ _ _ __ ___ _| |_ \n" - + " | | _ / _` | | | | | | | '_ ` _ \\'_ _|\n" - + " | |_| | (_| | | | | |_| | | | | | | |_| \n" - + " \\____|\\__,_|_|_|_|\\__,_|_| |_| |_| \n" - + $"\n Gallium+ Web API Server v{this.PrettyVersion}\n"; + public void SetVersion(int major, int minor, int patch, string stage = "unknown") + { + string build = Builtins.CompileDateTime.ToString("yyMMddHHmm"); + this.prettyVersion = $"{major}.{minor}.{patch}.{build} ({stage}/{CONFIGURATION})"; + this.compactVersion = $"{major}.{minor}.{patch}-{CONFIGURATION[0]}"; + } + + public override string ToString() + { + return " ___ _ _ _ _ \n" + + " / __| __ _| | (_)_ _ _ __ ___ _| |_ \n" + + " | | _ / _` | | | | | | | '_ ` _ \\'_ _|\n" + + " | |_| | (_| | | | | |_| | | | | | | |_| \n" + + " \\____|\\__,_|_|_|_|\\__,_|_| |_| |_| \n" + + $"\n Gallium+ Web API Server v{this.PrettyVersion}\n"; + } } } From a12baedbb17bfe54249dd3b3f563c431e19c0c0c Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Fri, 18 Apr 2025 20:51:17 +0200 Subject: [PATCH 10/20] =?UTF-8?q?test:=20ajout=20des=20tests=20de=20tarifs?= =?UTF-8?q?,=20correction=20d'un=20test=20de=20cat=C3=A9gorie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tests/main.py | 2 +- .tests/tests/__init__.py | 5 +- .tests/tests/category_tests.py | 4 +- .tests/tests/pricing_tests.py | 227 +++++++++++++++++++++++++++++++++ WebService/Program.cs | 4 +- 5 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 .tests/tests/pricing_tests.py diff --git a/.tests/main.py b/.tests/main.py index 3368397..8e17c0e 100644 --- a/.tests/main.py +++ b/.tests/main.py @@ -9,4 +9,4 @@ if __name__ == "__main__": decimal.DefaultContext.prec = 2 - Launcher.launch("1.2.0") + Launcher.launch("1.3.0") diff --git a/.tests/tests/__init__.py b/.tests/tests/__init__.py index e467beb..a7f894e 100644 --- a/.tests/tests/__init__.py +++ b/.tests/tests/__init__.py @@ -1,7 +1,8 @@ +from .access_tests import AccessTests from .application_tests import ApplicationTests from .category_tests import CategoryTests +from .order_tests import OrderTests +from .pricing_tests import PricingTests from .product_tests import ProductTests from .role_tests import RoleTests -from .order_tests import OrderTests from .user_tests import UserTests -from .access_tests import AccessTests diff --git a/.tests/tests/category_tests.py b/.tests/tests/category_tests.py index 0a30e6c..011e0b5 100644 --- a/.tests/tests/category_tests.py +++ b/.tests/tests/category_tests.py @@ -71,7 +71,7 @@ def test_category_create(self): # Informations manquantes invalid_category = {} - response = self.post("users", invalid_category) + response = self.post("categories", invalid_category) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" @@ -80,7 +80,7 @@ def test_category_create(self): # Informations non valides invalid_category = {"name": ""} - response = self.post("users", invalid_category) + response = self.post("categories", invalid_category) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" diff --git a/.tests/tests/pricing_tests.py b/.tests/tests/pricing_tests.py new file mode 100644 index 0000000..daef031 --- /dev/null +++ b/.tests/tests/pricing_tests.py @@ -0,0 +1,227 @@ +from utils.test_base import TestBase +from utils.auth import BearerAuth +from .history_tests_helpers import HistoryTestHelpers + +INVALID_PRICING_TYPES = [ + { + "shortName": "EtiSmash", + "requiresMembership": False, + }, + { + "longName": "Tarif spécial EtiSmash", + "requiresMembership": True, + }, + { + "longName": "Tarif spécial EtiSmash", + "shortName": "EtiSmash", + }, + { + "longName": "", + "shortName": "EtiSmash", + "requiresMembership": True, + }, + { + "longName": " ", + "shortName": "EtiSmash", + "requiresMembership": False, + }, + { + "longName": "Tarif spécial EtiSmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaash", + "shortName": "EtiSmash", + "requiresMembership": False, + }, + { + "longName": "Tarif spécial EtiSmash", + "shortName": "", + "requiresMembership": True, + }, + { + "longName": "Tarif spécial EtiSmash", + "shortName": " ", + "requiresMembership": False, + }, + { + "longName": "Tarif spécial EtiSmash", + "shortName": "EtiSmaaaaaaaaaash", + "requiresMembership": True, + }, + { + "longName": "Tarif spécial EtiSmash", + "shortName": "EtiSmash", + "requiresMembership": 1, + }, +] + + +class PricingTests(TestBase): + def setUp(self): + super().setUp() + self.set_authentication(BearerAuth("09876543210987654321")) + self.audit = AuditTestHelpers(self) + + def tearDown(self): + self.unset_authentication() + + def test_pricing_type_get_all(self): + response = self.get("pricing_types") + self.expect(response.status_code).to.be.equal_to(200) + + pricing_types = response.json() + self.expect(pricing_types).to.be.a(list)._and._not.empty() + + pricing_type = pricing_types[0] + self.expect(pricing_type).to.have.an_item("id").of.type(int) + self.expect(pricing_type).to.have.an_item("longName").of.type(str) + self.expect(pricing_type).to.have.an_item("shortName").of.type(str) + self.expect(pricing_type).to.have.an_item("requiresMembership").of.type(bool) + + def test_pricing_type_get_one(self): + existing_id = self.get("pricing_types").json()[0]["id"] + invalid_id = 12345 + + # Test avec un tarif existant + + response = self.get(f"pricing_types/{existing_id}") + self.expect(response.status_code).to.be.equal_to(200) + + pricing_type = response.json() + self.expect(pricing_type).to.be.a(dict) + self.expect(pricing_type).to.have.an_item("id").of.type(int) + self.expect(pricing_type).to.have.an_item("longName").of.type(str) + self.expect(pricing_type).to.have.an_item("shortName").of.type(str) + self.expect(pricing_type).to.have.an_item("requiresMembership").of.type(bool) + + # Test avec un tarif inexistant + + response = self.get(f"pricing_types/{invalid_id}") + self.expect(response.status_code).to.be.equal_to(404) + + def test_pricing_type_create(self): + previous_pricing_type_count = len(self.get("pricing_types").json()) + + valid_pricing_type = { + "longName": "Tarif spécial EtiSmash", + "shortName": "EtiSmash", + "requiresMembership": False, + } + + with self.audit.watch(): + response = self.post("pricing_types", valid_pricing_type) + self.expect(response.status_code).to.be.equal_to(201) + location = self.expect(response.headers).to.have.an_item("Location").value + + created_pricing_type = response.json() + self.expect(created_pricing_type).to.have.an_item("id") + self.expect(created_pricing_type["name"]).to.be.equal_to("Jus") + + response = self.get(location) + self.expect(response.status_code).to.be.equal_to(200) + created_pricing_type = response.json() + self.expect(created_pricing_type["name"]).to.be.equal_to("Jus") + + new_pricing_type_count = len(self.get("pricing_types").json()) + self.expect(new_pricing_type_count).to.be.equal_to( + previous_pricing_type_count + 1 + ) + + self.audit.expect_entries( + self.audit.pricing_type_added_action("Jus", "eb069420") + ) + + for invalid_pricing_type in INVALID_PRICING_TYPES: + response = self.post("pricing_types", invalid_pricing_type) + self.expect(response.status_code).to.be.equal_to(400) + self.expect(response.json()).to.have.an_item("code").that._is.equal_to( + "InvalidResource" + ) + + def test_pricing_type_edit(self): + valid_pricing_type = self.get("pricing_types").json()[-1] + valid_pricing_type.update(Name="Jus") + pricing_type_id = valid_pricing_type["id"] + + with self.audit.watch(): + response = self.put(f"pricing_types/{pricing_type_id}", valid_pricing_type) + self.expect(response.status_code).to.be.equal_to(200) + + edited_pricing_type = self.get(f"pricing_types/{pricing_type_id}").json() + self.expect(edited_pricing_type["name"]).to.be.equal_to("Jus") + + self.audit.expect_entries( + self.audit.pricing_type_modified_action("Jus", "eb069420") + ) + + # pricing_type qui n'existe pas + + response = self.put("pricing_types/12345", valid_pricing_type) + self.expect(response.status_code).to.be.equal_to(404) + + # Informations manquantes + + invalid_pricing_type = {} + response = self.put(f"pricing_types/{pricing_type_id}", invalid_pricing_type) + self.expect(response.status_code).to.be.equal_to(400) + self.expect(response.json()).to.have.an_item("code").that._is.equal_to( + "InvalidResource" + ) + + # Informations non valides + + invalid_pricing_type = {"name": ""} + response = self.put(f"pricing_types/{pricing_type_id}", invalid_pricing_type) + self.expect(response.status_code).to.be.equal_to(400) + self.expect(response.json()).to.have.an_item("code").that._is.equal_to( + "InvalidResource" + ) + + def test_pricing_type_delete(self): + pricing_type = {"name": "Jus"} + location = self.post("pricing_types", pricing_type).headers["Location"] + + # On supprimme la catégorie + + with self.audit.watch(): + response = self.delete(location) + self.expect(response.status_code).to.be.equal_to(200) + + self.audit.expect_entries( + self.audit.pricing_type_deleted_action("Jus", "eb069420") + ) + + # La catégorie n'existe plus + + response = self.get(location) + self.expect(response.status_code).to.be.equal_to(404) + + # On ne peut plus le supprimer + + response = self.delete(location) + self.expect(response.status_code).to.be.equal_to(404) + + def test_pricing_type_no_authentification(self): + self.unset_authentication() + + response = self.get("pricing_types") + self.expect(response.status_code).to.be.equal_to(401) + response = self.post("pricing_types", {}) + self.expect(response.status_code).to.be.equal_to(401) + response = self.get("pricing_types/1") + self.expect(response.status_code).to.be.equal_to(401) + response = self.put("pricing_types/1", {}) + self.expect(response.status_code).to.be.equal_to(401) + response = self.delete("pricing_types/1") + self.expect(response.status_code).to.be.equal_to(401) + + def test_pricing_type_no_permission(self): + self.set_authentication(BearerAuth("12345678901234567890")) + + response = self.get("pricing_types") + self.expect(response.status_code).to.be.equal_to(403) + response = self.post("pricing_types", {"name": "/"}) + self.expect(response.status_code).to.be.equal_to(403) + response = self.get("pricing_types/1") + self.expect(response.status_code).to.be.equal_to(403) + response = self.put("pricing_types/1", {"name": "/"}) + self.expect(response.status_code).to.be.equal_to(403) + response = self.delete("pricing_types/1") + self.expect(response.status_code).to.be.equal_to(403) diff --git a/WebService/Program.cs b/WebService/Program.cs index ad5135c..34008e4 100644 --- a/WebService/Program.cs +++ b/WebService/Program.cs @@ -62,7 +62,7 @@ #endregion -#region Base de données (Fake & MariaDB) +#region Base de données #if FAKE_DB // ajout en singleton, sinon les données ne sont pas gardées d'une requête à l'autre @@ -208,7 +208,7 @@ app.UseAuthorization(); app.MapControllers(); -ServerInfo.Current.SetVersion(1, 2, 1, "beta"); +ServerInfo.Current.SetVersion(1, 3, 0, "beta"); Console.WriteLine(ServerInfo.Current); #if !FAKE_DB From b0f1200a6129ec2b26bdb710e8e39077acb35ccb Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Fri, 2 May 2025 22:47:06 +0200 Subject: [PATCH 11/20] Installation de Multiflag v2 --- Core/Applications/SameSignOn.cs | 2 +- Core/Applications/SameSignOnScopes.cs | 28 +++++++++++++++++++-------- Core/Core.csproj | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Core/Applications/SameSignOn.cs b/Core/Applications/SameSignOn.cs index 58360e8..b9c563b 100644 --- a/Core/Applications/SameSignOn.cs +++ b/Core/Applications/SameSignOn.cs @@ -59,7 +59,7 @@ public class SameSignOn /// /// Si l'application a besoin ou non d'une clé d'API à la connexion. /// - public bool RequiresApiKey => this.scope.Includes(SameSignOnScopes.Gallium); + public bool RequiresApiKey => SameSignOnScopes.Current.Gallium.IsIn(this.scope); /// /// Crée un paramétrage SSO existant. diff --git a/Core/Applications/SameSignOnScopes.cs b/Core/Applications/SameSignOnScopes.cs index b73153c..bd3be13 100644 --- a/Core/Applications/SameSignOnScopes.cs +++ b/Core/Applications/SameSignOnScopes.cs @@ -40,17 +40,29 @@ public enum SameSignOnScope /// /// La portée de l'accès Same Sign-On. /// -public static class SameSignOnScopes +public class SameSignOnScopes : EnumBitflagSet { + private static SameSignOnScopes? current; + + public static SameSignOnScopes Current => current ??= new SameSignOnScopes(); + /// - public static readonly FlagEnum Identity = new(SameSignOnScope.Identity); - + public Flag Identity { get; } + /// - public static readonly FlagEnum Email = new(SameSignOnScope.Email); - + public Flag Email { get; } + /// - public static readonly FlagEnum Role = new(SameSignOnScope.Role); - + public Flag Role { get; } + /// - public static readonly FlagEnum Gallium = new(SameSignOnScope.Gallium); + public Flag Gallium { get; } + + private SameSignOnScopes() + { + this.Identity = this.Flag(SameSignOnScope.Identity); + this.Email = this.Flag(SameSignOnScope.Email); + this.Role = this.Flag(SameSignOnScope.Role); + this.Gallium = this.Flag(SameSignOnScope.Gallium); + } } \ No newline at end of file diff --git a/Core/Core.csproj b/Core/Core.csproj index 36a08e3..7d5742f 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -12,7 +12,7 @@ - + From d75a8f06d0865efa9c8d04f892bc8005a3d4c98b Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 3 May 2025 00:08:05 +0200 Subject: [PATCH 12/20] utilisation de multiflag pour les permissions --- Core/Applications/Client.cs | 27 +-- Core/Applications/SameSignOn.cs | 2 +- Core/Applications/SameSignOnScopes.cs | 8 +- Core/Exceptions/PermissionDeniedException.cs | 8 +- Core/Security/Permissions.cs | 109 ++++------- Core/Users/Role.cs | 6 +- Core/Users/Session.cs | 2 +- CoreTests/Applications/ClientTest.cs | 178 +++++++++--------- CoreTests/Users/PermissionsTest.cs | 67 ++----- CoreTests/Users/RoleTest.cs | 4 +- CoreTests/Users/SessionTest.cs | 20 +- CoreTests/Users/UserTest.cs | 2 +- CoreTests/Usings.cs | 1 - .../Data/MariaDb/Implementations/RoleDao.cs | 2 +- .../Data/MariaDb/Implementations/UserDao.cs | 2 +- Fake/Data/Fake/ClientDao.cs | 36 ++-- Fake/Data/Fake/RoleDao.cs | 12 +- WebService/Controllers/CategoryController.cs | 10 +- WebService/Controllers/ClientController.cs | 14 +- WebService/Controllers/GalliumController.cs | 4 +- WebService/Controllers/LogsController.cs | 4 +- WebService/Controllers/OrderController.cs | 2 +- WebService/Controllers/ProductController.cs | 16 +- WebService/Controllers/RoleController.cs | 10 +- WebService/Controllers/UserController.cs | 24 ++- WebService/Dto/Legacy/OrderSummary.cs | 2 +- WebService/Dto/Legacy/RoleDetails.cs | 2 +- ...onverter.cs => PermissionCodeConverter.cs} | 8 +- .../Authorization/PermissionsFilter.cs | 14 +- .../RequiresPermissionsAttribute.cs | 6 +- WebService/Program.cs | 2 +- WebService/Services/AccessService.cs | 6 +- WebService/Services/ClientService.cs | 8 +- 33 files changed, 283 insertions(+), 335 deletions(-) rename WebService/Dto/{PermissionsCodeConverter.cs => PermissionCodeConverter.cs} (50%) diff --git a/Core/Applications/Client.cs b/Core/Applications/Client.cs index 3191c0b..a6d293d 100644 --- a/Core/Applications/Client.cs +++ b/Core/Applications/Client.cs @@ -14,8 +14,8 @@ public class Client private int id; private string apiKey; private string name; - private Permissions granted; - private Permissions allowed; + private Permission granted; + private Permission allowed; private bool isEnabled; [HasOne("id")] @@ -54,7 +54,7 @@ public string Name /// /// Les permissions autorisées aux utilisateurs de l'application. /// - public Permissions Allowed + public Permission Allowed { get => this.allowed; set => this.allowed = value; @@ -63,7 +63,7 @@ public Permissions Allowed /// /// Les permissions données à tous les utilisteurs de l'application. /// - public Permissions Granted + public Permission Granted { get => this.granted; set => this.granted = value; @@ -125,8 +125,8 @@ public Client( string apiKey, string name, bool isEnabled, - Permissions allowed, - Permissions granted + Permission allowed, + Permission granted ) { this.id = id; @@ -147,8 +147,8 @@ Permissions granted public Client( string name, bool isEnabled = true, - Permissions allowed = Permissions.NONE, - Permissions granted = Permissions.NONE + Permission allowed = Permission.None, + Permission granted = Permission.None ) { var rtg = new RandomTextGenerator(new BasicRandomProvider()); @@ -162,13 +162,16 @@ public Client( /// /// Applique les filtres de permissions. - /// Les ajouts () sont appliqués avant - /// les restrictions (). + /// Les restrictions () sont appliquées par-dessus + /// les ajouts (). /// /// Les permissions à filtrer. /// Les permission restantes. - public Permissions Filter(Permissions permissions) + public Permission Filter(Permission permissions) { - return permissions.Grant(this.granted).Mask(this.allowed); + permissions = Permissions.Current.Minimum(permissions); + permissions = Permissions.Current.Union(permissions, this.granted); + permissions = Permissions.Current.Intersection(permissions, this.allowed); + return permissions; } } \ No newline at end of file diff --git a/Core/Applications/SameSignOn.cs b/Core/Applications/SameSignOn.cs index b9c563b..a89d6e9 100644 --- a/Core/Applications/SameSignOn.cs +++ b/Core/Applications/SameSignOn.cs @@ -65,7 +65,7 @@ public class SameSignOn /// Crée un paramétrage SSO existant. /// /// L'identifiant de l'application auquel les informations appartiennent. - /// Le code secret utilisé pour signer les jeton d'authentification + /// Le code secret utilisé pour signer les jetons d'authentification /// La méthode de signature utilisée. /// La portée de l'accès aux informations des utilisateurs. /// Le nom à afficher pour présenter l'application. diff --git a/Core/Applications/SameSignOnScopes.cs b/Core/Applications/SameSignOnScopes.cs index bd3be13..08784d8 100644 --- a/Core/Applications/SameSignOnScopes.cs +++ b/Core/Applications/SameSignOnScopes.cs @@ -11,22 +11,22 @@ public enum SameSignOnScope /// /// Accès au strict minimum (identifiant utilisateur et identifiant immuable) /// - Minimum = 0x00, + Minimum = 0x0, /// /// Accès au profil de l'utilisateur (nom et prénom). /// - Identity = 0x01, + Identity = 0x1, /// /// Accès à l'adresse mail de l'utilisateur. /// - Email = 0x02, + Email = 0x2, /// /// Accès au rôle de l'utilisateur. /// - Role = 0x04, + Role = 0x4, /// /// Accès à l'API Gallium en tant que l'utilisateur connecté. diff --git a/Core/Exceptions/PermissionDeniedException.cs b/Core/Exceptions/PermissionDeniedException.cs index 9664bb0..4e12496 100644 --- a/Core/Exceptions/PermissionDeniedException.cs +++ b/Core/Exceptions/PermissionDeniedException.cs @@ -8,12 +8,12 @@ namespace GalliumPlus.Core.Exceptions; /// public class PermissionDeniedException : GalliumException { - private Permissions required; + private readonly Permission required; /// - /// Les permissions qu étaient requises. + /// Les permissions qui étaient requises. /// - public Permissions Required { get => this.required; } + public Permission Required => this.required; public override ErrorCode ErrorCode => ErrorCode.PermissionDenied; @@ -21,7 +21,7 @@ public class PermissionDeniedException : GalliumException /// Instancie l'exception. /// /// Les permissions requises pour effectuer l'action. - public PermissionDeniedException(Permissions required) + public PermissionDeniedException(Permission required) { this.required = required; } diff --git a/Core/Security/Permissions.cs b/Core/Security/Permissions.cs index 412aed6..63bc343 100644 --- a/Core/Security/Permissions.cs +++ b/Core/Security/Permissions.cs @@ -4,151 +4,124 @@ * Vous pouvez rajouter de nouvelles permissions avec des puissances de 2 * (jusqu'à 2 147 483 648) * - * RÉFLÉCHISSEZ AUSSI AVANT DE RENOMMER/CHANGER LA SIGNIFICATION D'UNE PERMISSION + * RÉFLÉCHISSEZ AUSSI AVANT DE RENOMMER/CHANGER LA SIGNIFICATION D'UNE PERMISSION. */ +using Multiflag; + namespace GalliumPlus.Core.Security; /// /// Permissions spéciales attribuées aux rôles. /// [Flags] -public enum Permissions : uint +public enum Permission : uint { /// /// Accès en lecture aux produits et catégories. /// - SEE_PRODUCTS_AND_CATEGORIES = 1, - - NOT_SEE_PRODUCTS_AND_CATEGORIES = 1 | NOT_MANAGE_PRODUCTS | NOT_MANAGE_CATEGORIES, + SeeProductsAndCategories = 0x1, /// /// Gestion des produits (accès à tous, création, modification et suppression). /// - MANAGE_PRODUCTS = 2 | SEE_PRODUCTS_AND_CATEGORIES, - - NOT_MANAGE_PRODUCTS = 2, + ManageProducts = 0x2, /// /// Gestion des catégories (accès, création, modification et suppression). /// - MANAGE_CATEGORIES = 4 | SEE_PRODUCTS_AND_CATEGORIES, - - NOT_MANAGE_CATEGORIES = 4, + ManageCategories = 0x4, /// /// Accès en lecture à tous les comptes et rôles. /// - SEE_ALL_USERS_AND_ROLES = 8, - - NOT_SEE_ALL_USERS_AND_ROLES = 8 | NOT_MANAGE_DEPOSITS | NOT_MANAGE_ROLES, + SeeAllUsersAndRoles = 0x8, /// /// Gestion des acomptes (ajout et retrait). /// - MANAGE_DEPOSITS = 16 | SEE_ALL_USERS_AND_ROLES, - - NOT_MANAGE_DEPOSITS = 16 | NOT_MANAGE_USERS, + ManageDeposits = 0x10, /// /// Gestion des utilisateurs (création, modification et suppression de compte). /// S'applique uniquement aux utilisateurs dont le rang est inférieur ou égal /// au rang de l'utilisateur ayant cette permission. /// - MANAGE_USERS = 32 | MANAGE_DEPOSITS, - - NOT_MANAGE_USERS = 32 | NOT_RESET_MEMBERSHIPS, + ManageUsers = 0x20, /// /// Gestion des rôles (création, modification et suppression). /// - MANAGE_ROLES = 64 | SEE_ALL_USERS_AND_ROLES, - - NOT_MANAGE_ROLES = 64, + ManageRoles = 0x40, /// /// Accès aux logs. /// - READ_LOGS = 128, - + ReadLogs = 0x80, + /// /// Permission de révoquer toutes les adhésions. /// - RESET_MEMBERSHIPS = 256 | MANAGE_USERS, - - NOT_RESET_MEMBERSHIPS = 256, + ResetMemberships = 0x100, /// /// Permission de gérer les applications connectées à Gallium. /// - MANAGE_CLIENTS = 512, - - NOT_MANAGE_CLIENTS = 512, + ManageClients = 0x200, /// /// Permission d'utiliser les différents outils de développement associés à Gallium. /// - USE_DEVELOPER_TOOLS = 1024, - - NOT_USE_DEVELOPER_TOOLS = 1024, + UseDeveloperTools = 0x400, /// /// Permission de modifier les acomptes manuellement. Cette permission ne doit pas pouvoir être donnée à quiconque, /// elle sert uniquement à garder la compatibilité avec Gallium V2. /// - FORCE_DEPOSIT_MODIFICATION = 1073741824, - - NOT_FORCE_DEPOSIT_MODIFICATION = 1073741824, + ForceDepositModification = 0x40000000, //=== PERMISSIONS COMPOSÉES ===// /// /// Faire des ventes. /// - SELL = MANAGE_PRODUCTS | MANAGE_DEPOSITS, - + Sell = ManageProducts | ManageDeposits, + //=== VALEURS SPÉCIALES ===// /// /// Aucune permission /// - NONE = 0, - + None = 0x0, + /// /// Toutes les permissions /// - ALL = 2047, + All = 0x7FF, } -public static class PermissionsExtensions +public class Permissions : EnumBitflagSet { - /// - /// Vérifie que est inclus dans ces permissions. - /// - /// Les permissions à tester. - /// si les permissions sont incluses, sinon - public static bool Includes(this Permissions @this, Permissions other) - { - return (@this & other) == other; - } + private static Permissions? current; - /// - /// Ajoute à ces permissions. - /// - /// Les permissions à ajouter. - /// Les permissions combinées. - public static Permissions Grant(this Permissions @this, Permissions other) - { - return @this | other; - } + public static Permissions Current => current ??= new Permissions(); - /// - /// Enlève les permissions non présentes dans de ces permissions. - /// - /// Les permissions à garder. - /// Les permissions restantes. - public static Permissions Mask(this Permissions @this, Permissions other) + private Permissions() { - return @this & other; + var seeProductsAndCategories = this.Flag(Permission.SeeProductsAndCategories); + this.Flag(Permission.ManageProducts, seeProductsAndCategories); + this.Flag(Permission.ManageCategories, seeProductsAndCategories); + + var seeAllUsersAndRoles = this.Flag(Permission.SeeAllUsersAndRoles); + var manageDeposits = this.Flag(Permission.ManageDeposits, seeAllUsersAndRoles); + var manageUsers = this.Flag(Permission.ManageUsers, manageDeposits); + this.Flag(Permission.ManageRoles, seeAllUsersAndRoles); + this.Flag(Permission.ResetMemberships, manageUsers); + + this.Flag(Permission.ReadLogs); + + this.Flag(Permission.ManageClients); + + this.Flag(Permission.ForceDepositModification); } } \ No newline at end of file diff --git a/Core/Users/Role.cs b/Core/Users/Role.cs index 2e3b0d5..c0ae752 100644 --- a/Core/Users/Role.cs +++ b/Core/Users/Role.cs @@ -9,7 +9,7 @@ public class Role { private int id; private string name; - private Permissions permissions; + private Permission permissions; /// /// L'identifiant du rôle. @@ -24,7 +24,7 @@ public class Role /// /// La somme des permissions attribuées au rôle. /// - public Permissions Permissions { get => this.permissions; set => this.permissions = value; } + public Permission Permissions { get => this.permissions; set => this.permissions = value; } /// /// Crée un rôle. @@ -32,7 +32,7 @@ public class Role /// L'identifiant du rôle. /// Le nom affiché du rôle. /// La somme des permissions attribuées au rôle. - public Role(int id, string name, Permissions permissions) + public Role(int id, string name, Permission permissions) { this.id = id; this.name = name; diff --git a/Core/Users/Session.cs b/Core/Users/Session.cs index c97f6fd..7188588 100644 --- a/Core/Users/Session.cs +++ b/Core/Users/Session.cs @@ -56,7 +56,7 @@ public class Session /// /// Les permissions accordées pour cette session. /// - public Permissions Permissions => this.client.Filter(this.user?.Role.Permissions ?? Permissions.NONE); + public Permission Permissions => this.client.Filter(this.user?.Role.Permissions ?? Permission.None); /// /// Indique si la session est expirée ou non, en prenant en compte l'inactivité. diff --git a/CoreTests/Applications/ClientTest.cs b/CoreTests/Applications/ClientTest.cs index 8461044..b7574f3 100644 --- a/CoreTests/Applications/ClientTest.cs +++ b/CoreTests/Applications/ClientTest.cs @@ -4,116 +4,116 @@ namespace GalliumPlus.Core.Applications; public class ClientTest { - public static readonly Regex ReApiKey = new Regex(@"^[A-Za-z0-9]{20}$"); - public static readonly Regex ReSecret = new Regex(@"^([A-Za-z0-9]{8})-([A-Za-z0-9]{12})-([A-Za-z0-9]{8})$"); + public static readonly Regex ReApiKey = new(@"^[A-Za-z0-9]{20}$"); + public static readonly Regex ReSecret = new(@"^([A-Za-z0-9]{8})-([A-Za-z0-9]{12})-([A-Za-z0-9]{8})$"); [Fact] public void ConstructorExisting() { - Client client = new( - id: 123, - apiKey: "test-api-key", - name: "App", - isEnabled: false, - granted: Permissions.SEE_PRODUCTS_AND_CATEGORIES, - allowed: Permissions.MANAGE_USERS - ); - - Assert.Equal(123, client.Id); - Assert.Equal("test-api-key", client.ApiKey); - Assert.Equal("App", client.Name); - Assert.False(client.IsEnabled); - Assert.Equal(Permissions.SEE_PRODUCTS_AND_CATEGORIES, client.Granted); - Assert.Equal(Permissions.MANAGE_USERS, client.Allowed); - Assert.False(client.AllowDirectUserLogin); - } + Client client = new( + id: 123, + apiKey: "test-api-key", + name: "App", + isEnabled: false, + granted: Permission.SeeProductsAndCategories, + allowed: Permission.ManageUsers + ); + + Assert.Equal(123, client.Id); + Assert.Equal("test-api-key", client.ApiKey); + Assert.Equal("App", client.Name); + Assert.False(client.IsEnabled); + Assert.Equal(Permission.SeeProductsAndCategories, client.Granted); + Assert.Equal(Permission.ManageUsers, client.Allowed); + Assert.False(client.AllowDirectUserLogin); + } [Fact] public void ConstructorNew() { - Client client = new( - name: "App", - allowed: Permissions.MANAGE_USERS - ); - - Assert.Matches(ReApiKey, client.ApiKey); - Assert.Equal("App", client.Name); - Assert.True(client.IsEnabled); - Assert.Equal(Permissions.NONE, client.Granted); - Assert.Equal(Permissions.MANAGE_USERS, client.Allowed); - Assert.True(client.AllowDirectUserLogin); - } + Client client = new( + name: "App", + allowed: Permission.ManageUsers + ); + + Assert.Matches(ReApiKey, client.ApiKey); + Assert.Equal("App", client.Name); + Assert.True(client.IsEnabled); + Assert.Equal(Permission.None, client.Granted); + Assert.Equal(Permission.ManageUsers, client.Allowed); + Assert.True(client.AllowDirectUserLogin); + } [Fact] public void AllowDirectUserLogin() { - Client client1 = new( - id: 123, - apiKey: "test-api-key", - name: "App", - isEnabled: true, - granted: Permissions.NONE, - allowed: Permissions.NONE - ); - Client client2 = new( - id: 123, - apiKey: "test-api-key", - name: "App", - isEnabled: false, - granted: Permissions.NONE, - allowed: Permissions.NONE - ); - Client client3 = new( - id: 123, - apiKey: "test-api-key", - name: "App", - isEnabled: true, - granted: Permissions.NONE, - allowed: Permissions.NONE - ); - Client client4 = new( - id: 123, - apiKey: "test-api-key", - name: "App", - isEnabled: false, - granted: Permissions.NONE, - allowed: Permissions.NONE - ); - - Assert.True(client1.AllowDirectUserLogin); - Assert.False(client2.AllowDirectUserLogin); - Assert.True(client3.AllowDirectUserLogin); - Assert.False(client4.AllowDirectUserLogin); - } + Client client1 = new( + id: 123, + apiKey: "test-api-key", + name: "App", + isEnabled: true, + granted: Permission.None, + allowed: Permission.None + ); + Client client2 = new( + id: 123, + apiKey: "test-api-key", + name: "App", + isEnabled: false, + granted: Permission.None, + allowed: Permission.None + ); + Client client3 = new( + id: 123, + apiKey: "test-api-key", + name: "App", + isEnabled: true, + granted: Permission.None, + allowed: Permission.None + ); + Client client4 = new( + id: 123, + apiKey: "test-api-key", + name: "App", + isEnabled: false, + granted: Permission.None, + allowed: Permission.None + ); + + Assert.True(client1.AllowDirectUserLogin); + Assert.False(client2.AllowDirectUserLogin); + Assert.True(client3.AllowDirectUserLogin); + Assert.False(client4.AllowDirectUserLogin); + } [Fact] public void Filter() { - // test simple + // test simple - Permissions before1 = Permissions.SEE_PRODUCTS_AND_CATEGORIES - | Permissions.SEE_ALL_USERS_AND_ROLES; + const Permission BEFORE1 = Permission.SeeProductsAndCategories + | Permission.SeeAllUsersAndRoles; - Client client1 = new Client( - name: "App 1", - allowed: Permissions.MANAGE_PRODUCTS, - granted: Permissions.MANAGE_PRODUCTS - ); - Permissions after1 = client1.Filter(before1); + var client1 = new Client( + name: "App 1", + allowed: Permission.ManageProducts, + granted: Permission.ManageProducts + ); + Permission after1 = client1.Filter(BEFORE1); - Assert.Equal(Permissions.MANAGE_PRODUCTS, after1); + Assert.Equal(Permission.ManageProducts, after1); - // test de priorité + // test de priorité - Permissions before2 = Permissions.READ_LOGS; + const Permission BEFORE2 = Permission.ReadLogs; - Client client2 = new Client( - name: "App 2", - granted: Permissions.SEE_PRODUCTS_AND_CATEGORIES, - allowed: Permissions.READ_LOGS // écrase la permission donnée précedemment - ); - Permissions after2 = client2.Filter(before2); + var client2 = new Client( + name: "App 2", + granted: Permission.SeeProductsAndCategories, + allowed: Permission.ReadLogs // écrase la permission donnée précédemment + ); + Permission after2 = client2.Filter(BEFORE2); - Assert.Equal(before2, after2); - } + Assert.Equal(BEFORE2, after2); + } } \ No newline at end of file diff --git a/CoreTests/Users/PermissionsTest.cs b/CoreTests/Users/PermissionsTest.cs index cfff409..602f512 100644 --- a/CoreTests/Users/PermissionsTest.cs +++ b/CoreTests/Users/PermissionsTest.cs @@ -2,20 +2,14 @@ namespace GalliumPlus.Core.Users; -using P = Permissions; +using P = Permission; public class PermissionsTest { + private static readonly Permissions perms = Permissions.Current; + [Fact] - public void Includes() - { - Assert.True(P.MANAGE_PRODUCTS.Includes(P.MANAGE_PRODUCTS)); - Assert.True(P.MANAGE_PRODUCTS.Includes(P.SEE_PRODUCTS_AND_CATEGORIES)); - Assert.False(P.MANAGE_PRODUCTS.Includes(P.MANAGE_CATEGORIES)); - } - - [Fact] - public void Regression() + public void NonRegression() { /* * ATTENTION !!! @@ -23,45 +17,18 @@ public void Regression() * Il faut éviter cela le plus possible pour ne pas attribuer * des permissions par erreur à certains utilisateurs. */ - Assert.Equal((P)1, P.SEE_PRODUCTS_AND_CATEGORIES); - Assert.Equal((P)2 | P.SEE_PRODUCTS_AND_CATEGORIES, P.MANAGE_PRODUCTS); - Assert.Equal((P)4 | P.SEE_PRODUCTS_AND_CATEGORIES, P.MANAGE_CATEGORIES); - Assert.Equal((P)8, P.SEE_ALL_USERS_AND_ROLES); - Assert.Equal((P)16 | P.SEE_ALL_USERS_AND_ROLES, P.MANAGE_DEPOSITS); - Assert.Equal((P)32 | P.MANAGE_DEPOSITS, P.MANAGE_USERS); - Assert.Equal((P)64 | P.SEE_ALL_USERS_AND_ROLES, P.MANAGE_ROLES); - Assert.Equal((P)128, P.READ_LOGS); - Assert.Equal((P)256 | P.MANAGE_USERS, P.RESET_MEMBERSHIPS); - Assert.Equal(P.MANAGE_PRODUCTS | P.MANAGE_DEPOSITS, P.SELL); - } - - [Fact] - public void None() - { - Assert.False(P.NONE.Includes(P.SEE_PRODUCTS_AND_CATEGORIES)); - Assert.False(P.NONE.Includes(P.MANAGE_PRODUCTS)); - Assert.False(P.NONE.Includes(P.MANAGE_CATEGORIES)); - Assert.False(P.NONE.Includes(P.SEE_ALL_USERS_AND_ROLES)); - Assert.False(P.NONE.Includes(P.MANAGE_DEPOSITS)); - Assert.False(P.NONE.Includes(P.MANAGE_USERS)); - Assert.False(P.NONE.Includes(P.MANAGE_ROLES)); - Assert.False(P.NONE.Includes(P.READ_LOGS)); - Assert.False(P.NONE.Includes(P.RESET_MEMBERSHIPS)); - Assert.False(P.NONE.Includes(P.SELL)); - } - - [Fact] - public void All() - { - Assert.True(P.ALL.Includes(P.SEE_PRODUCTS_AND_CATEGORIES)); - Assert.True(P.ALL.Includes(P.MANAGE_PRODUCTS)); - Assert.True(P.ALL.Includes(P.MANAGE_CATEGORIES)); - Assert.True(P.ALL.Includes(P.SEE_ALL_USERS_AND_ROLES)); - Assert.True(P.ALL.Includes(P.MANAGE_DEPOSITS)); - Assert.True(P.ALL.Includes(P.MANAGE_USERS)); - Assert.True(P.ALL.Includes(P.MANAGE_ROLES)); - Assert.True(P.ALL.Includes(P.READ_LOGS)); - Assert.True(P.ALL.Includes(P.RESET_MEMBERSHIPS)); - Assert.True(P.ALL.Includes(P.SELL)); + Assert.Equal((P)0x1, perms.Maximum(P.SeeProductsAndCategories)); + Assert.Equal((P)0x3, perms.Maximum(P.ManageProducts)); + Assert.Equal((P)0x5, perms.Maximum(P.ManageCategories)); + Assert.Equal((P)0x8, perms.Maximum(P.SeeAllUsersAndRoles)); + Assert.Equal((P)0x18, perms.Maximum(P.ManageDeposits)); + Assert.Equal((P)0x38, perms.Maximum(P.ManageUsers)); + Assert.Equal((P)0x48, perms.Maximum(P.ManageRoles)); + Assert.Equal((P)0x80, perms.Maximum(P.ReadLogs)); + Assert.Equal((P)0x138, perms.Maximum(P.ResetMemberships)); + Assert.Equal((P)0x200, perms.Maximum(P.ManageClients)); + Assert.Equal((P)0x400, perms.Maximum(P.UseDeveloperTools)); + Assert.Equal((P)0x40000000, perms.Maximum(P.ForceDepositModification)); + Assert.Equal((P)0x1B, perms.Maximum(P.Sell)); } } \ No newline at end of file diff --git a/CoreTests/Users/RoleTest.cs b/CoreTests/Users/RoleTest.cs index 0fa5673..7e1048f 100644 --- a/CoreTests/Users/RoleTest.cs +++ b/CoreTests/Users/RoleTest.cs @@ -7,10 +7,10 @@ public class RoleTest [Fact] public void Constructor() { - Role role = new Role(123, "Rôle", Permissions.MANAGE_PRODUCTS); + Role role = new Role(123, "Rôle", Permission.ManageProducts); Assert.Equal(123, role.Id); Assert.Equal("Rôle", role.Name); - Assert.Equal(Permissions.MANAGE_PRODUCTS, role.Permissions); + Assert.Equal(Permission.ManageProducts, role.Permissions); } } \ No newline at end of file diff --git a/CoreTests/Users/SessionTest.cs b/CoreTests/Users/SessionTest.cs index d9b991e..a0e6bf6 100644 --- a/CoreTests/Users/SessionTest.cs +++ b/CoreTests/Users/SessionTest.cs @@ -15,7 +15,7 @@ public class SessionTest 1, "mmansouri", new UserIdentity("Mehdi", "Mansouri", "mehdi.mansouri@iut-dijon.u-bourgogne.fr", "PROF"), - new Role(0, "Adhérent", Permissions.NONE), + new Role(0, "Adhérent", Permission.None), 21.30m, false ); @@ -40,26 +40,26 @@ public void Constructor() [Fact] public void PermissionsProperty() { - this.user.Role.Permissions = Permissions.SEE_PRODUCTS_AND_CATEGORIES - | Permissions.SEE_ALL_USERS_AND_ROLES; + this.user.Role.Permissions = Permission.SeeProductsAndCategories + | Permission.SeeAllUsersAndRoles; var client1 = new Client( name: "App 1", - granted: Permissions.MANAGE_PRODUCTS, - allowed: Permissions.MANAGE_PRODUCTS + granted: Permission.ManageProducts, + allowed: Permission.ManageProducts ); Session session1 = Session.LogIn(this.sessionOptions, client1, this.user); - Assert.Equal(Permissions.MANAGE_PRODUCTS, session1.Permissions); + Assert.Equal(Permission.ManageProducts, session1.Permissions); - this.user.Role.Permissions = Permissions.READ_LOGS; + this.user.Role.Permissions = Permission.ReadLogs; var client2 = new Client( name: "App 2", - granted: Permissions.SEE_PRODUCTS_AND_CATEGORIES, - allowed: Permissions.NONE // écrase la permission donnée précedemment + granted: Permission.SeeProductsAndCategories, + allowed: Permission.None // écrase la permission donnée précedemment ); Session session2 = Session.LogIn(this.sessionOptions, client2, this.user); - Assert.Equal(Permissions.NONE, session2.Permissions); + Assert.Equal(Permission.None, session2.Permissions); } [Fact] diff --git a/CoreTests/Users/UserTest.cs b/CoreTests/Users/UserTest.cs index 1ac385a..12d84fa 100644 --- a/CoreTests/Users/UserTest.cs +++ b/CoreTests/Users/UserTest.cs @@ -5,7 +5,7 @@ namespace GalliumPlus.Core.Users; public class UserTest { - private readonly Role profRole = new Role(0, "Prof", Permissions.NONE); + private readonly Role profRole = new Role(0, "Prof", Permission.None); private readonly PasswordInformation password = PasswordInformation.FromPassword("motdepasse123"); private string[] validIdList = new string[] { "am200927", "amdzznzs", "AM200927", "AMDzzNZs", "am200NDs" }; diff --git a/CoreTests/Usings.cs b/CoreTests/Usings.cs index 76023e8..4d11379 100644 --- a/CoreTests/Usings.cs +++ b/CoreTests/Usings.cs @@ -1,3 +1,2 @@ -global using GalliumPlus.WebApi.Core; global using System.Text.RegularExpressions; global using Xunit; diff --git a/External/Data/MariaDb/Implementations/RoleDao.cs b/External/Data/MariaDb/Implementations/RoleDao.cs index 988df79..2a711d4 100644 --- a/External/Data/MariaDb/Implementations/RoleDao.cs +++ b/External/Data/MariaDb/Implementations/RoleDao.cs @@ -36,7 +36,7 @@ private static Role Hydrate(MySqlDataReader row) return new Role( row.GetInt32("id"), row.GetString("name"), - (Permissions)row.GetInt32("permissions") + (Permission)row.GetInt32("permissions") ); } diff --git a/External/Data/MariaDb/Implementations/UserDao.cs b/External/Data/MariaDb/Implementations/UserDao.cs index 68cbd91..72983b7 100644 --- a/External/Data/MariaDb/Implementations/UserDao.cs +++ b/External/Data/MariaDb/Implementations/UserDao.cs @@ -70,7 +70,7 @@ internal static User Hydrate(MySqlDataReader row) Role role = new( row.GetInt32("roleId"), row.GetString("roleName"), - (Permissions)row.GetInt32("permissions") + (Permission)row.GetInt32("permissions") ); byte[] passwordBytes = new byte[32]; diff --git a/Fake/Data/Fake/ClientDao.cs b/Fake/Data/Fake/ClientDao.cs index 37f24cb..f7adb1b 100644 --- a/Fake/Data/Fake/ClientDao.cs +++ b/Fake/Data/Fake/ClientDao.cs @@ -15,8 +15,8 @@ public ClientDao() name: "Tests (normal)", apiKey: "test-api-key-normal", isEnabled: true, - allowed: Permissions.ALL, - granted: Permissions.NONE + allowed: Permission.All, + granted: Permission.None ) ); @@ -26,10 +26,10 @@ public ClientDao() name: "Tests (restricted)", apiKey: "test-api-key-restric", isEnabled: true, - allowed: Permissions.SEE_PRODUCTS_AND_CATEGORIES - | Permissions.SEE_ALL_USERS_AND_ROLES - | Permissions.READ_LOGS, - granted: Permissions.NONE + allowed: Permission.SeeProductsAndCategories + | Permission.SeeAllUsersAndRoles + | Permission.ReadLogs, + granted: Permission.None ) ); @@ -39,8 +39,8 @@ public ClientDao() name: "Tests (minimum)", apiKey: "test-api-key-minimum", isEnabled: true, - allowed: Permissions.ALL, - granted: Permissions.SEE_PRODUCTS_AND_CATEGORIES + allowed: Permission.All, + granted: Permission.SeeProductsAndCategories ) ); @@ -50,8 +50,8 @@ public ClientDao() name: "Tests (Modif acompte forcée)", apiKey: "test-api-key-macompf", isEnabled: true, - allowed: Permissions.ALL | Permissions.FORCE_DEPOSIT_MODIFICATION, - granted: Permissions.FORCE_DEPOSIT_MODIFICATION + allowed: Permission.All | Permission.ForceDepositModification, + granted: Permission.ForceDepositModification ) ); @@ -61,8 +61,8 @@ public ClientDao() name: "Tests (bot)", apiKey: "test-api-key-bot", isEnabled: true, - allowed: Permissions.SEE_PRODUCTS_AND_CATEGORIES, - granted: Permissions.SEE_PRODUCTS_AND_CATEGORIES + allowed: Permission.SeeProductsAndCategories, + granted: Permission.SeeProductsAndCategories ); this.Create(botClient); botClient.AppAccess = new AppAccess(botClient.Id, new OneTimeSecret(botKey.Hash, botKey.Salt)); @@ -72,8 +72,8 @@ public ClientDao() name: "Tests (SSO, direct)", apiKey: "test-api-key-sso-dir", isEnabled: true, - allowed: Permissions.ALL, - granted: Permissions.NONE + allowed: Permission.All, + granted: Permission.None ); this.Create(ssoClient1); ssoClient1.SameSignOn = new SameSignOn( @@ -91,8 +91,8 @@ public ClientDao() name: "Tests (SSO, externe)", apiKey: "test-api-key-sso-ext", isEnabled: true, - allowed: Permissions.ALL, - granted: Permissions.NONE + allowed: Permission.All, + granted: Permission.None ); this.Create(ssoClient2); ssoClient2.SameSignOn = new SameSignOn( @@ -110,8 +110,8 @@ public ClientDao() name: "Tests (SSO, applicatif)", apiKey: "test-api-key-sso-bot", isEnabled: true, - allowed: Permissions.ALL, - granted: Permissions.NONE + allowed: Permission.All, + granted: Permission.None ); this.Create(ssoClient3); ssoClient3.SameSignOn = new SameSignOn( diff --git a/Fake/Data/Fake/RoleDao.cs b/Fake/Data/Fake/RoleDao.cs index 42e8c88..678c2ac 100644 --- a/Fake/Data/Fake/RoleDao.cs +++ b/Fake/Data/Fake/RoleDao.cs @@ -9,19 +9,19 @@ public class RoleDao : BaseDaoWithAutoIncrement, IRoleDao public RoleDao() { this.Create( - new Role(0, "Adhérent", Permissions.NONE) + new Role(0, "Adhérent", Permission.None) ); this.Create( new Role(0, "CA", - Permissions.MANAGE_PRODUCTS - | Permissions.SEE_ALL_USERS_AND_ROLES - | Permissions.SELL - | Permissions.MANAGE_DEPOSITS + Permission.ManageProducts + | Permission.SeeAllUsersAndRoles + | Permission.Sell + | Permission.ManageDeposits ) ); this.Create( new Role(0, "Président", - Permissions.ALL + Permission.All ) ); } diff --git a/WebService/Controllers/CategoryController.cs b/WebService/Controllers/CategoryController.cs index c9ada6b..330ecd3 100644 --- a/WebService/Controllers/CategoryController.cs +++ b/WebService/Controllers/CategoryController.cs @@ -18,21 +18,21 @@ public class CategoryController(ICategoryDao categoryDao, IHistoryDao historyDao private CategoryDetails.Mapper mapper = new(); [HttpGet] - [RequiresPermissions(Permissions.SEE_PRODUCTS_AND_CATEGORIES)] + [RequiresPermissions(Permission.SeeProductsAndCategories)] public IActionResult Get() { return this.Json(this.mapper.FromModel(categoryDao.Read())); } [HttpGet("{id}", Name = "category")] - [RequiresPermissions(Permissions.SEE_PRODUCTS_AND_CATEGORIES)] + [RequiresPermissions(Permission.SeeProductsAndCategories)] public IActionResult Get(int id) { return this.Json(this.mapper.FromModel(categoryDao.Read(id))); } [HttpPost] - [RequiresPermissions(Permissions.MANAGE_CATEGORIES)] + [RequiresPermissions(Permission.ManageCategories)] public IActionResult Post(CategoryDetails newCategory) { Category category = categoryDao.Create(this.mapper.ToModel(newCategory)); @@ -49,7 +49,7 @@ public IActionResult Post(CategoryDetails newCategory) } [HttpPut("{id}")] - [RequiresPermissions(Permissions.MANAGE_CATEGORIES)] + [RequiresPermissions(Permission.ManageCategories)] public IActionResult Put(int id, CategoryDetails updatedCategory) { categoryDao.Update(id, this.mapper.ToModel(updatedCategory)); @@ -65,7 +65,7 @@ public IActionResult Put(int id, CategoryDetails updatedCategory) } [HttpDelete("{id}")] - [RequiresPermissions(Permissions.MANAGE_CATEGORIES)] + [RequiresPermissions(Permission.ManageCategories)] public IActionResult Delete(int id) { string categoryName = categoryDao.Read(id).Name; diff --git a/WebService/Controllers/ClientController.cs b/WebService/Controllers/ClientController.cs index 8561082..bf35496 100644 --- a/WebService/Controllers/ClientController.cs +++ b/WebService/Controllers/ClientController.cs @@ -16,14 +16,14 @@ public class ClientController(ClientService clientService, AuditService auditSer : GalliumController { [HttpGet] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult Get() { return this.Json(clientService.GetAll()); } [HttpGet("{id:int}", Name = "client")] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult Get(int id) { return this.Json(clientService.GetById(id)); @@ -37,7 +37,7 @@ public IActionResult GetSsoPublicInfo(string apiKey) } [HttpPost] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult Post(PartialClient newClient) { new PartialClient.Validator().ValidateAndThrow(newClient); @@ -47,7 +47,7 @@ public IActionResult Post(PartialClient newClient) } [HttpPost("{id:int}/app-access-secret")] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult PostNewAppAccessSecret(int id) { Client client = clientService.GetById(id); @@ -59,7 +59,7 @@ public IActionResult PostNewAppAccessSecret(int id) } [HttpPost("{id:int}/sso-secret")] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult PostNewSsoSecret(int id, SameSignOnSecretParameters parameters) { Client client = clientService.GetById(id); @@ -71,7 +71,7 @@ public IActionResult PostNewSsoSecret(int id, SameSignOnSecretParameters paramet } [HttpPut("{id}")] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult Put(int id, PartialClient clientUpdate) { new PartialClient.Validator().ValidateAndThrow(clientUpdate); @@ -84,7 +84,7 @@ public IActionResult Put(int id, PartialClient clientUpdate) } [HttpDelete("{id}")] - [RequiresPermissions(Permissions.MANAGE_CLIENTS)] + [RequiresPermissions(Permission.ManageClients)] public IActionResult Delete(int id) { Client client = clientService.Delete(id); diff --git a/WebService/Controllers/GalliumController.cs b/WebService/Controllers/GalliumController.cs index d84e066..b674165 100644 --- a/WebService/Controllers/GalliumController.cs +++ b/WebService/Controllers/GalliumController.cs @@ -93,9 +93,9 @@ public IActionResult Created(string route, string id, object? value = null) => this.Created(route, new { id }, value); [NonAction] - public void RequirePermissions(Permissions required) + public void RequirePermissions(Permission required) { - if (!this.Session!.Permissions.Includes(required)) + if (!Permissions.Current.IsSupersetOf(this.Session!.Permissions, Permissions.Current.Maximum(required))) { throw new PermissionDeniedException(required); } diff --git a/WebService/Controllers/LogsController.cs b/WebService/Controllers/LogsController.cs index c4228cd..4144f82 100644 --- a/WebService/Controllers/LogsController.cs +++ b/WebService/Controllers/LogsController.cs @@ -29,7 +29,7 @@ private static DateTime ParseTime(string dateTime, string queryParameter) } [HttpGet("logs")] - [RequiresPermissions(Permissions.READ_LOGS)] + [RequiresPermissions(Permission.ReadLogs)] public IActionResult GetLogs( int pageSize = 50, int pageIndex = 0, @@ -47,7 +47,7 @@ public IActionResult GetLogs( } [HttpGet("history")] - [RequiresPermissions(Permissions.READ_LOGS)] + [RequiresPermissions(Permission.ReadLogs)] public IActionResult GetLegacyHistory( int pageSize = 50, int pageIndex = 0, diff --git a/WebService/Controllers/OrderController.cs b/WebService/Controllers/OrderController.cs index e6f12f1..742b8d4 100644 --- a/WebService/Controllers/OrderController.cs +++ b/WebService/Controllers/OrderController.cs @@ -28,7 +28,7 @@ public OrderController(IProductDao productDao, IUserDao userDao, IHistoryDao his } [HttpPost] - [RequiresPermissions(Permissions.SELL)] + [RequiresPermissions(Permission.Sell)] public IActionResult Post(OrderSummary newOrder) { Order order = this.mapper.ToModel(newOrder); diff --git a/WebService/Controllers/ProductController.cs b/WebService/Controllers/ProductController.cs index b1df9b8..221c9e1 100644 --- a/WebService/Controllers/ProductController.cs +++ b/WebService/Controllers/ProductController.cs @@ -29,14 +29,14 @@ public ProductController(IProductDao productDao, IHistoryDao historyDao) } [HttpGet] - [RequiresPermissions(Permissions.SEE_PRODUCTS_AND_CATEGORIES)] + [RequiresPermissions(Permission.SeeProductsAndCategories)] public IActionResult Get() { return this.Json(this.summaryMapper.FromModel(this.productDao.Read())); } [HttpGet("{id}", Name = "product")] - [RequiresPermissions(Permissions.SEE_PRODUCTS_AND_CATEGORIES)] + [RequiresPermissions(Permission.SeeProductsAndCategories)] public IActionResult Get(int id) { return this.Json(this.detailsMapper.FromModel(this.productDao.Read(id))); @@ -44,14 +44,14 @@ public IActionResult Get(int id) [HttpGet("{id}/image")] [Produces("image/png")] - [RequiresPermissions(Permissions.SEE_PRODUCTS_AND_CATEGORIES)] + [RequiresPermissions(Permission.SeeProductsAndCategories)] public IActionResult GetImage(int id) { return this.File(this.productDao.ReadImage(id).Bytes, "image/png"); } [HttpPost] - [RequiresPermissions(Permissions.MANAGE_PRODUCTS)] + [RequiresPermissions(Permission.ManageProducts)] public IActionResult Post(ProductSummary newProduct) { Product product = this.productDao.Create(this.summaryMapper.ToModel(newProduct)); @@ -67,7 +67,7 @@ public IActionResult Post(ProductSummary newProduct) } [HttpPut("{id}")] - [RequiresPermissions(Permissions.MANAGE_PRODUCTS)] + [RequiresPermissions(Permission.ManageProducts)] public IActionResult Put(int id, ProductSummary updatedProduct) { this.productDao.Update(id, this.summaryMapper.ToModel(updatedProduct)); @@ -84,7 +84,7 @@ public IActionResult Put(int id, ProductSummary updatedProduct) [HttpPut("{id}/image")] [ConsumesProductImages] - [RequiresPermissions(Permissions.MANAGE_PRODUCTS)] + [RequiresPermissions(Permission.ManageProducts)] public async Task PutImage(int id, [FromBody] byte[] image) { ProductImage normalisedImage = await ProductImage.FromAnyImage(image, this.Request.ContentType ?? ""); @@ -102,7 +102,7 @@ public async Task PutImage(int id, [FromBody] byte[] image) } [HttpDelete("{id}")] - [RequiresPermissions(Permissions.MANAGE_PRODUCTS)] + [RequiresPermissions(Permission.ManageProducts)] public IActionResult Delete(int id) { string productName = this.productDao.Read(id).Name; @@ -119,7 +119,7 @@ public IActionResult Delete(int id) } [HttpDelete("{id}/image")] - [RequiresPermissions(Permissions.MANAGE_PRODUCTS)] + [RequiresPermissions(Permission.ManageProducts)] public IActionResult DeleteImage(int id) { string productName = this.productDao.Read(id).Name; diff --git a/WebService/Controllers/RoleController.cs b/WebService/Controllers/RoleController.cs index 3d2d9d2..59ebf06 100644 --- a/WebService/Controllers/RoleController.cs +++ b/WebService/Controllers/RoleController.cs @@ -26,21 +26,21 @@ public RoleController(IRoleDao roleDao, IHistoryDao historyDao) } [HttpGet] - [RequiresPermissions(Permissions.SEE_ALL_USERS_AND_ROLES)] + [RequiresPermissions(Permission.SeeAllUsersAndRoles)] public IActionResult Get() { return this.Json(this.mapper.FromModel(this.roleDao.Read())); } [HttpGet("{id}", Name = "role")] - [RequiresPermissions(Permissions.SEE_ALL_USERS_AND_ROLES)] + [RequiresPermissions(Permission.SeeAllUsersAndRoles)] public IActionResult Get(int id) { return this.Json(this.mapper.FromModel(this.roleDao.Read(id))); } [HttpPost] - [RequiresPermissions(Permissions.MANAGE_ROLES)] + [RequiresPermissions(Permission.ManageRoles)] public IActionResult Post(RoleDetails newRole) { Role role = this.roleDao.Create(this.mapper.ToModel(newRole)); @@ -56,7 +56,7 @@ public IActionResult Post(RoleDetails newRole) } [HttpPut("{id}")] - [RequiresPermissions(Permissions.MANAGE_ROLES)] + [RequiresPermissions(Permission.ManageRoles)] public IActionResult Put(int id, RoleDetails updatedRole) { this.roleDao.Update(id, this.mapper.ToModel(updatedRole)); @@ -72,7 +72,7 @@ public IActionResult Put(int id, RoleDetails updatedRole) } [HttpDelete("{id}")] - [RequiresPermissions(Permissions.MANAGE_ROLES)] + [RequiresPermissions(Permission.ManageRoles)] public IActionResult Delete(int id) { string roleName = this.roleDao.Read(id).Name; diff --git a/WebService/Controllers/UserController.cs b/WebService/Controllers/UserController.cs index 51f3a7c..3e37468 100644 --- a/WebService/Controllers/UserController.cs +++ b/WebService/Controllers/UserController.cs @@ -31,7 +31,7 @@ AuditService auditService private UserDetails.Mapper detailsMapper = new(); [HttpGet] - [RequiresPermissions(Permissions.SEE_ALL_USERS_AND_ROLES)] + [RequiresPermissions(Permission.SeeAllUsersAndRoles)] public IActionResult Get() { return this.Json(this.summaryMapper.FromModel(userDao.Read())); @@ -42,7 +42,7 @@ public IActionResult Get(string id) { if (id != this.User!.Id) { - this.RequirePermissions(Permissions.SEE_ALL_USERS_AND_ROLES); + this.RequirePermissions(Permission.SeeAllUsersAndRoles); } return this.Json(this.detailsMapper.FromModel(userDao.Read(id))); @@ -62,13 +62,16 @@ public IActionResult GetSelf() } [HttpPost] - [RequiresPermissions(Permissions.MANAGE_USERS)] + [RequiresPermissions(Permission.ManageUsers)] public async Task Post(UserSummary newUser) { historyDao.CheckUserNotInHistory(newUser.Id); User user = this.summaryMapper.ToModel(newUser); - if (!this.Session!.Permissions.Includes(Permissions.FORCE_DEPOSIT_MODIFICATION)) + if (!Permissions.Current.IsSupersetOf( + this.Session!.Permissions, + Permission.ForceDepositModification + )) { user.Deposit = null; } @@ -94,7 +97,7 @@ public async Task Post(UserSummary newUser) } [HttpPut("{id}")] - [RequiresPermissions(Permissions.MANAGE_USERS)] + [RequiresPermissions(Permission.ManageUsers)] public IActionResult Put(string id, UserSummary updatedUser) { if (updatedUser.Id != id) @@ -102,7 +105,10 @@ public IActionResult Put(string id, UserSummary updatedUser) historyDao.CheckUserNotInHistory(updatedUser.Id); } - if (this.Session!.Permissions.Includes(Permissions.FORCE_DEPOSIT_MODIFICATION)) + if (Permissions.Current.IsSupersetOf( + this.Session!.Permissions, + Permission.ForceDepositModification + )) { userDao.UpdateForcingDepositModification(id, this.summaryMapper.ToModel(updatedUser)); } @@ -128,7 +134,7 @@ public IActionResult Put(string id, UserSummary updatedUser) } [HttpDelete("{id}")] - [RequiresPermissions(Permissions.MANAGE_USERS)] + [RequiresPermissions(Permission.ManageUsers)] public IActionResult Delete(string id) { User userToDelete = userDao.Read(id); @@ -152,7 +158,7 @@ public IActionResult Delete(string id) } [HttpPost("{id}/deposit")] - [RequiresPermissions(Permissions.MANAGE_DEPOSITS)] + [RequiresPermissions(Permission.ManageDeposits)] public IActionResult PostDeposit(string id, [FromBody] decimal added) { decimal? currentAmount = userDao.ReadDeposit(id);; @@ -175,7 +181,7 @@ public IActionResult PostDeposit(string id, [FromBody] decimal added) } [HttpDelete("{id}/deposit")] - [RequiresPermissions(Permissions.MANAGE_DEPOSITS)] + [RequiresPermissions(Permission.ManageDeposits)] public IActionResult PostDeposit(string id) { User user = userDao.Read(id); diff --git a/WebService/Dto/Legacy/OrderSummary.cs b/WebService/Dto/Legacy/OrderSummary.cs index 4d69141..7c67c64 100644 --- a/WebService/Dto/Legacy/OrderSummary.cs +++ b/WebService/Dto/Legacy/OrderSummary.cs @@ -74,7 +74,7 @@ private static User BuildAnonymousMember() 999999999, "anonymousmember00000000000", // pas possible d'être rentré en BDD new UserIdentity("Anonyme", "", "", ""), - new Role(-1, "Membre anonyme", Permissions.NONE), + new Role(-1, "Membre anonyme", Permission.None), 0.00m, true ); diff --git a/WebService/Dto/Legacy/RoleDetails.cs b/WebService/Dto/Legacy/RoleDetails.cs index 43bda96..6e71ef3 100644 --- a/WebService/Dto/Legacy/RoleDetails.cs +++ b/WebService/Dto/Legacy/RoleDetails.cs @@ -31,7 +31,7 @@ public override RoleDetails FromModel(Role model) public override Role ToModel(RoleDetails dto) { - return new Role(dto.Id, dto.Name, (Permissions)dto.Permissions!.Value); + return new Role(dto.Id, dto.Name, (Permission)dto.Permissions!.Value); } } } diff --git a/WebService/Dto/PermissionsCodeConverter.cs b/WebService/Dto/PermissionCodeConverter.cs similarity index 50% rename from WebService/Dto/PermissionsCodeConverter.cs rename to WebService/Dto/PermissionCodeConverter.cs index a35cafd..5a17327 100644 --- a/WebService/Dto/PermissionsCodeConverter.cs +++ b/WebService/Dto/PermissionCodeConverter.cs @@ -4,14 +4,14 @@ namespace GalliumPlus.WebService.Dto; -public class PermissionsCodeConverter : JsonConverter +public class PermissionCodeConverter : JsonConverter { - public override Permissions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override Permission Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return (Permissions)reader.GetUInt32(); + return (Permission)reader.GetUInt32(); } - public override void Write(Utf8JsonWriter writer, Permissions value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, Permission value, JsonSerializerOptions options) { writer.WriteNumberValue((uint)value); } diff --git a/WebService/Middleware/Authorization/PermissionsFilter.cs b/WebService/Middleware/Authorization/PermissionsFilter.cs index 01d440a..44996d2 100644 --- a/WebService/Middleware/Authorization/PermissionsFilter.cs +++ b/WebService/Middleware/Authorization/PermissionsFilter.cs @@ -13,11 +13,11 @@ namespace GalliumPlus.WebService.Middleware.Authorization public class PermissionsFilter : IAuthorizationFilter { /// - /// Récupère les permissions requise par une action. + /// Récupère les permissions requises par une action. /// /// L'action en question. /// Les permissions demandées. - private static Permissions RequiresPermissions(ActionDescriptor action) + private static Permission RequiresPermissions(ActionDescriptor action) { foreach (object metadata in action.EndpointMetadata) { @@ -26,20 +26,20 @@ private static Permissions RequiresPermissions(ActionDescriptor action) return permissions.Required; } } - return Permissions.NONE; + return Permission.None; } public void OnAuthorization(AuthorizationFilterContext context) { - Permissions required = RequiresPermissions(context.ActionDescriptor); + Permission required = RequiresPermissions(context.ActionDescriptor); // OK, aucune permission demandée - if (required == Permissions.NONE) return; + if (required == Permission.None) return; Session session = (Session)context.HttpContext.Items["Session"]!; - // OK, l'utilisateur a toutes les permissions nécéssaires - if (session.Permissions.Includes(required)) return; + // OK, l'utilisateur a toutes les permissions nécessaires + if (Permissions.Current.IsSupersetOf(session.Permissions, required)) return; string messageAction = context.HttpContext.Request.Method switch { diff --git a/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs b/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs index 3a2ab23..b6be910 100644 --- a/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs +++ b/WebService/Middleware/Authorization/RequiresPermissionsAttribute.cs @@ -8,18 +8,18 @@ namespace GalliumPlus.WebService.Middleware.Authorization [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class RequiresPermissionsAttribute : Attribute { - private Permissions required; + private Permission required; /// /// Les permissions requises. /// - public Permissions Required => this.required; + public Permission Required => this.required; /// /// /// /// - public RequiresPermissionsAttribute(Permissions required) + public RequiresPermissionsAttribute(Permission required) { this.required = required; } diff --git a/WebService/Program.cs b/WebService/Program.cs index 34008e4..11d47dd 100644 --- a/WebService/Program.cs +++ b/WebService/Program.cs @@ -136,7 +136,7 @@ // garde les noms de propriétés tels quels options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; // sérialise les permissions et les portées sous forme numérique - options.JsonSerializerOptions.Converters.Add(new PermissionsCodeConverter()); + options.JsonSerializerOptions.Converters.Add(new PermissionCodeConverter()); options.JsonSerializerOptions.Converters.Add(new SameSignOnScopesCodeConverter()); // et les autres énumérations sous forme de texte options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); diff --git a/WebService/Services/AccessService.cs b/WebService/Services/AccessService.cs index 8882cdf..f5d431b 100644 --- a/WebService/Services/AccessService.cs +++ b/WebService/Services/AccessService.cs @@ -111,19 +111,19 @@ public class AccessService(IClientDao clientDao, ISessionDao sessionDao, Gallium .AddClaim(JWT_CLAIM_USER_ID, user.Id) .AddClaim(JWT_CLAIM_IMMUTABLE_UID, user.Iuid); // autres portées - if (app.SameSignOn.Scope.Includes(SameSignOnScopes.Identity)) + if (SameSignOnScopes.Current.Identity.IsIn(app.SameSignOn.Scope)) { jwtBuilder .AddClaim(JWT_CLAIM_FIRST_NAME, user.Identity.FirstName) .AddClaim(JWT_CLAIM_LAST_NAME, user.Identity.LastName); } - if (app.SameSignOn.Scope.Includes(SameSignOnScopes.Email)) + if (SameSignOnScopes.Current.Email.IsIn(app.SameSignOn.Scope)) { jwtBuilder.AddClaim(JWT_CLAIM_EMAIL, user.Identity.Email); } - if (app.SameSignOn.Scope.Includes(SameSignOnScopes.Role)) + if (SameSignOnScopes.Current.Role.IsIn(app.SameSignOn.Scope)) { jwtBuilder .AddClaim(JWT_CLAIM_ROLE, user.Role.Id) diff --git a/WebService/Services/ClientService.cs b/WebService/Services/ClientService.cs index 2f9dac4..9b6f237 100644 --- a/WebService/Services/ClientService.cs +++ b/WebService/Services/ClientService.cs @@ -30,8 +30,8 @@ public Client Create(PartialClient newClient) new Client( newClient.Name, newClient.IsEnabled, - (Permissions)newClient.Allowed, - (Permissions)newClient.Granted + (Permission)newClient.Allowed, + (Permission)newClient.Granted ) ); @@ -96,8 +96,8 @@ public Client Update(int id, PartialClient clientUpdate) Client client = clientDao.Read(id); client.Name = clientUpdate.Name; - client.Allowed = (Permissions)clientUpdate.Allowed; - client.Granted = (Permissions)clientUpdate.Granted; + client.Allowed = (Permission)clientUpdate.Allowed; + client.Granted = (Permission)clientUpdate.Granted; client.IsEnabled = clientUpdate.IsEnabled; clientDao.Update(id, client); From 795f7c25e5bf9941dc059c96225e5bbbea4e471d Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 3 May 2025 15:44:16 +0200 Subject: [PATCH 13/20] feat: renommer `PricingType` en `PriceList` --- .tests/tests/pricing_tests.py | 134 +++++++++--------- .tests/utils/expectations.py | 17 +-- Core/Items/{PricingType.cs => PriceList.cs} | 4 +- Core/Security/Permissions.cs | 8 +- .../MariaDb/Implementations/ProductDao.cs | 12 +- .../RenameColumn_Client_revoked_allowed.cs | 19 +++ WebService/Controllers/PriceListController.cs | 19 +++ WebService/Services/PricingService.cs | 6 +- 8 files changed, 123 insertions(+), 96 deletions(-) rename Core/Items/{PricingType.cs => PriceList.cs} (92%) create mode 100644 External/Data/MariaDb/Migrations/v1_03_00/RenameColumn_Client_revoked_allowed.cs create mode 100644 WebService/Controllers/PriceListController.cs diff --git a/.tests/tests/pricing_tests.py b/.tests/tests/pricing_tests.py index daef031..374c382 100644 --- a/.tests/tests/pricing_tests.py +++ b/.tests/tests/pricing_tests.py @@ -1,8 +1,8 @@ from utils.test_base import TestBase from utils.auth import BearerAuth -from .history_tests_helpers import HistoryTestHelpers +from .audit_tests_helpers import AuditTestHelpers -INVALID_PRICING_TYPES = [ +INVALID_PRICE_LISTS = [ { "shortName": "EtiSmash", "requiresMembership": False, @@ -62,104 +62,100 @@ def setUp(self): def tearDown(self): self.unset_authentication() - def test_pricing_type_get_all(self): - response = self.get("pricing_types") + def test_price_list_get_all(self): + response = self.get("price-lists") self.expect(response.status_code).to.be.equal_to(200) - pricing_types = response.json() - self.expect(pricing_types).to.be.a(list)._and._not.empty() + price_lists = response.json() + self.expect(price_lists).to.be.a(list)._and._not.empty() - pricing_type = pricing_types[0] - self.expect(pricing_type).to.have.an_item("id").of.type(int) - self.expect(pricing_type).to.have.an_item("longName").of.type(str) - self.expect(pricing_type).to.have.an_item("shortName").of.type(str) - self.expect(pricing_type).to.have.an_item("requiresMembership").of.type(bool) + price_list = price_lists[0] + self.expect(price_list).to.have.an_item("id").of.type(int) + self.expect(price_list).to.have.an_item("longName").of.type(str) + self.expect(price_list).to.have.an_item("shortName").of.type(str) + self.expect(price_list).to.have.an_item("requiresMembership").of.type(bool) - def test_pricing_type_get_one(self): - existing_id = self.get("pricing_types").json()[0]["id"] + def test_price_list_get_one(self): + existing_id = self.get("price-lists").json()[0]["id"] invalid_id = 12345 # Test avec un tarif existant - response = self.get(f"pricing_types/{existing_id}") + response = self.get(f"price-lists/{existing_id}") self.expect(response.status_code).to.be.equal_to(200) - pricing_type = response.json() - self.expect(pricing_type).to.be.a(dict) - self.expect(pricing_type).to.have.an_item("id").of.type(int) - self.expect(pricing_type).to.have.an_item("longName").of.type(str) - self.expect(pricing_type).to.have.an_item("shortName").of.type(str) - self.expect(pricing_type).to.have.an_item("requiresMembership").of.type(bool) + price_list = response.json() + self.expect(price_list).to.be.a(dict) + self.expect(price_list).to.have.an_item("id").of.type(int) + self.expect(price_list).to.have.an_item("longName").of.type(str) + self.expect(price_list).to.have.an_item("shortName").of.type(str) + self.expect(price_list).to.have.an_item("requiresMembership").of.type(bool) # Test avec un tarif inexistant - response = self.get(f"pricing_types/{invalid_id}") + response = self.get(f"price-lists/{invalid_id}") self.expect(response.status_code).to.be.equal_to(404) - def test_pricing_type_create(self): - previous_pricing_type_count = len(self.get("pricing_types").json()) + def test_price_list_create(self): + previous_price_list_count = len(self.get("price-lists").json()) - valid_pricing_type = { + valid_price_list = { "longName": "Tarif spécial EtiSmash", "shortName": "EtiSmash", "requiresMembership": False, } with self.audit.watch(): - response = self.post("pricing_types", valid_pricing_type) + response = self.post("price-lists", valid_price_list) self.expect(response.status_code).to.be.equal_to(201) location = self.expect(response.headers).to.have.an_item("Location").value - created_pricing_type = response.json() - self.expect(created_pricing_type).to.have.an_item("id") - self.expect(created_pricing_type["name"]).to.be.equal_to("Jus") + created_price_list = response.json() + self.expect(created_price_list).to.have.an_item("id") + self.expect(created_price_list["name"]).to.be.equal_to("Jus") response = self.get(location) self.expect(response.status_code).to.be.equal_to(200) - created_pricing_type = response.json() - self.expect(created_pricing_type["name"]).to.be.equal_to("Jus") + created_price_list = response.json() + self.expect(created_price_list["name"]).to.be.equal_to("Jus") - new_pricing_type_count = len(self.get("pricing_types").json()) - self.expect(new_pricing_type_count).to.be.equal_to( - previous_pricing_type_count + 1 - ) + new_price_list_count = len(self.get("price-lists").json()) + self.expect(new_price_list_count).to.be.equal_to(previous_price_list_count + 1) - self.audit.expect_entries( - self.audit.pricing_type_added_action("Jus", "eb069420") - ) + self.audit.expect_entries(self.audit.price_list_added_action("Jus", "eb069420")) - for invalid_pricing_type in INVALID_PRICING_TYPES: - response = self.post("pricing_types", invalid_pricing_type) + for invalid_price_list in INVALID_PRICE_LISTS: + response = self.post("price-lists", invalid_price_list) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" ) - def test_pricing_type_edit(self): - valid_pricing_type = self.get("pricing_types").json()[-1] - valid_pricing_type.update(Name="Jus") - pricing_type_id = valid_pricing_type["id"] + def test_price_list_edit(self): + valid_price_list = self.get("price-lists").json()[-1] + valid_price_list.update(Name="Jus") + price_list_id = valid_price_list["id"] with self.audit.watch(): - response = self.put(f"pricing_types/{pricing_type_id}", valid_pricing_type) + response = self.put(f"price-lists/{price_list_id}", valid_price_list) self.expect(response.status_code).to.be.equal_to(200) - edited_pricing_type = self.get(f"pricing_types/{pricing_type_id}").json() - self.expect(edited_pricing_type["name"]).to.be.equal_to("Jus") + edited_price_list = self.get(f"price-lists/{price_list_id}").json() + self.expect(edited_price_list["name"]).to.be.equal_to("Jus") self.audit.expect_entries( - self.audit.pricing_type_modified_action("Jus", "eb069420") + self.audit.price_list_modified_action("Jus", "eb069420") ) - # pricing_type qui n'existe pas + # price_list qui n'existe pas - response = self.put("pricing_types/12345", valid_pricing_type) + response = self.put("price-lists/12345", valid_price_list) self.expect(response.status_code).to.be.equal_to(404) # Informations manquantes - invalid_pricing_type = {} - response = self.put(f"pricing_types/{pricing_type_id}", invalid_pricing_type) + invalid_price_list = {} + response = self.put(f"price-lists/{price_list_id}", invalid_price_list) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" @@ -167,16 +163,16 @@ def test_pricing_type_edit(self): # Informations non valides - invalid_pricing_type = {"name": ""} - response = self.put(f"pricing_types/{pricing_type_id}", invalid_pricing_type) + invalid_price_list = {"name": ""} + response = self.put(f"price-lists/{price_list_id}", invalid_price_list) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" ) - def test_pricing_type_delete(self): - pricing_type = {"name": "Jus"} - location = self.post("pricing_types", pricing_type).headers["Location"] + def test_price_list_delete(self): + price_list = {"name": "Jus"} + location = self.post("price-lists", price_list).headers["Location"] # On supprimme la catégorie @@ -185,7 +181,7 @@ def test_pricing_type_delete(self): self.expect(response.status_code).to.be.equal_to(200) self.audit.expect_entries( - self.audit.pricing_type_deleted_action("Jus", "eb069420") + self.audit.price_list_deleted_action("Jus", "eb069420") ) # La catégorie n'existe plus @@ -198,30 +194,30 @@ def test_pricing_type_delete(self): response = self.delete(location) self.expect(response.status_code).to.be.equal_to(404) - def test_pricing_type_no_authentification(self): + def test_price_list_no_authentification(self): self.unset_authentication() - response = self.get("pricing_types") + response = self.get("price-lists") self.expect(response.status_code).to.be.equal_to(401) - response = self.post("pricing_types", {}) + response = self.post("price-lists", {}) self.expect(response.status_code).to.be.equal_to(401) - response = self.get("pricing_types/1") + response = self.get("price-lists/1") self.expect(response.status_code).to.be.equal_to(401) - response = self.put("pricing_types/1", {}) + response = self.put("price-lists/1", {}) self.expect(response.status_code).to.be.equal_to(401) - response = self.delete("pricing_types/1") + response = self.delete("price-lists/1") self.expect(response.status_code).to.be.equal_to(401) - def test_pricing_type_no_permission(self): + def test_price_list_no_permission(self): self.set_authentication(BearerAuth("12345678901234567890")) - response = self.get("pricing_types") + response = self.get("price-lists") self.expect(response.status_code).to.be.equal_to(403) - response = self.post("pricing_types", {"name": "/"}) + response = self.post("price-lists", {"name": "/"}) self.expect(response.status_code).to.be.equal_to(403) - response = self.get("pricing_types/1") + response = self.get("price-lists/1") self.expect(response.status_code).to.be.equal_to(403) - response = self.put("pricing_types/1", {"name": "/"}) + response = self.put("price-lists/1", {"name": "/"}) self.expect(response.status_code).to.be.equal_to(403) - response = self.delete("pricing_types/1") + response = self.delete("price-lists/1") self.expect(response.status_code).to.be.equal_to(403) diff --git a/.tests/utils/expectations.py b/.tests/utils/expectations.py index c831079..45ed2f3 100644 --- a/.tests/utils/expectations.py +++ b/.tests/utils/expectations.py @@ -16,24 +16,11 @@ def __init__(self, test_case: TestCase, value): self.nullable = False def __check_measurable(self): - if not ( - isinstance(self.value, str) - or isinstance(self.value, list) - or isinstance(self.value, tuple) - or isinstance(self.value, dict) - or isinstance(self.value, set) - or hasattr(self.value, "__len__") - ): + if not hasattr(self.value, "__len__"): raise ValueError(f"Values of type {type(self.value)} have no length") def __check_collection(self): - if not ( - isinstance(self.value, dict) - or isinstance(self.value, list) - or isinstance(self.value, tuple) - or isinstance(self.value, set) - or hasattr(self.value, "__getitem__") - ): + if not hasattr(self.value, "__getitem__"): raise ValueError(f"{type(self.value)} is not a collection type") # ASSERTIONS diff --git a/Core/Items/PricingType.cs b/Core/Items/PriceList.cs similarity index 92% rename from Core/Items/PricingType.cs rename to Core/Items/PriceList.cs index 0f06b55..60ee16a 100644 --- a/Core/Items/PricingType.cs +++ b/Core/Items/PriceList.cs @@ -6,7 +6,7 @@ namespace GalliumPlus.Core.Items; /// /// Représente un tarif, comme le tarif adhérent ou le tarif non-adhérent. /// -public class PricingType +public class PriceList { [PrimaryKey(AutoIncrement = true)] private readonly long id; @@ -45,7 +45,7 @@ public class PricingType /// Le nom abrégé du tarif. /// Le nom complet du tarif. /// Si le tarif est applicable uniquement aux adhérents. - public PricingType(long id, string shortName, string longName, bool requiresMembership) + public PriceList(long id, string shortName, string longName, bool requiresMembership) { this.id = id; this.shortName = shortName; diff --git a/Core/Security/Permissions.cs b/Core/Security/Permissions.cs index 63bc343..8d15541 100644 --- a/Core/Security/Permissions.cs +++ b/Core/Security/Permissions.cs @@ -73,6 +73,11 @@ public enum Permission : uint /// Permission d'utiliser les différents outils de développement associés à Gallium. /// UseDeveloperTools = 0x400, + + /// + /// Permission de gérer les tarifs des articles. + /// + ManagePrices = 0x800, /// /// Permission de modifier les acomptes manuellement. Cette permission ne doit pas pouvoir être donnée à quiconque, @@ -97,7 +102,7 @@ public enum Permission : uint /// /// Toutes les permissions /// - All = 0x7FF, + All = 0x7FF } public class Permissions : EnumBitflagSet @@ -111,6 +116,7 @@ private Permissions() var seeProductsAndCategories = this.Flag(Permission.SeeProductsAndCategories); this.Flag(Permission.ManageProducts, seeProductsAndCategories); this.Flag(Permission.ManageCategories, seeProductsAndCategories); + this.Flag(Permission.ManagePrices, seeProductsAndCategories); var seeAllUsersAndRoles = this.Flag(Permission.SeeAllUsersAndRoles); var manageDeposits = this.Flag(Permission.ManageDeposits, seeAllUsersAndRoles); diff --git a/External/Data/MariaDb/Implementations/ProductDao.cs b/External/Data/MariaDb/Implementations/ProductDao.cs index f1dbdc3..a5acba8 100644 --- a/External/Data/MariaDb/Implementations/ProductDao.cs +++ b/External/Data/MariaDb/Implementations/ProductDao.cs @@ -35,7 +35,7 @@ public Product Create(Product client) .Value("expirationDate", (object?)null) .Value("expiresUponExhaustion", false) .Value("type", - db.Select("id").From("PricingType").Where(SQL.AND( + db.Select("id").From("PriceList").Where(SQL.AND( db.Column("applicableDuring") == 1, db.Column("requiresMembership") == 0 )) @@ -50,7 +50,7 @@ public Product Create(Product client) .Value("expirationDate", (object?)null) .Value("expiresUponExhaustion", false) .Value("type", - db.Select("id").From("PricingType").Where(SQL.AND( + db.Select("id").From("PriceList").Where(SQL.AND( db.Column("applicableDuring") == 1, db.Column("requiresMembership") == 1 )) @@ -94,7 +94,7 @@ public IEnumerable Read() var cmd = connection.CreateCommand(); cmd.CommandText = @" WITH pv AS ( - SELECT p.price, p.item, pt.requiresMembership FROM Price p JOIN PricingType pt ON p.type = pt.id AND pt.applicableDuring = 1 + SELECT p.price, p.item, pt.requiresMembership FROM Price p JOIN PriceList pt ON p.type = pt.id AND pt.applicableDuring = 1 ) SELECT i.id AS productId , i.name AS productName @@ -123,7 +123,7 @@ public Product Read(int key) var cmd = connection.CreateCommand(); cmd.CommandText = @" WITH pv AS ( - SELECT p.price, p.item, pt.requiresMembership FROM Price p JOIN PricingType pt ON p.type = pt.id AND pt.applicableDuring = 1 + SELECT p.price, p.item, pt.requiresMembership FROM Price p JOIN PriceList pt ON p.type = pt.id AND pt.applicableDuring = 1 ) SELECT i.id AS productId , i.name AS productName @@ -168,7 +168,7 @@ public Product Update(int key, Product item) var cmd = connection.CreateCommand(); cmd.CommandText = @" UPDATE Price SET price = @price WHERE TYPE IN - ( SELECT id FROM PricingType WHERE + ( SELECT id FROM PriceList WHERE applicableDuring = 1 AND requiresMembership <> 1 ) AND @@ -181,7 +181,7 @@ public Product Update(int key, Product item) cmd = connection.CreateCommand(); cmd.CommandText = @" UPDATE Price SET price = @price WHERE TYPE IN - ( SELECT id FROM PricingType WHERE + ( SELECT id FROM PriceList WHERE applicableDuring = 1 AND requiresMembership = 1 ) AND diff --git a/External/Data/MariaDb/Migrations/v1_03_00/RenameColumn_Client_revoked_allowed.cs b/External/Data/MariaDb/Migrations/v1_03_00/RenameColumn_Client_revoked_allowed.cs new file mode 100644 index 0000000..ab8a9c7 --- /dev/null +++ b/External/Data/MariaDb/Migrations/v1_03_00/RenameColumn_Client_revoked_allowed.cs @@ -0,0 +1,19 @@ +using FluentMigrator; + +namespace GalliumPlus.Data.MariaDb.Migrations.v1_03_00; + +// ReSharper disable once InconsistentNaming +// ReSharper disable once UnusedType.Global +[Migration(1_03_00_001)] +public class RenameColumn_Client_revoked_allowed : Migration +{ + public override void Up() + { + this.Rename.Table("PricingType").To("PriceList"); + } + + public override void Down() + { + this.Rename.Table("PriceList").To("PricingType"); + } +} \ No newline at end of file diff --git a/WebService/Controllers/PriceListController.cs b/WebService/Controllers/PriceListController.cs new file mode 100644 index 0000000..bffe354 --- /dev/null +++ b/WebService/Controllers/PriceListController.cs @@ -0,0 +1,19 @@ +using GalliumPlus.Core.Security; +using GalliumPlus.WebService.Middleware.Authorization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace GalliumPlus.WebService.Controllers; + +[Route("v1/price-lists")] +[Authorize] +[ApiController] +public class PriceListController : GalliumController +{ + [HttpGet] + [RequiresPermissions(Permission.ManagePrices)] + public IActionResult Get() + { + return this.Json(null); + } +} \ No newline at end of file diff --git a/WebService/Services/PricingService.cs b/WebService/Services/PricingService.cs index f787ac3..fadcb15 100644 --- a/WebService/Services/PricingService.cs +++ b/WebService/Services/PricingService.cs @@ -5,16 +5,16 @@ namespace GalliumPlus.WebService.Services; [ScopedService] public class PricingService { - public IEnumerable GetActivePricingTypes() + public IEnumerable GetActivePricingTypes() { - return new PricingType[] + return new PriceList[] { new(90001, "Adhérent", "Tarif normal adhérent", true), new(90002, "Non-adhérent", "Tarif normal non-adhérent", false), }; } - public IEnumerable GetPricingTypes() + public IEnumerable GetPricingTypes() { return this.GetActivePricingTypes(); } From ae37c0a01a83cf76fef13233d18a899290edeaeb Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 10 May 2025 12:56:31 +0200 Subject: [PATCH 14/20] =?UTF-8?q?ci:=20s=C3=A9paration=20des=20tests=20et?= =?UTF-8?q?=20du=20d=C3=A9ploiement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploiement.yml | 34 ++++++++++++++++ .../workflows/verifications.yml | 40 +++---------------- Gallium+ API.sln | 3 +- 3 files changed, 41 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/deploiement.yml rename ".github/workflows/V\303\251rifications.yml" => .github/workflows/verifications.yml (57%) diff --git a/.github/workflows/deploiement.yml b/.github/workflows/deploiement.yml new file mode 100644 index 0000000..ee7acc3 --- /dev/null +++ b/.github/workflows/deploiement.yml @@ -0,0 +1,34 @@ +name: Déploiement + +on: + push: + tags: [ '**' ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build WebService --no-restore /warnaserror -c Release + - name: Setup Python + uses: actions/setup-python@v4.7.1 + with: + python-version: '3.11' + - name: Install Paramiko + run: python3 -m pip install paramiko + - name: Deploy build + env: + DEPLOY_HOSTNAME: ${{ secrets.DEPLOY_HOSTNAME }} + DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }} + DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }} + run: python3 .deployment/galliumplus-deploy \ No newline at end of file diff --git "a/.github/workflows/V\303\251rifications.yml" b/.github/workflows/verifications.yml similarity index 57% rename from ".github/workflows/V\303\251rifications.yml" rename to .github/workflows/verifications.yml index df84d81..ac90b2c 100644 --- "a/.github/workflows/V\303\251rifications.yml" +++ b/.github/workflows/verifications.yml @@ -1,45 +1,12 @@ name: Vérifications on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build WebService --no-restore /warnaserror -c Release - - name: Setup Python - uses: actions/setup-python@v4.7.1 - with: - python-version: '3.11' - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - - name: Install Paramiko - run: python3 -m pip install paramiko - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - - name: Deploy build - env: - DEPLOY_HOSTNAME: ${{ secrets.DEPLOY_HOSTNAME }} - DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }} - DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }} - run: python3 .deployment/galliumplus-deploy - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - unit-tests: runs-on: ubuntu-latest - if: github.event_name == 'pull_request' env: GALLIUM_ENV: test GALLIUM_HTTP: 5080 @@ -51,13 +18,16 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.0.x + dotnet-version: 8.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build WebService --no-restore /warnaserror -c Release - name: Run unit tests run: dotnet test CoreTests api-tests: runs-on: ubuntu-latest - if: github.event_name == 'pull_request' env: GALLIUM_ENV: test GALLIUM_HTTP: 5080 diff --git a/Gallium+ API.sln b/Gallium+ API.sln index 2988055..eeb3e83 100644 --- a/Gallium+ API.sln +++ b/Gallium+ API.sln @@ -13,8 +13,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore - .github\workflows\Vérifications.yml = .github\workflows\Vérifications.yml README.md = README.md + .github\workflows\deploiement.yml = .github\workflows\deploiement.yml + .github\workflows\verifications.yml = .github\workflows\verifications.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "External", "External\External.csproj", "{0091163D-F909-442C-9397-859D8E069322}" From 0202e71ae8a9c8ca76bc22b69c7d0d7d04cc4f7a Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 10 May 2025 14:19:13 +0200 Subject: [PATCH 15/20] Lecture des tarifs --- Core/Data/ICategoryDao.cs | 4 +- Core/Data/IPriceListDao.cs | 5 +++ Core/Security/Permissions.cs | 2 +- Core/{Items => Stocks}/PriceList.cs | 8 ++-- Fake/Data/Fake/BaseDao.cs | 37 +++++++++++-------- Fake/Data/Fake/BaseDaoWithAutoIncrement.cs | 13 +++++-- Fake/Data/Fake/CategoryDao.cs | 4 +- Fake/Data/Fake/ClientDao.cs | 6 ++- Fake/Data/Fake/PriceListDao.cs | 19 ++++++++++ Fake/Data/Fake/ProductDao.cs | 6 ++- Fake/Data/Fake/RoleDao.cs | 7 +++- Fake/Data/Fake/SessionDao.cs | 2 +- Fake/Data/Fake/UserDao.cs | 4 +- WebService/Controllers/PriceListController.cs | 5 ++- WebService/Program.cs | 3 +- WebService/Services/PricingService.cs | 18 +++------ 16 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 Core/Data/IPriceListDao.cs rename Core/{Items => Stocks}/PriceList.cs (88%) create mode 100644 Fake/Data/Fake/PriceListDao.cs diff --git a/Core/Data/ICategoryDao.cs b/Core/Data/ICategoryDao.cs index 2adaf21..5dcf925 100644 --- a/Core/Data/ICategoryDao.cs +++ b/Core/Data/ICategoryDao.cs @@ -2,6 +2,4 @@ namespace GalliumPlus.Core.Data; -public interface ICategoryDao : IBasicDao -{ -} \ No newline at end of file +public interface ICategoryDao : IBasicDao; \ No newline at end of file diff --git a/Core/Data/IPriceListDao.cs b/Core/Data/IPriceListDao.cs new file mode 100644 index 0000000..3b0e725 --- /dev/null +++ b/Core/Data/IPriceListDao.cs @@ -0,0 +1,5 @@ +using GalliumPlus.Core.Stocks; + +namespace GalliumPlus.Core.Data; + +public interface IPriceListDao : IBasicDao; \ No newline at end of file diff --git a/Core/Security/Permissions.cs b/Core/Security/Permissions.cs index 8d15541..5c8a976 100644 --- a/Core/Security/Permissions.cs +++ b/Core/Security/Permissions.cs @@ -102,7 +102,7 @@ public enum Permission : uint /// /// Toutes les permissions /// - All = 0x7FF + All = 0xFFF } public class Permissions : EnumBitflagSet diff --git a/Core/Items/PriceList.cs b/Core/Stocks/PriceList.cs similarity index 88% rename from Core/Items/PriceList.cs rename to Core/Stocks/PriceList.cs index 60ee16a..2f3a143 100644 --- a/Core/Items/PriceList.cs +++ b/Core/Stocks/PriceList.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using KiwiQuery.Mapped; -namespace GalliumPlus.Core.Items; +namespace GalliumPlus.Core.Stocks; /// /// Représente un tarif, comme le tarif adhérent ou le tarif non-adhérent. @@ -9,7 +9,7 @@ namespace GalliumPlus.Core.Items; public class PriceList { [PrimaryKey(AutoIncrement = true)] - private readonly long id; + private readonly int id; private readonly string shortName; private readonly string longName; private readonly bool requiresMembership; @@ -17,7 +17,7 @@ public class PriceList /// /// Le code du tarif. /// - public long Id => this.id; + public int Id => this.id; /// /// Le nom du tarif abrégé en un seul mot. @@ -45,7 +45,7 @@ public class PriceList /// Le nom abrégé du tarif. /// Le nom complet du tarif. /// Si le tarif est applicable uniquement aux adhérents. - public PriceList(long id, string shortName, string longName, bool requiresMembership) + public PriceList(int id, string shortName, string longName, bool requiresMembership) { this.id = id; this.shortName = shortName; diff --git a/Fake/Data/Fake/BaseDao.cs b/Fake/Data/Fake/BaseDao.cs index c04cf03..d1e7348 100644 --- a/Fake/Data/Fake/BaseDao.cs +++ b/Fake/Data/Fake/BaseDao.cs @@ -7,16 +7,16 @@ namespace GalliumPlus.Data.Fake public abstract class BaseDao : IBasicDao where TKey : notnull { - private Dictionary items; + private readonly Dictionary items; - public Dictionary Items => this.items; + protected Dictionary Items => this.items; - public BaseDao() + protected BaseDao() { this.items = new Dictionary(); } - virtual public TItem Create(TItem client) + public virtual TItem Create(TItem client) { lock (this.items) { @@ -36,7 +36,7 @@ virtual public TItem Create(TItem client) } } - virtual public void Delete(TKey key) + public virtual void Delete(TKey key) { lock (this.items) { @@ -47,12 +47,12 @@ virtual public void Delete(TKey key) } } - virtual public IEnumerable Read() + public virtual IEnumerable Read() { return this.items.Values; } - virtual public TItem Read(TKey key) + public virtual TItem Read(TKey key) { try { @@ -64,7 +64,7 @@ virtual public TItem Read(TKey key) } } - virtual public TItem Update(TKey key, TItem item) + public virtual TItem Update(TKey key, TItem item) { lock (this.items) { @@ -73,11 +73,16 @@ virtual public TItem Update(TKey key, TItem item) throw new InvalidResourceException("Custom constraints violated"); } - if (!this.items.ContainsKey(key)) throw new ItemNotFoundException(); - - this.SetKey(ref item, key); - AssignObject(this.items[key], item); - return item; + if (this.items.TryGetValue(key, out TItem? value)) + { + item = this.SetKey(item, key); + AssignObject(value, item); + return item; + } + else + { + throw new ItemNotFoundException(); + } } } @@ -95,10 +100,10 @@ private static void AssignObject(T destination, T source) } } - abstract protected TKey GetKey(TItem item); + protected abstract TKey GetKey(TItem item); - protected abstract void SetKey(ref TItem item, TKey key); + protected abstract TItem SetKey(TItem item, TKey key); - virtual protected bool CheckConstraints(TItem item) => true; + protected virtual bool CheckConstraints(TItem item) => true; } } diff --git a/Fake/Data/Fake/BaseDaoWithAutoIncrement.cs b/Fake/Data/Fake/BaseDaoWithAutoIncrement.cs index dad9b5f..2b76306 100644 --- a/Fake/Data/Fake/BaseDaoWithAutoIncrement.cs +++ b/Fake/Data/Fake/BaseDaoWithAutoIncrement.cs @@ -4,11 +4,18 @@ public abstract class BaseDaoWithAutoIncrement : BaseDao { private int nextInsertKey = 1; - public override TItem Create(TItem client) + public override TItem Create(TItem item) { - this.SetKey(ref client, this.nextInsertKey); + item = this.SetKey(item, this.nextInsertKey); this.nextInsertKey++; - return base.Create(client); + return base.Create(item); + } + + public TItem Create(Func factory) + { + TItem item = factory.Invoke(this.nextInsertKey); + this.nextInsertKey++; + return base.Create(item); } } } diff --git a/Fake/Data/Fake/CategoryDao.cs b/Fake/Data/Fake/CategoryDao.cs index 506b04e..7547558 100644 --- a/Fake/Data/Fake/CategoryDao.cs +++ b/Fake/Data/Fake/CategoryDao.cs @@ -8,14 +8,12 @@ public class CategoryDao : BaseDaoWithAutoIncrement, ICategoryDao public CategoryDao() { this.Create(new Category(0, "Boissons")); - this.Create(new Category(0, "Snacks")); - this.Create(new Category(0, "Pablo")); } protected override int GetKey(Category item) => item.Id; - protected override void SetKey(ref Category item, int key) => item = item.WithId(key); + protected override Category SetKey(Category item, int key) => item.WithId(key); } } diff --git a/Fake/Data/Fake/ClientDao.cs b/Fake/Data/Fake/ClientDao.cs index f7adb1b..0eecfb7 100644 --- a/Fake/Data/Fake/ClientDao.cs +++ b/Fake/Data/Fake/ClientDao.cs @@ -206,7 +206,11 @@ public void DeleteSameSignOn(int clientId) protected override int GetKey(Client item) => item.Id; - protected override void SetKey(ref Client item, int key) => item.Id = key; + protected override Client SetKey(Client item, int key) + { + item.Id = key; + return item; + } protected override bool CheckConstraints(Client item) { diff --git a/Fake/Data/Fake/PriceListDao.cs b/Fake/Data/Fake/PriceListDao.cs new file mode 100644 index 0000000..9a8465c --- /dev/null +++ b/Fake/Data/Fake/PriceListDao.cs @@ -0,0 +1,19 @@ +using GalliumPlus.Core.Data; +using GalliumPlus.Core.Stocks; + +namespace GalliumPlus.Data.Fake +{ + public class PriceListDao : BaseDaoWithAutoIncrement, IPriceListDao + { + public PriceListDao() + { + this.Create(id => new PriceList(id, "Adhérent", "Tarif normal adhérent", true)); + this.Create(id => new PriceList(id, "Non-adhérent", "Tarif normal non-adhérent", false)); + } + + protected override int GetKey(PriceList item) => item.Id; + + protected override PriceList SetKey(PriceList item, int key) => + new(key, item.ShortName, item.LongName, item.RequiresMembership); + } +} \ No newline at end of file diff --git a/Fake/Data/Fake/ProductDao.cs b/Fake/Data/Fake/ProductDao.cs index 949f657..1349cb3 100644 --- a/Fake/Data/Fake/ProductDao.cs +++ b/Fake/Data/Fake/ProductDao.cs @@ -46,7 +46,11 @@ public ProductDao(ICategoryDao categoryDao) protected override int GetKey(Product item) => item.Id; - protected override void SetKey(ref Product item, int key) => item.Id = key; + protected override Product SetKey(Product item, int key) + { + item.Id = key; + return item; + } public override void Delete(int key) { diff --git a/Fake/Data/Fake/RoleDao.cs b/Fake/Data/Fake/RoleDao.cs index 678c2ac..1411ab3 100644 --- a/Fake/Data/Fake/RoleDao.cs +++ b/Fake/Data/Fake/RoleDao.cs @@ -27,6 +27,11 @@ public RoleDao() } protected override int GetKey(Role item) => item.Id; - protected override void SetKey(ref Role item, int key) => item.Id = key; + + protected override Role SetKey(Role item, int key) + { + item.Id = key; + return item; + } } } diff --git a/Fake/Data/Fake/SessionDao.cs b/Fake/Data/Fake/SessionDao.cs index cbefdc3..863511d 100644 --- a/Fake/Data/Fake/SessionDao.cs +++ b/Fake/Data/Fake/SessionDao.cs @@ -73,7 +73,7 @@ public void DeleteByClientId(int clientId) protected override string GetKey(Session item) => item.Token; - protected override void SetKey(ref Session item, string key) + protected override Session SetKey(Session item, string key) { throw new InvalidOperationException("A session token is read-only"); } diff --git a/Fake/Data/Fake/UserDao.cs b/Fake/Data/Fake/UserDao.cs index bb7199e..4b66173 100644 --- a/Fake/Data/Fake/UserDao.cs +++ b/Fake/Data/Fake/UserDao.cs @@ -27,7 +27,7 @@ public PrtDao() protected override string GetKey(PasswordResetToken item) => item.Token; - protected override void SetKey(ref PasswordResetToken item, string key) => throw new NotImplementedException(); // tkt mon pote + protected override PasswordResetToken SetKey(PasswordResetToken item, string key) => throw new NotImplementedException(); // tkt mon pote } private PrtDao prtDao; @@ -112,7 +112,7 @@ public void UpdateDeposit(string id, decimal? money) protected override string GetKey(User item) => item.Id; - protected override void SetKey(ref User item, string key) + protected override User SetKey(User item, string key) { throw new InvalidOperationException("Can't set the user ID automatically"); } diff --git a/WebService/Controllers/PriceListController.cs b/WebService/Controllers/PriceListController.cs index bffe354..a6b6fee 100644 --- a/WebService/Controllers/PriceListController.cs +++ b/WebService/Controllers/PriceListController.cs @@ -1,5 +1,6 @@ using GalliumPlus.Core.Security; using GalliumPlus.WebService.Middleware.Authorization; +using GalliumPlus.WebService.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -8,12 +9,12 @@ namespace GalliumPlus.WebService.Controllers; [Route("v1/price-lists")] [Authorize] [ApiController] -public class PriceListController : GalliumController +public class PriceListController(PricingService pricingService) : GalliumController { [HttpGet] [RequiresPermissions(Permission.ManagePrices)] public IActionResult Get() { - return this.Json(null); + return this.Json(pricingService.GetActivePriceLists()); } } \ No newline at end of file diff --git a/WebService/Program.cs b/WebService/Program.cs index 11d47dd..9497f00 100644 --- a/WebService/Program.cs +++ b/WebService/Program.cs @@ -70,6 +70,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -78,7 +79,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/WebService/Services/PricingService.cs b/WebService/Services/PricingService.cs index fadcb15..cc09d8a 100644 --- a/WebService/Services/PricingService.cs +++ b/WebService/Services/PricingService.cs @@ -1,21 +1,13 @@ -using GalliumPlus.Core.Items; +using GalliumPlus.Core.Data; +using GalliumPlus.Core.Stocks; namespace GalliumPlus.WebService.Services; [ScopedService] -public class PricingService +public class PricingService(IPriceListDao priceListDao) { - public IEnumerable GetActivePricingTypes() + public IEnumerable GetActivePriceLists() { - return new PriceList[] - { - new(90001, "Adhérent", "Tarif normal adhérent", true), - new(90002, "Non-adhérent", "Tarif normal non-adhérent", false), - }; - } - - public IEnumerable GetPricingTypes() - { - return this.GetActivePricingTypes(); + return priceListDao.Read(); } } \ No newline at end of file From b9e93837864ddae90199514be7c61466c37e59a3 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 10 May 2025 14:31:34 +0200 Subject: [PATCH 16/20] fix: utiliser uint pour SameSignOnScope --- Core/Applications/SameSignOnScopes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Applications/SameSignOnScopes.cs b/Core/Applications/SameSignOnScopes.cs index 08784d8..58a3959 100644 --- a/Core/Applications/SameSignOnScopes.cs +++ b/Core/Applications/SameSignOnScopes.cs @@ -6,7 +6,7 @@ namespace GalliumPlus.Core.Applications; /// La portée de l'accès Same Sign-On. /// [Flags] -public enum SameSignOnScope +public enum SameSignOnScope : uint { /// /// Accès au strict minimum (identifiant utilisateur et identifiant immuable) From d42933db776adf92f7e4b25b1b4e4d0383d1a0a8 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 10 May 2025 20:57:02 +0200 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20API=20des=20tarifs=20termin=C3=A9?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tests/tests/audit_tests_helpers.py | 6 +- .tests/tests/pricing_tests.py | 165 ++++++------------ Core/Logs/Builders/AuditLogEntryBuilder.cs | 11 ++ Core/Logs/LoggedAction.cs | 6 +- Core/Stocks/PriceList.cs | 1 + Fake/Data/Fake/PriceListDao.cs | 4 +- WebService/Controllers/PriceListController.cs | 21 ++- WebService/Services/PricingService.cs | 10 ++ 8 files changed, 107 insertions(+), 117 deletions(-) diff --git a/.tests/tests/audit_tests_helpers.py b/.tests/tests/audit_tests_helpers.py index 586f7e5..727337f 100644 --- a/.tests/tests/audit_tests_helpers.py +++ b/.tests/tests/audit_tests_helpers.py @@ -22,9 +22,9 @@ "PriceAdded": 0x161, "PriceModified": 0x162, "PriceDeleted": 0x163, - "PricingTypeAdded": 0x171, - "PricingTypeModified": 0x172, - "PricingTypeDeleted": 0x173, + "PriceListAdded": 0x171, + "PriceListModified": 0x172, + "PriceListDeleted": 0x173, "RoleAdded": 0x181, "RoleModified": 0x182, "RoleDeleted": 0x183, diff --git a/.tests/tests/pricing_tests.py b/.tests/tests/pricing_tests.py index 374c382..6469804 100644 --- a/.tests/tests/pricing_tests.py +++ b/.tests/tests/pricing_tests.py @@ -2,62 +2,12 @@ from utils.auth import BearerAuth from .audit_tests_helpers import AuditTestHelpers -INVALID_PRICE_LISTS = [ - { - "shortName": "EtiSmash", - "requiresMembership": False, - }, - { - "longName": "Tarif spécial EtiSmash", - "requiresMembership": True, - }, - { - "longName": "Tarif spécial EtiSmash", - "shortName": "EtiSmash", - }, - { - "longName": "", - "shortName": "EtiSmash", - "requiresMembership": True, - }, - { - "longName": " ", - "shortName": "EtiSmash", - "requiresMembership": False, - }, - { - "longName": "Tarif spécial EtiSmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaash", - "shortName": "EtiSmash", - "requiresMembership": False, - }, - { - "longName": "Tarif spécial EtiSmash", - "shortName": "", - "requiresMembership": True, - }, - { - "longName": "Tarif spécial EtiSmash", - "shortName": " ", - "requiresMembership": False, - }, - { - "longName": "Tarif spécial EtiSmash", - "shortName": "EtiSmaaaaaaaaaash", - "requiresMembership": True, - }, - { - "longName": "Tarif spécial EtiSmash", - "shortName": "EtiSmash", - "requiresMembership": 1, - }, -] - class PricingTests(TestBase): def setUp(self): super().setUp() self.set_authentication(BearerAuth("09876543210987654321")) - self.audit = AuditTestHelpers(self) + self.audit = AuditTestHelpers(self, 1, 3) def tearDown(self): self.unset_authentication() @@ -96,44 +46,47 @@ def test_price_list_get_one(self): response = self.get(f"price-lists/{invalid_id}") self.expect(response.status_code).to.be.equal_to(404) - def test_price_list_create(self): - previous_price_list_count = len(self.get("price-lists").json()) + # A faire quand on gèrera les évènements - valid_price_list = { - "longName": "Tarif spécial EtiSmash", - "shortName": "EtiSmash", - "requiresMembership": False, - } + # def test_price_list_create(self): + # previous_price_list_count = len(self.get("price-lists").json()) - with self.audit.watch(): - response = self.post("price-lists", valid_price_list) - self.expect(response.status_code).to.be.equal_to(201) - location = self.expect(response.headers).to.have.an_item("Location").value + # valid_price_list = { + # "longName": "Tarif spécial EtiSmash", + # "shortName": "EtiSmash", + # "requiresMembership": False, + # # ajouter le champ "applicableDuring" ici ? + # } - created_price_list = response.json() - self.expect(created_price_list).to.have.an_item("id") - self.expect(created_price_list["name"]).to.be.equal_to("Jus") + # with self.audit.watch(): + # response = self.post("price-lists", valid_price_list) + # self.expect(response.status_code).to.be.equal_to(201) + # location = self.expect(response.headers).to.have.an_item("Location").value - response = self.get(location) - self.expect(response.status_code).to.be.equal_to(200) - created_price_list = response.json() - self.expect(created_price_list["name"]).to.be.equal_to("Jus") + # created_price_list = response.json() + # self.expect(created_price_list).to.have.an_item("id") + # self.expect(created_price_list["name"]).to.be.equal_to("Jus") - new_price_list_count = len(self.get("price-lists").json()) - self.expect(new_price_list_count).to.be.equal_to(previous_price_list_count + 1) + # response = self.get(location) + # self.expect(response.status_code).to.be.equal_to(200) + # created_price_list = response.json() + # self.expect(created_price_list["name"]).to.be.equal_to("Jus") - self.audit.expect_entries(self.audit.price_list_added_action("Jus", "eb069420")) + # new_price_list_count = len(self.get("price-lists").json()) + # self.expect(new_price_list_count).to.be.equal_to(previous_price_list_count + 1) - for invalid_price_list in INVALID_PRICE_LISTS: - response = self.post("price-lists", invalid_price_list) - self.expect(response.status_code).to.be.equal_to(400) - self.expect(response.json()).to.have.an_item("code").that._is.equal_to( - "InvalidResource" - ) + # self.audit.expect_entries(self.audit.price_list_added_action("Jus", "eb069420")) + + # for invalid_price_list in INVALID_PRICE_LISTS: + # response = self.post("price-lists", invalid_price_list) + # self.expect(response.status_code).to.be.equal_to(400) + # self.expect(response.json()).to.have.an_item("code").that._is.equal_to( + # "InvalidResource" + # ) def test_price_list_edit(self): valid_price_list = self.get("price-lists").json()[-1] - valid_price_list.update(Name="Jus") + valid_price_list.update(shortName="TEST") price_list_id = valid_price_list["id"] with self.audit.watch(): @@ -141,13 +94,15 @@ def test_price_list_edit(self): self.expect(response.status_code).to.be.equal_to(200) edited_price_list = self.get(f"price-lists/{price_list_id}").json() - self.expect(edited_price_list["name"]).to.be.equal_to("Jus") + self.expect(edited_price_list["shortName"]).to.be.equal_to("TEST") self.audit.expect_entries( - self.audit.price_list_modified_action("Jus", "eb069420") + self.audit.entry( + "PriceListModified", id=price_list_id, name="Tarif test adhérent" + ) ) - # price_list qui n'existe pas + # Tarif qui n'existe pas response = self.put("price-lists/12345", valid_price_list) self.expect(response.status_code).to.be.equal_to(404) @@ -163,61 +118,55 @@ def test_price_list_edit(self): # Informations non valides - invalid_price_list = {"name": ""} + invalid_price_list = {"shortName": "long comme un lundi"} response = self.put(f"price-lists/{price_list_id}", invalid_price_list) self.expect(response.status_code).to.be.equal_to(400) self.expect(response.json()).to.have.an_item("code").that._is.equal_to( "InvalidResource" ) - def test_price_list_delete(self): - price_list = {"name": "Jus"} - location = self.post("price-lists", price_list).headers["Location"] + # Pareil que pour test_price_list_create - # On supprimme la catégorie + # def test_price_list_delete(self): + # price_list = {"name": "Jus"} + # location = self.post("price-lists", price_list).headers["Location"] - with self.audit.watch(): - response = self.delete(location) - self.expect(response.status_code).to.be.equal_to(200) + # # On supprimme la catégorie - self.audit.expect_entries( - self.audit.price_list_deleted_action("Jus", "eb069420") - ) + # with self.audit.watch(): + # response = self.delete(location) + # self.expect(response.status_code).to.be.equal_to(200) - # La catégorie n'existe plus + # self.audit.expect_entries( + # self.audit.price_list_deleted_action("Jus", "eb069420") + # ) - response = self.get(location) - self.expect(response.status_code).to.be.equal_to(404) + # # La catégorie n'existe plus - # On ne peut plus le supprimer + # response = self.get(location) + # self.expect(response.status_code).to.be.equal_to(404) - response = self.delete(location) - self.expect(response.status_code).to.be.equal_to(404) + # # On ne peut plus le supprimer + + # response = self.delete(location) + # self.expect(response.status_code).to.be.equal_to(404) def test_price_list_no_authentification(self): self.unset_authentication() response = self.get("price-lists") self.expect(response.status_code).to.be.equal_to(401) - response = self.post("price-lists", {}) - self.expect(response.status_code).to.be.equal_to(401) response = self.get("price-lists/1") self.expect(response.status_code).to.be.equal_to(401) response = self.put("price-lists/1", {}) self.expect(response.status_code).to.be.equal_to(401) - response = self.delete("price-lists/1") - self.expect(response.status_code).to.be.equal_to(401) def test_price_list_no_permission(self): self.set_authentication(BearerAuth("12345678901234567890")) response = self.get("price-lists") self.expect(response.status_code).to.be.equal_to(403) - response = self.post("price-lists", {"name": "/"}) - self.expect(response.status_code).to.be.equal_to(403) response = self.get("price-lists/1") self.expect(response.status_code).to.be.equal_to(403) - response = self.put("price-lists/1", {"name": "/"}) - self.expect(response.status_code).to.be.equal_to(403) - response = self.delete("price-lists/1") + response = self.put("price-lists/1", {}) self.expect(response.status_code).to.be.equal_to(403) diff --git a/Core/Logs/Builders/AuditLogEntryBuilder.cs b/Core/Logs/Builders/AuditLogEntryBuilder.cs index a9473a7..d304a55 100644 --- a/Core/Logs/Builders/AuditLogEntryBuilder.cs +++ b/Core/Logs/Builders/AuditLogEntryBuilder.cs @@ -46,6 +46,17 @@ public IGenericEntryBuilder Category(Category category) ); public IClientEntryBuilder Client(Client client) => new ClientEntryBuilder(client, this); + + + public IGenericEntryBuilder PriceList(PriceList priceList) + => new GenericEntryBuilder( + this, + LoggedAction.PriceListAdded, LoggedAction.PriceListModified, LoggedAction.PriceListDeleted, + root => { + root.details.Add("id", priceList.Id); + root.details.Add("name", priceList.LongName); + } + ); public IUserEntryBuilder User(User user) => new UserEntryBuilder(user, this); } \ No newline at end of file diff --git a/Core/Logs/LoggedAction.cs b/Core/Logs/LoggedAction.cs index 295e1c1..4e53d78 100644 --- a/Core/Logs/LoggedAction.cs +++ b/Core/Logs/LoggedAction.cs @@ -33,9 +33,9 @@ public enum LoggedAction : uint // PriceModified = 0x162, // PriceDeleted = 0x163, - // PricingTypeAdded = 0x171, - // PricingTypeModified = 0x172, - // PricingTypeDeleted = 0x173, + PriceListAdded = 0x171, + PriceListModified = 0x172, + PriceListDeleted = 0x173, // RoleAdded = 0x181, // RoleModified = 0x182, diff --git a/Core/Stocks/PriceList.cs b/Core/Stocks/PriceList.cs index 2f3a143..6a7c17e 100644 --- a/Core/Stocks/PriceList.cs +++ b/Core/Stocks/PriceList.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using KiwiQuery.Mapped; namespace GalliumPlus.Core.Stocks; diff --git a/Fake/Data/Fake/PriceListDao.cs b/Fake/Data/Fake/PriceListDao.cs index 9a8465c..85c5f71 100644 --- a/Fake/Data/Fake/PriceListDao.cs +++ b/Fake/Data/Fake/PriceListDao.cs @@ -7,8 +7,8 @@ public class PriceListDao : BaseDaoWithAutoIncrement, IPriceListDao { public PriceListDao() { - this.Create(id => new PriceList(id, "Adhérent", "Tarif normal adhérent", true)); - this.Create(id => new PriceList(id, "Non-adhérent", "Tarif normal non-adhérent", false)); + this.Create(id => new PriceList(id, "NON ADHÉRENT", "Tarif test non-adhérent", false)); + this.Create(id => new PriceList(id, "ADHÉRENT", "Tarif test adhérent", true)); } protected override int GetKey(PriceList item) => item.Id; diff --git a/WebService/Controllers/PriceListController.cs b/WebService/Controllers/PriceListController.cs index a6b6fee..aaa62de 100644 --- a/WebService/Controllers/PriceListController.cs +++ b/WebService/Controllers/PriceListController.cs @@ -1,4 +1,5 @@ using GalliumPlus.Core.Security; +using GalliumPlus.Core.Stocks; using GalliumPlus.WebService.Middleware.Authorization; using GalliumPlus.WebService.Services; using Microsoft.AspNetCore.Authorization; @@ -9,7 +10,7 @@ namespace GalliumPlus.WebService.Controllers; [Route("v1/price-lists")] [Authorize] [ApiController] -public class PriceListController(PricingService pricingService) : GalliumController +public class PriceListController(PricingService pricingService, AuditService auditService) : GalliumController { [HttpGet] [RequiresPermissions(Permission.ManagePrices)] @@ -17,4 +18,22 @@ public IActionResult Get() { return this.Json(pricingService.GetActivePriceLists()); } + + [HttpGet("{id:int}", Name = "PriceList")] + [RequiresPermissions(Permission.ManagePrices)] + public IActionResult Get(int id) + { + return this.Json(pricingService.GetPriceList(id)); + } + + [HttpPut("{id:int}")] + [RequiresPermissions(Permission.ManagePrices)] + public IActionResult Put(int id, PriceList priceList) + { + PriceList updatedPriceList = pricingService.UpdatePriceList(id, priceList); + auditService.AddEntry( + entry => entry.PriceList(updatedPriceList).Modified().By(this.Client!, this.User) + ); + return this.Json(updatedPriceList); + } } \ No newline at end of file diff --git a/WebService/Services/PricingService.cs b/WebService/Services/PricingService.cs index cc09d8a..c51e838 100644 --- a/WebService/Services/PricingService.cs +++ b/WebService/Services/PricingService.cs @@ -10,4 +10,14 @@ public IEnumerable GetActivePriceLists() { return priceListDao.Read(); } + + public PriceList GetPriceList(int id) + { + return priceListDao.Read(id); + } + + public PriceList UpdatePriceList(int id, PriceList modifiedPriceList) + { + return priceListDao.Update(id, modifiedPriceList); + } } \ No newline at end of file From 455f2d9f9437f559af5e6768d57fd215446f5efc Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sun, 11 May 2025 00:09:12 +0200 Subject: [PATCH 18/20] fix: renommer les members de `Availability` --- Core/Stocks/Availability.cs | 10 +++++----- Core/Stocks/Product.cs | 6 +++--- Fake/Data/Fake/ProductDao.cs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Core/Stocks/Availability.cs b/Core/Stocks/Availability.cs index 8467c2c..7e21cdf 100644 --- a/Core/Stocks/Availability.cs +++ b/Core/Stocks/Availability.cs @@ -1,22 +1,22 @@ namespace GalliumPlus.Core.Stocks; /// -/// Disponiblité d'un produit. +/// La disponibilité d'un produit. /// public enum Availability { /// - /// Le produit est disponible si il en reste en stock. + /// Le produit est disponible s'il en reste en stock. /// - AUTO = 0, + Auto = 0, /// /// Le produit est toujours considéré comme disponible. /// - ALWAYS = 1, + Always = 1, /// /// Le produit est toujours considéré comme indisponible. /// - NEVER = 2, + Never = 2, } \ No newline at end of file diff --git a/Core/Stocks/Product.cs b/Core/Stocks/Product.cs index afef53b..f04a2c1 100644 --- a/Core/Stocks/Product.cs +++ b/Core/Stocks/Product.cs @@ -53,9 +53,9 @@ public class Product /// public bool Available => this.Availability switch { - Availability.ALWAYS => true, - Availability.AUTO => this.Stock > 0, - Availability.NEVER => false, + Availability.Always => true, + Availability.Auto => this.Stock > 0, + Availability.Never => false, // ignore les états invalides _ => false, diff --git a/Fake/Data/Fake/ProductDao.cs b/Fake/Data/Fake/ProductDao.cs index 1349cb3..eaf4fc6 100644 --- a/Fake/Data/Fake/ProductDao.cs +++ b/Fake/Data/Fake/ProductDao.cs @@ -27,19 +27,19 @@ public ProductDao(ICategoryDao categoryDao) createdId = this.Create(new Product( 0, "Coca Cherry", 27, 1.00m, 0.80m, - Availability.AUTO, categoryDao.Read(1) + Availability.Auto, categoryDao.Read(1) )).Id; this.images.Add(createdId, defaultImage); createdId = this.Create(new Product( 0, "KitKat", 14, 0.80m, 0.60m, - Availability.AUTO, categoryDao.Read(2) + Availability.Auto, categoryDao.Read(2) )).Id; this.images.Add(createdId, defaultImage); createdId = this.Create(new Product( 0, "Pablo", 1, 999.99m, 500.00m, - Availability.ALWAYS, categoryDao.Read(3) + Availability.Always, categoryDao.Read(3) )).Id; this.images.Add(createdId, defaultImage); } From 34e02007aba72d8f244e609dbfe4c5159720f417 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sun, 11 May 2025 11:53:49 +0200 Subject: [PATCH 19/20] =?UTF-8?q?fix:=20M=C3=A0J=20des=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tests/tests/access_tests.py | 4 ++-- Core/Security/Permissions.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.tests/tests/access_tests.py b/.tests/tests/access_tests.py index a838e8b..2f7c706 100644 --- a/.tests/tests/access_tests.py +++ b/.tests/tests/access_tests.py @@ -93,7 +93,7 @@ def test_login_permissions_normal(self): self.expect(president_session) .to.have.an_item("permissions") .of.type(int) - .that._is.equal_to(2047) # president, full permissions + .that._is.equal_to(4095) # president, full permissions ) member_token = self.expect(member_session).to.have.an_item("token").value @@ -217,7 +217,7 @@ def test_login_permissions_minimum(self): self.expect(president_session) .to.have.an_item("permissions") .of.type(int) - .that._is.equal_to(2047) # president, full permissions + .that._is.equal_to(4095) # president, full permissions ) member_token = self.expect(member_session).to.have.an_item("token").value diff --git a/Core/Security/Permissions.cs b/Core/Security/Permissions.cs index 5c8a976..efb58fc 100644 --- a/Core/Security/Permissions.cs +++ b/Core/Security/Permissions.cs @@ -128,6 +128,8 @@ private Permissions() this.Flag(Permission.ManageClients); + this.Flag(Permission.UseDeveloperTools); + this.Flag(Permission.ForceDepositModification); } } \ No newline at end of file From 6365015b295a4d86304bcb68b58e4cd9ee758be6 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sun, 11 May 2025 12:29:11 +0200 Subject: [PATCH 20/20] =?UTF-8?q?fix:=20impl=C3=A9mentation=20du=20dao=20p?= =?UTF-8?q?our=20mariadb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MariaDb/Implementations/PriceListDao.cs | 62 +++++++++++++++++++ Gallium+ API.sln | 1 + SQL/galliumplus-test-dataset.sql | 18 +++--- WebService/Program.cs | 1 + 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 External/Data/MariaDb/Implementations/PriceListDao.cs diff --git a/External/Data/MariaDb/Implementations/PriceListDao.cs b/External/Data/MariaDb/Implementations/PriceListDao.cs new file mode 100644 index 0000000..fd3e160 --- /dev/null +++ b/External/Data/MariaDb/Implementations/PriceListDao.cs @@ -0,0 +1,62 @@ +using GalliumPlus.Core.Data; +using GalliumPlus.Core.Exceptions; +using GalliumPlus.Core.Stocks; +using KiwiQuery; +using KiwiQuery.Mapped; +using MySqlConnector; + +namespace GalliumPlus.Data.MariaDb.Implementations; + +public class PriceListDao : Dao, IPriceListDao +{ + public PriceListDao(DatabaseConnector connector) : base(connector) + { + } + + public PriceList Create(PriceList client) + { + throw new NotImplementedException(); + } + + public void Delete(int key) + { + throw new NotImplementedException(); + } + + public IEnumerable Read() + { + using MySqlConnection connection = this.Connect(); + Schema db = new(connection); + + return db.Select().Where(db.Column("applicableDuring") == 1).FetchList(); + } + + public PriceList Read(int key) + { + using MySqlConnection connection = this.Connect(); + Schema db = new(connection); + + var found = db.Select().WhereAll(db.Column("id") == key, db.Column("applicableDuring") == 1).FetchList(); + + if (found.Count == 1) + { + return found[0]; + } + else + { + throw new ItemNotFoundException(); + } + } + + public PriceList Update(int key, PriceList item) + { + using var connection = this.Connect(); + Schema db = new(connection); + + bool ok = db.Update().SetInserted(item).Where(db.Column("id") == key).Apply(); + + if (!ok) throw new ItemNotFoundException("Cette catégorie"); + + return new PriceList(key, item.ShortName, item.LongName, item.RequiresMembership); + } +} \ No newline at end of file diff --git a/Gallium+ API.sln b/Gallium+ API.sln index eeb3e83..83a3381 100644 --- a/Gallium+ API.sln +++ b/Gallium+ API.sln @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md .github\workflows\deploiement.yml = .github\workflows\deploiement.yml .github\workflows\verifications.yml = .github\workflows\verifications.yml + SQL\galliumplus-test-dataset.sql = SQL\galliumplus-test-dataset.sql EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "External", "External\External.csproj", "{0091163D-F909-442C-9397-859D8E069322}" diff --git a/SQL/galliumplus-test-dataset.sql b/SQL/galliumplus-test-dataset.sql index 087d784..a16d5a9 100644 --- a/SQL/galliumplus-test-dataset.sql +++ b/SQL/galliumplus-test-dataset.sql @@ -1,6 +1,6 @@ DELETE FROM `Price`; -DELETE FROM `PricingType`; +DELETE FROM `PriceList`; DELETE FROM `Product`; DELETE FROM `Item`; DELETE FROM `Category`; @@ -15,7 +15,7 @@ INSERT INTO `Item` VALUES (2, 'KitKat', 0, 0, 14, 2, NULL, NULL, 0), (3, 'Pablo', 0, 1, 1, 3, NULL, NULL, 0); -INSERT INTO `PricingType` VALUES +INSERT INTO `PriceList` VALUES (1, 'NON ADHÉRENT', 'Tarif test non-adhérent', 0, 1), (2, 'ADHÉRENT', 'Tarif test adhérent', 1, 1); @@ -37,14 +37,14 @@ DELETE FROM `Session`; delete from `PasswordResetToken`; INSERT INTO `Client` VALUES - (1, 'test-api-key-normal', 'Tests (normal)', 0, 2047, 1, 0), + (1, 'test-api-key-normal', 'Tests (normal)', 0, 4095, 1, 0), (2, 'test-api-key-restric', 'Tests (restricted)', 0, 137, 1, 0), - (3, 'test-api-key-minimum', 'Tests (minimum)', 1, 2047, 1, 0), - (4, 'test-api-key-macompf', 'Tests (Modif acompte forcée)', 1073741824, 1073743871, 1, 0), + (3, 'test-api-key-minimum', 'Tests (minimum)', 1, 4095, 1, 0), + (4, 'test-api-key-macompf', 'Tests (Modif acompte forcée)', 1073741824, 1073745919, 1, 0), (5, 'test-api-key-bot', 'Tests (bot)', 1, 1, 1, 0), - (6, 'test-api-key-sso-dir', 'Tests (SSO, direct)', 0, 2047, 1, 0), - (7, 'test-api-key-sso-ext', 'Tests (SSO, externe)', 0, 2047, 1, 0), - (8, 'test-api-key-sso-bot', 'Tests (SSO, applicatif)', 0, 2047, 1, 0); + (6, 'test-api-key-sso-dir', 'Tests (SSO, direct)', 0, 4095, 1, 0), + (7, 'test-api-key-sso-ext', 'Tests (SSO, externe)', 0, 4095, 1, 0), + (8, 'test-api-key-sso-bot', 'Tests (SSO, applicatif)', 0, 4095, 1, 0); INSERT INTO `AppAccess` VALUES (5, 0x6ff1904d29b818007ccbf05954bc1cd50f70148e41265cb823d54e2e3312b095, 'sel'), @@ -58,7 +58,7 @@ INSERT INTO `SameSignOn` VALUES INSERT INTO `Role` VALUES (1, 'Adhérent', 0), (2, 'CA', 27), - (3, 'Président', 2047); + (3, 'Président', 4095); INSERT INTO `User` VALUES (1, 'lomens', 'Nicolas', 'RESIN', 'nicolas.resin@iut-dijon.u-bourgogne.fr', 1, 'PROF', 20.00, 1, 0x6ff1904d29b818007ccbf05954bc1cd50f70148e41265cb823d54e2e3312b095, 'sel', NOW(), 0, 1, '2099-12-31'), diff --git a/WebService/Program.cs b/WebService/Program.cs index 9497f00..de0d102 100644 --- a/WebService/Program.cs +++ b/WebService/Program.cs @@ -80,6 +80,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped();