diff --git a/.lagoon.yml b/.lagoon.yml index 3167013c3..8261b6b63 100644 --- a/.lagoon.yml +++ b/.lagoon.yml @@ -14,12 +14,21 @@ tasks: service: cli - run: name: Run Drupal deploy tasks - command: drush -y deploy + # Source before https://github.com/uselagoon/lagoon/issues/574 + command: source /home/.bashrc && drush -y deploy service: cli - run: name: import translations from the ui command: drush scr scripts/translations-import.php service: cli + - run: + name: Create Keys for Simple OAuth if necessary + command: | + if [[ ! -f /app/web/sites/default/files/private/keys/private.key || ! -f /app/web/sites/default/files/private/keys/public.key ]]; then + mkdir -p /app/web/sites/default/files/private/keys + drush simple-oauth:generate-keys /app/web/sites/default/files/private/keys + fi + service: cli environments: prod: routes: diff --git a/INIT.md b/INIT.md index ff7f38219..98c56377e 100644 --- a/INIT.md +++ b/INIT.md @@ -66,6 +66,18 @@ replace( 'PROJECT_NAME=example', 'PROJECT_NAME=' + process.env.PROJECT_NAME_MACHINE, ); +const clientSecret = randomString(32); +replace( + ['apps/cms/.lagoon.env', 'apps/website/.lagoon.env'], + 'PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME', + 'PUBLISHER_OAUTH2_CLIENT_SECRET=' + clientSecret, +); +const sessionSecret = randomString(32); +replace( + ['apps/website/.lagoon.env'], + 'PUBLISHER_OAUTH2_SESSION_SECRET=REPLACE_ME', + 'PUBLISHER_OAUTH2_SESSION_SECRET=' + sessionSecret, +); // Template's prod domain is special. replace( '.lagoon.yml', @@ -106,7 +118,7 @@ Update the default hash salt. ```ts replace( 'apps/cms/scaffold/settings.php.append.txt', - 'banana123', + 'time-flies-like-an-arrow-fruit-flies-like-a-banana', randomString(32), ); ``` diff --git a/README.md b/README.md index b7815d135..1bad3b821 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,71 @@ lagoon runtime configuration. lagoon add variable -p [project name] -e dev -N NETLIFY_SITE_ID -V [netlify site id] ``` +### Publisher authentication with Drupal + +Publisher can require to authenticate with Drupal based on OAuth2. +It is only used on Lagoon environments. + +
+ How it works + + #### Drupal configuration + + ##### Create keys + + Per environment, keys are gitignored and are auto-generated via a Lagoon post-rollout task. + + To generate keys manually + + via Drush: cd in the cms directory then + + ```bash + drush simple-oauth:generate-keys ./keys + ``` + + or via the UI + + - Go to `/admin/config/people/simple_oauth` + - Click on "Generate keys", the directory should be set to `./sites/default/files/private/keys` + + ##### Create the Publisher Consumer + + Per environment, Consumers are content entities. + + - Go to `/admin/config/services/consumer` + - Create a Consumer + - Label: `Publisher` + - Client ID: `publisher` + - Secret: a random string + - Redirect URI: `[publisher-url]/oauth/callback` + - Optional: the default Consumer can be safely deleted + + Troubleshooting: + - make sure that the `DRUPAL_HASH_SALT` environment variable is >= 32 chars. + - if enabled on local development, use `127.0.0.1:8888` for the cms and `127.0.0.1:8000` for Publisher + + #### Publisher authentication + + Edit [website environment variables](./apps/website/.lagoon.env) + + ``` + PUBLISHER_SKIP_AUTHENTICATION=false + PUBLISHER_OAUTH2_CLIENT_SECRET="[secret used in the Drupal Consumer]" + PUBLISHER_OAUTH2_SESSION_SECRET="[another random string]" + ``` + + ##### Set the 'Access Publisher' permission + + Optional: add this permission to relevant roles. + +
+ +
+ How to disable it + + In website `.lagoon.env` set `PUBLISHER_SKIP_AUTHENTICATION=true` +
+ ## Storybook If a `CHROMATIC_PROJECT_TOKEN` environment variable is set, the Storybook build diff --git a/apps/cms/.lagoon.env b/apps/cms/.lagoon.env index 2086c7611..a2fa48dbc 100644 --- a/apps/cms/.lagoon.env +++ b/apps/cms/.lagoon.env @@ -1,3 +1,6 @@ PROJECT_NAME=example PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" + +# Used to set the original client secret. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/cms/composer.json b/apps/cms/composer.json index 1b1e2d248..810c814f7 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -68,6 +68,7 @@ "drupal/redirect": "^1.8", "drupal/reroute_email": "^2.2", "drupal/role_delegation": "^1.2", + "drupal/simple_oauth": "^5.2", "drupal/slack": "^1.4", "drupal/stage_file_proxy": "^2.0.2", "drupal/userprotect": "^1.2", @@ -91,6 +92,9 @@ }, "extra": { "patches": { + "drupal/core": { + "#2706241 AccessAwareRouter does not respect HTTP method": "https://www.drupal.org/files/issues/2023-03-17/2706241-74.patch" + }, "drupal/config_ignore": { "#2857247 Do not export ignored config": "https://www.drupal.org/files/issues/2021-08-18/config_ignore_2857247-75.patch" }, diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index ca1f19f06..f9cac386a 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bc473cdb597af1e9aa1f8fb6cc8af83d", + "content-hash": "9d07ea8f1856520e8afa87e0af9c7fad", "packages": [ { "name": "amazeeio/drupal_integrations", @@ -431,16 +431,16 @@ }, { "name": "amazeelabs/silverback_gatsby", - "version": "2.3.11", + "version": "2.4.3", "source": { "type": "git", "url": "https://github.com/AmazeeLabs/silverback_gatsby.git", - "reference": "8a183f4a2a2fb7b7619912447153cff24195f9db" + "reference": "4cf68d4bab512310ede1136444da79c4b1b2b95a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AmazeeLabs/silverback_gatsby/zipball/8a183f4a2a2fb7b7619912447153cff24195f9db", - "reference": "8a183f4a2a2fb7b7619912447153cff24195f9db", + "url": "https://api.github.com/repos/AmazeeLabs/silverback_gatsby/zipball/4cf68d4bab512310ede1136444da79c4b1b2b95a", + "reference": "4cf68d4bab512310ede1136444da79c4b1b2b95a", "shasum": "" }, "type": "drupal-module", @@ -459,9 +459,9 @@ "homepage": "https://silverback.netlify.app", "support": { "issues": "https://github.com/AmazeeLabs/silverback_gatsby/issues", - "source": "https://github.com/AmazeeLabs/silverback_gatsby/tree/2.3.11" + "source": "https://github.com/AmazeeLabs/silverback_gatsby/tree/2.4.3" }, - "time": "2023-10-10T22:27:50+00:00" + "time": "2023-11-01T14:00:06+00:00" }, { "name": "amazeelabs/silverback_graphql_persisted", @@ -1723,6 +1723,73 @@ }, "time": "2022-12-20T22:53:13+00:00" }, + { + "name": "defuse/php-encryption", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v1.1.0", @@ -2328,6 +2395,54 @@ "issues": "http://drupal.org/project/issues/config_pages" } }, + { + "name": "drupal/consumers", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/consumers.git", + "reference": "8.x-1.17" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/consumers-8.x-1.17.zip", + "reference": "8.x-1.17", + "shasum": "8e7efc2c8386ead8ca459a1a5e0558fb66cde142" + }, + "require": { + "drupal/core": "^8 || ^9 || ^10" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.17", + "datestamp": "1679920063", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "e0ipso", + "homepage": "https://www.drupal.org/user/550110" + }, + { + "name": "eojthebrave", + "homepage": "https://www.drupal.org/user/79230" + } + ], + "description": "Declare all the consumers of your API", + "homepage": "https://www.drupal.org/project/consumers", + "support": { + "source": "https://git.drupalcode.org/project/consumers" + } + }, { "name": "drupal/core", "version": "9.5.9", @@ -3000,6 +3115,10 @@ "name": "e0ipso", "homepage": "https://www.drupal.org/user/550110" }, + { + "name": "isholgueras", + "homepage": "https://www.drupal.org/user/733162" + }, { "name": "mrfelton", "homepage": "https://www.drupal.org/user/305669" @@ -3044,6 +3163,10 @@ "GPL-2.0-or-later" ], "authors": [ + { + "name": "Anybody", + "homepage": "https://www.drupal.org/user/291091" + }, { "name": "Hydra", "homepage": "https://www.drupal.org/user/647364" @@ -3131,6 +3254,10 @@ "name": "klausi", "homepage": "https://www.drupal.org/user/262198" }, + { + "name": "luigisa", + "homepage": "https://www.drupal.org/user/1022312" + }, { "name": "pmelab", "homepage": "https://www.drupal.org/user/555322" @@ -3197,6 +3324,10 @@ "name": "Stephan Zeidler (szeidler)", "homepage": "https://www.drupal.org/u/szeidler" }, + { + "name": "roborn", + "homepage": "https://www.drupal.org/user/139474" + }, { "name": "szeidler", "homepage": "https://www.drupal.org/user/767652" @@ -3627,7 +3758,7 @@ "extra": { "drupal": { "version": "2.0.0", - "datestamp": "1687857487", + "datestamp": "1692368265", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -3770,6 +3901,10 @@ "name": "Dave Reid", "homepage": "https://www.drupal.org/user/53892" }, + { + "name": "Kristen Pol", + "homepage": "https://www.drupal.org/user/8389" + }, { "name": "pifagor", "homepage": "https://www.drupal.org/user/2375692" @@ -3903,6 +4038,75 @@ "issues": "http://drupal.org/project/role_delegation" } }, + { + "name": "drupal/simple_oauth", + "version": "5.2.3", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/simple_oauth.git", + "reference": "5.2.3" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/simple_oauth-5.2.3.zip", + "reference": "5.2.3", + "shasum": "9dae165101c0cff5e1f792457ec6a8eff5c1faf4" + }, + "require": { + "drupal/consumers": "^1.14", + "drupal/core": "^9 || ^10", + "lcobucci/jwt": "^4", + "league/oauth2-server": "^8.3", + "php": ">=7.4", + "steverhoades/oauth2-openid-connect-server": "^2.4" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "5.2.3", + "datestamp": "1670908048", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + }, + "drush": { + "services": { + "drush.services.yml": "^9" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "bojan_dev", + "homepage": "https://www.drupal.org/user/2801849" + }, + { + "name": "bradjones1", + "homepage": "https://www.drupal.org/user/405824" + }, + { + "name": "e0ipso", + "homepage": "https://www.drupal.org/user/550110" + }, + { + "name": "pcambra", + "homepage": "https://www.drupal.org/user/122101" + } + ], + "description": "The Simple OAuth module for Drupal", + "homepage": "https://www.drupal.org/project/simple_oauth", + "support": { + "source": "https://git.drupalcode.org/project/simple_oauth" + } + }, { "name": "drupal/slack", "version": "1.4.0", @@ -5286,6 +5490,144 @@ ], "time": "2022-07-27T12:28:58+00:00" }, + { + "name": "lcobucci/clock", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "shasum": "" + }, + "require": { + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^9.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.27" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2022-12-19T15:00:24+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-sodium": "*", + "lcobucci/clock": "^2.0 || ^3.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.21", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6.7", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/4.3.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2023-01-02T13:28:00+00:00" + }, { "name": "league/container", "version": "3.4.1", @@ -5365,6 +5707,319 @@ ], "time": "2021-07-09T08:23:52+00:00" }, + { + "name": "league/event", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119", + "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/master" + }, + "time": "2018-11-26T11:52:41+00:00" + }, + { + "name": "league/oauth2-server", + "version": "8.5.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "8ab731e84eef904b5913ba31b38116acf8ea50b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/8ab731e84eef904b5913ba31b38116acf8ea50b6", + "reference": "8ab731e84eef904b5913ba31b38116acf8ea50b6", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.3", + "ext-openssl": "*", + "lcobucci/clock": "^2.2 || ^3.0", + "lcobucci/jwt": "^4.3 || ^5.0", + "league/event": "^2.2", + "league/uri": "^6.7", + "php": "^8.0", + "psr/http-message": "^1.0.1 || ^2.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^3.0.0", + "phpstan/phpstan": "^0.12.57", + "phpstan/phpstan-phpunit": "^0.12.16", + "phpunit/phpunit": "^9.6.6", + "roave/security-advisories": "dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.2" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2023-06-16T15:32:47+00:00" + }, + { + "name": "league/uri", + "version": "6.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39", + "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39", + "shasum": "" + }, + "require": { + "ext-json": "*", + "league/uri-interfaces": "^2.3", + "php": "^8.1", + "psr/http-message": "^1.0.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.9.5", + "nyholm/psr7": "^1.5.1", + "php-http/psr7-integration-tests": "^1.1.1", + "phpbench/phpbench": "^1.2.6", + "phpstan/phpstan": "^1.8.5", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1.1", + "phpstan/phpstan-strict-rules": "^1.4.3", + "phpunit/phpunit": "^9.5.24", + "psr/http-factory": "^1.0.1" + }, + "suggest": { + "ext-fileinfo": "Needed to create Data URI from a filepath", + "ext-intl": "Needed to improve host validation", + "league/uri-components": "Needed to easily manipulate URI objects", + "psr/http-factory": "Needed to use the URI factory" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri/issues", + "source": "https://github.com/thephpleague/uri/tree/6.8.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2022-09-13T19:58:47+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", + "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.19", + "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan-phpunit": "^0.12.19", + "phpstan/phpstan-strict-rules": "^0.12.9", + "phpunit/phpunit": "^8.5.15 || ^9.5" + }, + "suggest": { + "ext-intl": "to use the IDNA feature", + "symfony/intl": "to use the IDNA feature via Symfony Polyfill" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interface for URI representation", + "homepage": "http://github.com/thephpleague/uri-interfaces", + "keywords": [ + "rfc3986", + "rfc3987", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/thephpleague/uri-interfaces/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2021-06-28T04:27:21+00:00" + }, { "name": "longwave/laminas-diactoros", "version": "2.14.2", @@ -5761,6 +6416,56 @@ }, "time": "2023-06-25T14:52:30+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "pear/archive_tar", "version": "1.4.14", @@ -6123,6 +6828,54 @@ }, "time": "2016-08-06T20:24:11+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "1.1.2", @@ -6502,6 +7255,51 @@ }, "time": "2020-01-30T12:17:27+00:00" }, + { + "name": "steverhoades/oauth2-openid-connect-server", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/steverhoades/oauth2-openid-connect-server.git", + "reference": "23381585ebb410ffa11ca9eb0fdba3895fb23119" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/steverhoades/oauth2-openid-connect-server/zipball/23381585ebb410ffa11ca9eb0fdba3895fb23119", + "reference": "23381585ebb410ffa11ca9eb0fdba3895fb23119", + "shasum": "" + }, + "require": { + "lcobucci/jwt": "4.1.5|^4.2", + "league/oauth2-server": "^5.1|^6.0|^7.0|^8.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^1.3.2", + "phpunit/phpunit": "^5.0|^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "OpenIDConnectServer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steve Rhoades", + "email": "sedonami@gmail.com" + } + ], + "description": "An OpenID Connect Server that sites on The PHP League's OAuth2 Server", + "support": { + "issues": "https://github.com/steverhoades/oauth2-openid-connect-server/issues", + "source": "https://github.com/steverhoades/oauth2-openid-connect-server/tree/v2.5.0" + }, + "time": "2023-01-19T16:49:09+00:00" + }, { "name": "symfony-cmf/routing", "version": "2.3.4", @@ -13471,5 +14269,5 @@ "php": "^8.1 <8.2" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/apps/cms/config/sync/core.extension.yml b/apps/cms/config/sync/core.extension.yml index 72cb1cfb2..fba086434 100644 --- a/apps/cms/config/sync/core.extension.yml +++ b/apps/cms/config/sync/core.extension.yml @@ -13,6 +13,7 @@ module: config_notify: 0 config_pages: 0 config_translation: 0 + consumers: 0 content_moderation: 0 custom: 0 datetime: 0 @@ -56,6 +57,7 @@ module: redirect: 0 reroute_email: 0 role_delegation: 0 + serialization: 0 shortcut: 0 silverback_campaign_urls: 0 silverback_cloudinary: 0 @@ -66,6 +68,7 @@ module: silverback_iframe: 0 silverback_publisher_monitor: 0 silverback_translations: 0 + simple_oauth: 0 slack: 0 sqlite: 0 stage_file_proxy: 0 diff --git a/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.access_token.yml b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.access_token.yml new file mode 100644 index 000000000..87dd19c66 --- /dev/null +++ b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.access_token.yml @@ -0,0 +1,2 @@ +label: Zugriffs-Token +description: 'Der Typ des Zugriffstokens.' diff --git a/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.auth_code.yml b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.auth_code.yml new file mode 100644 index 000000000..01bc5f23b --- /dev/null +++ b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.auth_code.yml @@ -0,0 +1,2 @@ +label: Auth-Code +description: 'Der Auth-Code-Typ.' diff --git a/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.refresh_token.yml b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.refresh_token.yml new file mode 100644 index 000000000..c8fcf0116 --- /dev/null +++ b/apps/cms/config/sync/language/de/simple_oauth.oauth2_token.bundle.refresh_token.yml @@ -0,0 +1,2 @@ +label: Refresh-Token +description: 'Der Refresh-Token-Typ.' diff --git a/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml new file mode 100644 index 000000000..5733e0f8e --- /dev/null +++ b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.access_token.yml @@ -0,0 +1,10 @@ +uuid: 7015f368-02ba-4caa-a7e3-9af42cb15a95 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: z9ULI9nj9yt73YKI3ZE8v9yXhkVfvQsDJToEDzijcxY +id: access_token +label: 'Access Token' +description: 'The access token type.' +locked: true diff --git a/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml new file mode 100644 index 000000000..f16a8d4d7 --- /dev/null +++ b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.auth_code.yml @@ -0,0 +1,10 @@ +uuid: 367f912c-2360-4294-8c80-469183f3cdd6 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: zYKaSl4QZrKMFj7aIhSGDRcBy4SoNjvY2EZlT7amrBk +id: auth_code +label: 'Auth code' +description: 'The auth code type.' +locked: true diff --git a/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml new file mode 100644 index 000000000..b7fa539c9 --- /dev/null +++ b/apps/cms/config/sync/simple_oauth.oauth2_token.bundle.refresh_token.yml @@ -0,0 +1,10 @@ +uuid: 46fd3b44-6aea-4730-9e23-4f1c6f603cc2 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: YWMv3Do9fsPFhylyFkOwcqcFP4jSU6DLRootOlgrC0M +id: refresh_token +label: 'Refresh token' +description: 'The refresh token type.' +locked: true diff --git a/apps/cms/config/sync/simple_oauth.settings.yml b/apps/cms/config/sync/simple_oauth.settings.yml new file mode 100644 index 000000000..b9deb4886 --- /dev/null +++ b/apps/cms/config/sync/simple_oauth.settings.yml @@ -0,0 +1,11 @@ +_core: + default_config_hash: KsPFWSp6mgXIQgjBJEShfKUGn6VLRlbpIJ2EysXvXWM +access_token_expiration: 3600 +authorization_code_expiration: 300 +refresh_token_expiration: 1209600 +token_cron_batch_size: 0 +public_key: ./sites/default/files/private/keys/public.key +private_key: ./sites/default/files/private/keys/private.key +remember_clients: true +use_implicit: false +disable_openid_connect: false diff --git a/apps/cms/config/sync/user.role.authenticated.yml b/apps/cms/config/sync/user.role.authenticated.yml index deb1d251e..11c998238 100644 --- a/apps/cms/config/sync/user.role.authenticated.yml +++ b/apps/cms/config/sync/user.role.authenticated.yml @@ -5,6 +5,7 @@ dependencies: module: - graphql - media + - simple_oauth - system - userprotect _core: @@ -16,6 +17,7 @@ is_admin: false permissions: - 'access content' - 'execute main persisted graphql requests' + - 'grant simple_oauth codes' - userprotect.account.edit - userprotect.mail.edit - userprotect.pass.edit diff --git a/apps/cms/install-cache.zip b/apps/cms/install-cache.zip new file mode 100644 index 000000000..0ce1ca4f9 Binary files /dev/null and b/apps/cms/install-cache.zip differ diff --git a/apps/cms/scaffold/settings.php.append.txt b/apps/cms/scaffold/settings.php.append.txt index 9056d00e6..7627baaa7 100644 --- a/apps/cms/scaffold/settings.php.append.txt +++ b/apps/cms/scaffold/settings.php.append.txt @@ -1,5 +1,5 @@ // Basic settings -$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT') ?: 'banana123'; +$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT') ?: 'time-flies-like-an-arrow-fruit-flies-like-a-banana'; $settings['config_sync_directory'] = '../config/sync'; $settings['file_private_path'] = $app_root . '/sites/default/files/private'; @@ -60,3 +60,7 @@ if (getenv('LAGOON_ENVIRONMENT') !== 'prod') { if (getenv('LAGOON_ENVIRONMENT') !== 'prod') { $config['slack.settings']['slack_webhook_url'] = ''; } + +// Disable key permissions check for Simple OAuth. +// https://www.drupal.org/project/simple_oauth/issues/3021054 +$settings['simple_oauth.key_permissions_check'] = FALSE; diff --git a/apps/website/.lagoon.env b/apps/website/.lagoon.env index 3be6d1d37..89379ad89 100644 --- a/apps/website/.lagoon.env +++ b/apps/website/.lagoon.env @@ -1,4 +1,28 @@ PROJECT_NAME=example DRUPAL_INTERNAL_URL="http://nginx:8080" DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" -NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" \ No newline at end of file +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" + +# ----------------------------------------------- +# Publisher authentication with Drupal (OAuth2). +# See main ./README.md for more information. +# ----------------------------------------------- +# Set to true to fully skip authentication. +PUBLISHER_SKIP_AUTHENTICATION=false + +# Secret from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME + +# Client id from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_ID=publisher + +# A random string, used to encrypt the session. +PUBLISHER_OAUTH2_SESSION_SECRET=REPLACE_ME + +# "development" or "production", production will trust first proxy +# and serve secure cookies. +PUBLISHER_OAUTH2_ENVIRONMENT_TYPE=production + +# DRUPAL_EXTERNAL_URL is used by default, but can be overridden +# to match the Drupal production url, without the nginx prefix. +PUBLISHER_OAUTH2_TOKEN_HOST="${DRUPAL_EXTERNAL_URL}" diff --git a/apps/website/package.json b/apps/website/package.json index 6afca8b44..086624c31 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -6,7 +6,7 @@ "@amazeelabs/cloudinary-responsive-image": "^1.6.7", "@amazeelabs/gatsby-silverback-cloudinary": "^1.0.47", "@amazeelabs/gatsby-source-silverback": "^1.10.9", - "@amazeelabs/publisher": "^2.4.11", + "@amazeelabs/publisher": "^2.4.12", "@amazeelabs/strangler-netlify": "^1.1.2", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", diff --git a/apps/website/publisher.config.ts b/apps/website/publisher.config.ts index f64e4c114..8b03fba4c 100644 --- a/apps/website/publisher.config.ts +++ b/apps/website/publisher.config.ts @@ -36,4 +36,15 @@ export default defineConfig({ }, databaseUrl: 'persisted-store/publisher.sqlite', publisherPort: isLagoon ? 3000 : 8000, + oAuth2: isLagoon ? { + clientId: process.env.PUBLISHER_OAUTH2_CLIENT_ID || 'publisher', + clientSecret: process.env.PUBLISHER_OAUTH2_CLIENT_SECRET || 'publisher', + sessionSecret: process.env.PUBLISHER_OAUTH2_SESSION_SECRET || 'banana', + tokenHost: process.env.PUBLISHER_OAUTH2_TOKEN_HOST || 'http://127.0.0.1:8888', + environmentType: process.env.PUBLISHER_OAUTH2_ENVIRONMENT_TYPE || 'development', + scope: 'publisher', + tokenPath: '/oauth/token', + authorizePath: '/oauth/authorize?response_type=code', + grantType: 0, + } : undefined, }); diff --git a/packages/drupal/custom/custom.deploy.php b/packages/drupal/custom/custom.deploy.php new file mode 100644 index 000000000..29177aad4 --- /dev/null +++ b/packages/drupal/custom/custom.deploy.php @@ -0,0 +1,58 @@ +getStorage('consumer'); + $existingConsumers = $consumersStorage->loadMultiple(); + $hasPublisherConsumer = FALSE; + /** @var \Drupal\consumers\Entity\ConsumerInterface $consumer */ + foreach($existingConsumers as $consumer) { + // As a side effect, delete the default consumer. + // It is installed by the Consumers module. + if ($consumer->getClientId() === 'default_consumer') { + $consumer->delete(); + } + if ($consumer->getClientId() === 'publisher') { + $hasPublisherConsumer = TRUE; + } + } + + // Create the Publisher Consumer if it does not exist. + if (!$hasPublisherConsumer) { + $oAuthCallback = $publisherUrl . '/oauth/callback'; + $consumersStorage->create([ + 'label' => 'Publisher', + 'client_id' => 'publisher', + 'is_default' => TRUE, + 'secret' => $clientSecret, + 'redirect' => $oAuthCallback, + ])->save(); + return t('Created Publisher OAuth Consumer.'); + } + + return t('Publisher OAuth Consumer already exists.'); +} diff --git a/packages/drupal/custom/custom.info.yml b/packages/drupal/custom/custom.info.yml index 4a072b8dd..e0d6338fd 100644 --- a/packages/drupal/custom/custom.info.yml +++ b/packages/drupal/custom/custom.info.yml @@ -2,3 +2,7 @@ name: Various customizations package: Custom type: module core_version_requirement: ^9.0 || ^10.0 +dependencies: + - silverback_gatsby:silverback_gatsby + - simple_oauth:simple_oauth + - consumers:consumers diff --git a/packages/drupal/custom/custom.module b/packages/drupal/custom/custom.module index 9d84cafdf..030c5f087 100644 --- a/packages/drupal/custom/custom.module +++ b/packages/drupal/custom/custom.module @@ -12,6 +12,7 @@ use Drupal\media\Entity\Media; use Drupal\silverback_gutenberg\LinkProcessor; use Drupal\user\Entity\Role; use Drupal\user\UserInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * Implements hook_default_content_exported_fields_alter(). @@ -180,3 +181,14 @@ function _custom_key_auth_form_access(UserInterface $user): AccessResult { $access->addCacheableDependency($user); return $access; } + +/** + * Implements hook_file_download(). + * + * Prevent any access to keys. + */ +function custom_file_download($uri) { + if (str_starts_with($uri, 'private://') && str_ends_with($uri, '.key')) { + throw new AccessDeniedHttpException(); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ea510f53..4a1ef6839 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,8 +178,8 @@ importers: specifier: ^1.10.9 version: 1.10.9(gatsby@5.11.0) '@amazeelabs/publisher': - specifier: ^2.4.11 - version: 2.4.11(@types/react@18.2.14)(react@18.2.0)(typescript@4.9.5) + specifier: ^2.4.12 + version: 2.4.12(@types/react@18.2.14)(react@18.2.0)(typescript@4.9.5) '@amazeelabs/strangler-netlify': specifier: ^1.1.2 version: 1.1.2(happy-dom@9.20.3)(react@18.2.0) @@ -784,8 +784,8 @@ packages: resolution: {integrity: sha512-YqY6SZOPbFtnxuVhg6zktpE720LN8m8Ipzh6p5GWuyOiWX4FmWvb+rV/8bjVVWDZ65gJfGIzVSm3LopX/4HcbQ==} dev: false - /@amazeelabs/publisher@2.4.11(@types/react@18.2.14)(react@18.2.0)(typescript@4.9.5): - resolution: {integrity: sha512-ULuabN+WFg5i/FSW47vONvDul/I7b7p9uNIiXmSsEAsx3TJkxnhdXta2NUnc3O0Vtocjv0BeEkOdWHOoB92dUA==} + /@amazeelabs/publisher@2.4.12(@types/react@18.2.14)(react@18.2.0)(typescript@4.9.5): + resolution: {integrity: sha512-Tiphm3Ox+mGdCEM2FgJEhdpu31+mPaa5Nt+jagA1Wp0gDeADJCJP+VuF3WaQcriYZdvLf5OES7TjKz63m9TAsQ==} engines: {node: '>=16'} hasBin: true dependencies: