From f33b65154cfe05be9ec9b91652909020566c100e Mon Sep 17 00:00:00 2001 From: Nicolas Guilloux Date: Tue, 27 Jul 2021 18:18:57 +0200 Subject: [PATCH 1/5] Add documentation --- README.adoc | 9 ++++ docs/Events.adoc | 16 +++++++ docs/Security.adoc | 108 ++++++++++++++++++++++++++++++++++++++++++++ docs/Signatory.adoc | 8 ++-- 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 docs/Events.adoc create mode 100644 docs/Security.adoc diff --git a/README.adoc b/README.adoc index d7141db..fe0fe74 100644 --- a/README.adoc +++ b/README.adoc @@ -34,6 +34,11 @@ Quick showcase <<1. Installation>> + <<2. Documentation>> + - link:docs/Configuration.adoc[Configuration] + + - link:docs/Creation.adoc[Creation] + + - link:docs/Signatory.adoc[Signatory] + + - link:docs/Events.adoc[Events] + + - link:docs/BackOffice.adoc[BackOffice] + + - link:docs/Events.adoc[Events] + <<3. Versioning>> + <<4. Contributing>> + <<5. License>> + @@ -54,6 +59,10 @@ composer require rich-id/terms-module-bundle * link:docs/Configuration.adoc[Configuration] * link:docs/Creation.adoc[Creation] +* link:docs/Signatory.adoc[Signatory] +* link:docs/Events.adoc[Events] +* link:docs/BackOffice.adoc[BackOffice] +* link:docs/Events.adoc[Events] == 3. Versioning diff --git a/docs/Events.adoc b/docs/Events.adoc new file mode 100644 index 0000000..eb86b6f --- /dev/null +++ b/docs/Events.adoc @@ -0,0 +1,16 @@ +== Events + +Various events are available to let you react to every action. Every event is prefixed with the namespace `RichId\TermsModuleBundle\Domain\Event\`. + +[cols="1,1"] +|=== +| Event | Description + +| TermsPublishedEvent | Triggered on publication +| TermsSignedEvent | Triggered when a version is signed/not signed/skipped by a subject +| TermsUnpublishedEvent | Triggered on un-publication +| TermsVersionCreatedEvent | Triggered when a new version is created +| TermsVersionDeletedEvent | Triggered when a draft version is deleted +| TermsVersionEnabledEvent | Triggered when a version is published +| TermsVersionUpdatedEvent | Triggered when a version is updated +|=== diff --git a/docs/Security.adoc b/docs/Security.adoc new file mode 100644 index 0000000..0dc6f29 --- /dev/null +++ b/docs/Security.adoc @@ -0,0 +1,108 @@ +== Security + +=== Guards + +You may want to control who can sign the terms. This can be easily done using the `TermsGuardInterface` that will previously check if the subject can sign it. + +The following example protect the terms with the slug `your_slug` to be signed by anybody but a User. + +[source,php] +---- +final class RandomGuard implements TermsGuardInterface +{ + public function supports(string $slug, TermsSubjectInterface $subject): bool + { + return $slug === 'your_slug'; + } + + public function check(string $slug, TermsSubjectInterface $subject): bool + { + return $subject instanceof User; + } +} +---- + + +=== Protecting a route + +Since it can be hard to create a generic voter to check if a subject has sign the terms, the best way is to create a custom voter for each case. + +The following example exposes a voter that checks if the User has signed a route. + +[source, php] +---- +final class UserTermsVoter extends Voter +{ + /** @var HasSignedLastTermsVersion **/ + protected $hasSignedLastTermsVersion; + + /** @var Security + protected $security; + + // ... + + protected function supports($attribute, $subject): bool + { + $user = $this->security->getUser(); + + return $user instanceof User + && $user instanceof TermsSubjectInterface + && $attribute === 'HAS_USER_SIGNED_TERMS'; + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + return ($this->hasSignedLastTermsVersion)('your_slug', $subject); + } +} +---- + +Using it in a controller is now straight forward ! + +[source, php] +---- +final class RandomRoute extends AbstractController +{ + /** + * @IsGranted("HAS_USER_SIGNED_TERMS") + */ + public function __invoke(): Response + { + return new Response('Yay!'); + } +} +---- + +=== Redirection after a voter + +Since the voter blocks the access of the route, this may be a brutal behaviour. A redirection to the signing page of the appropriate terms would be smoother. For this, a listener that catches when an `AccessDeniedException` is thrown is the best approach. + +The following example redirects the User if the voter denies the access. + +[source, php] +---- +final class RedirectUserWhenTermsNotSignedListener +{ + /** @var Security */ + private $security; + + /** @var GenerateSigningRoute */ + private $generateSigningRoute; + + // ... + + public function __invoke(ExceptionEvent $event): void + { + $user = $this->security->getUserCompany(); + $exception = $event->getThrowable(); + + if (!$exception instanceof AccessDeniedException || !user instanceof User) { + return; + } + + $route = ($this->generateSigningRoute)('your_slug', $user); + $response = new RedirectResposne($route); + $event->setResponse($response); + } +} +---- diff --git a/docs/Signatory.adoc b/docs/Signatory.adoc index 16ba7b2..a6c81ad 100644 --- a/docs/Signatory.adoc +++ b/docs/Signatory.adoc @@ -66,8 +66,8 @@ From the code, you can check if the use has signed using the use case `HasSignTe ---- final class ProtectedRoute extends AbstractController { - /** @var GenerateSigningRedirectResponse */ - protected $generateSigningRedirectResponse; + /** @var GenerateSigningRoute */ + protected $generateSigningRoute; /** @var HasSignedLastTermsVersion */ protected $hasSignedLastTermsVersion; @@ -88,7 +88,9 @@ final class ProtectedRoute extends AbstractController $isTermsSigned = ($this->hasSignedLastTermsVersion)($terms->getSlug(), $subject); if (!$isTermsSigned) { - return ($this->redirectSigningRedirectResponse)($terms, $subject); + $route = ($this->redirectSigningRedirectResponse)($terms, $subject); + + return new RedirectResponse($route); } } } From 82f16b7f76263c0732dcdb25ee5c7afcd393f742 Mon Sep 17 00:00:00 2001 From: Nicolas Guilloux Date: Wed, 28 Jul 2021 08:52:59 +0200 Subject: [PATCH 2/5] Fix Security documentation link --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index fe0fe74..a3145d7 100644 --- a/README.adoc +++ b/README.adoc @@ -36,7 +36,7 @@ Quick showcase - link:docs/Configuration.adoc[Configuration] + - link:docs/Creation.adoc[Creation] + - link:docs/Signatory.adoc[Signatory] + - - link:docs/Events.adoc[Events] + + - link:docs/Security.adoc[Security] + - link:docs/BackOffice.adoc[BackOffice] + - link:docs/Events.adoc[Events] + <<3. Versioning>> + @@ -60,7 +60,7 @@ composer require rich-id/terms-module-bundle * link:docs/Configuration.adoc[Configuration] * link:docs/Creation.adoc[Creation] * link:docs/Signatory.adoc[Signatory] -* link:docs/Events.adoc[Events] +* link:docs/Security.adoc[Security] * link:docs/BackOffice.adoc[BackOffice] * link:docs/Events.adoc[Events] From fb801cc6113c892abbecbd78b96b08852ee08c75 Mon Sep 17 00:00:00 2001 From: Nicolas Guilloux Date: Wed, 28 Jul 2021 08:59:20 +0200 Subject: [PATCH 3/5] Add route import --- README.adoc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.adoc b/README.adoc index a3145d7..53012db 100644 --- a/README.adoc +++ b/README.adoc @@ -54,6 +54,24 @@ This version of the bundle requires Symfony 4.4+ and PHP 7.3+. composer require rich-id/terms-module-bundle ---- +You must add the routes to your application by adding the following content to the file `config/routes/rich_id_terms_module.yaml`: + +[source, yaml] +---- +rich_id_terms_module: + resource: "@RichIdTermsModuleBundle/Resources/config/routing/routing.xml" + +# # Edit the path of the routes +# module_terms_sign: +# path: /cgu/{termsSlug}/signature +# methods: [GET|POST] +# defaults: { _controller: RichId\TermsModuleBundle\UserInterface\Controller\SignRoute } +# +# module_terms_terms: +# path: /cgu/{termsSlug} +# methods: GET +# defaults: { _controller: RichId\TermsModuleBundle\UserInterface\Controller\TermsRoute } +---- == 2. Documentation From a143b71a48a921a51ef05e9cab276e432ecc7f64 Mon Sep 17 00:00:00 2001 From: Nicolas Guilloux Date: Wed, 28 Jul 2021 09:10:19 +0200 Subject: [PATCH 4/5] Change migration trait to deprecated --- docs/Creation.adoc | 30 +++++++++++++++---- .../Migrations/TermsMigrationTrait.php | 5 ++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/Creation.adoc b/docs/Creation.adoc index b3b1664..8ef2d0e 100644 --- a/docs/Creation.adoc +++ b/docs/Creation.adoc @@ -2,7 +2,7 @@ The only way to create a new term is to make a migration. Since a term should always have at least one version, the migration will also create a version. -Hopefully, there is trait that might help you to do so. Here is an example of the usage of this trait: +Hopefully, this is quite straight forward since it just requires to insert 2 entries. The following example shoes how to insert terms with the slug `my_slug` and the title `My Terms`. [source, php] ---- @@ -12,16 +12,36 @@ use RichId\TermsModuleBundle\Infrastructure\Migrations\TermsMigrationTrait; final class RandomMigration extends AbstractMigration { - use TermsMigrationTrait; - public function up(Schema $schema): void { - $this->createTerms('my_slug', 'My Terms'); + $this->addSQL(" + INSERT INTO module_terms_terms + (slug, name, is_published, is_depublication_locked) + VALUES ('my_slug', 'My Terms', 0, 0) + "); + + $this->addSQL(" + INSERT INTO module_terms_terms_version + (version, is_enabled, title, content, publication_date, terms_id) + VALUES ( + 1, + 0, + 'My title', + 'My content', + CURRENT_TIME, + (SELECT id FROM module_terms_terms WHERE slug = 'my_slug') + ) + "); } public function down(Schema $schema): void { - $this->deleteTerms('my_slug'); + $this->addSQL(" + DELETE FROM module_terms_terms_version AS version WHERE + version.terms_id = (SELECT terms.id FROM module_terms_terms WHERE terms.slug = 'my_slug') + "); + + $this->addSQL("DELETE FROM module_terms_terms WHERE slug = 'my_slug'"); } } ---- diff --git a/src/Infrastructure/Migrations/TermsMigrationTrait.php b/src/Infrastructure/Migrations/TermsMigrationTrait.php index 43496cf..1331a7e 100644 --- a/src/Infrastructure/Migrations/TermsMigrationTrait.php +++ b/src/Infrastructure/Migrations/TermsMigrationTrait.php @@ -4,8 +4,12 @@ namespace RichId\TermsModuleBundle\Infrastructure\Migrations; +/** + * @deprecated The migrations should be stateless, please copy the raw SQL instead + */ trait TermsMigrationTrait { + /** @deprecated The migrations should be stateless, please copy the raw SQL instead */ protected function createTerms( string $slug, string $name, @@ -31,6 +35,7 @@ protected function createTerms( "); } + /** @deprecated The migrations should be stateless, please copy the raw SQL instead */ protected function deleteTerms(string $slug): void { $this->addSQL(" From d1743a382aa250abf8a476d74c0b2853a8e0404f Mon Sep 17 00:00:00 2001 From: Nicolas Guilloux Date: Wed, 28 Jul 2021 09:11:37 +0200 Subject: [PATCH 5/5] Fix CI redundancy --- .github/workflows/tests.yml | 2 +- README.adoc | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f4d8c3..49cc851 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,5 @@ name: Tests -on: [push, pull_request] +on: [pull_request] jobs: build-and-test: diff --git a/README.adoc b/README.adoc index 53012db..38ab94d 100644 --- a/README.adoc +++ b/README.adoc @@ -25,10 +25,6 @@ image:https://img.shields.io/badge/Symfony-5%2B-black[] A module to quickly implement a terms approvals voter with terms version support. -== Quick showcase - -Quick showcase - == Table of content <<1. Installation>> +