From 41b8f9328a9f395ad1cdc6dc202a20c0368acaf5 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Willms <tleilax+studip@gmail.com> Date: Fri, 9 Sep 2022 11:07:12 +0000 Subject: [PATCH] add language middleware that sets the system's language from a given... Closes #1568 Merge request studip/studip!993 --- composer.json | 3 +- composer.lock | 546 ++++++++++++++++++- lib/classes/JsonApi/Middlewares/Language.php | 89 +++ lib/classes/JsonApi/middleware.php | 3 + 4 files changed, 621 insertions(+), 20 deletions(-) create mode 100644 lib/classes/JsonApi/Middlewares/Language.php diff --git a/composer.json b/composer.json index 696383c3c73..03104690821 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ "symfony/console": "~5.3.16", "symfony/process": "^5.4", "jumbojett/openid-connect-php": "^0.9.2", - "league/oauth2-server": "^8.3" + "league/oauth2-server": "^8.3", + "willdurand/negotiation": "^3.1" } } diff --git a/composer.lock b/composer.lock index 6ee399c66cb..863f5548742 100644 --- a/composer.lock +++ b/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": "d8b9598df77b1d0451559f6951ad6c2d", + "content-hash": "7a9043e1984ae4fb02e5872928486f0a", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -220,6 +220,72 @@ }, "time": "2016-10-19T07:14:15+00:00" }, + { + "name": "defuse/php-encryption", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/77880488b9954b7884c25555c2a0ea9e7053f9d2", + "reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5|^6|^7|^8|^9" + }, + "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.3.1" + }, + "time": "2021-04-09T23:57:26+00:00" + }, { "name": "ezyang/htmlpurifier", "version": "v4.13.0", @@ -638,6 +704,392 @@ }, "time": "2019-10-25T12:34:43+00:00" }, + { + "name": "lcobucci/jwt", + "version": "3.4.6", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "3ef8657a78278dfeae7707d51747251db4176240" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/3ef8657a78278dfeae7707d51747251db4176240", + "reference": "3ef8657a78278dfeae7707d51747251db4176240", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-openssl": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "mikey179/vfsstream": "~1.5", + "phpmd/phpmd": "~2.2", + "phpunit/php-invoker": "~1.1", + "phpunit/phpunit": "^5.7 || ^7.3", + "squizlabs/php_codesniffer": "~2.3" + }, + "suggest": { + "lcobucci/clock": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "files": [ + "compat/class-aliases.php", + "compat/json-exception-polyfill.php", + "compat/lcobucci-clock-polyfill.php" + ], + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Otávio Cobucci Oblonczyk", + "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/3.4.6" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2021-09-28T19:18:28+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.3.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/7aeb7c42b463b1a6fe4d084d3145e2fa22436876", + "reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.2.1", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/jwt": "^3.4.6 || ^4.0.4", + "league/event": "^2.2", + "league/uri": "^6.4", + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0.1" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^2.4.1", + "phpstan/phpstan": "^0.12.57", + "phpstan/phpstan-phpunit": "^0.12.16", + "phpunit/phpunit": "^8.5.13", + "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.3.5" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2022-05-03T21:21:28+00:00" + }, + { + "name": "league/uri", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/09da64118eaf4c5d52f9923a1e6a5be1da52fd9a", + "reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "league/uri-interfaces": "^2.1", + "php": ">=7.2", + "psr/http-message": "^1.0" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^8.0 || ^9.0", + "psr/http-factory": "^1.0" + }, + "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": "http://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.4.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2020-11-22T14:29:11+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": "mishal/iless", "version": "2.2.0", @@ -2298,16 +2750,16 @@ }, { "name": "symfony/console", - "version": "v5.3.6", + "version": "v5.3.16", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + "reference": "2e322c76cdccb302af6b275ea2207169c8355328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "url": "https://api.github.com/repos/symfony/console/zipball/2e322c76cdccb302af6b275ea2207169c8355328", + "reference": "2e322c76cdccb302af6b275ea2207169c8355328", "shasum": "" }, "require": { @@ -2320,7 +2772,6 @@ "symfony/string": "^5.1" }, "conflict": { - "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", @@ -2377,7 +2828,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.6" + "source": "https://github.com/symfony/console/tree/v5.3.16" }, "funding": [ { @@ -2393,7 +2844,7 @@ "type": "tidelift" } ], - "time": "2021-07-27T19:10:22+00:00" + "time": "2022-03-01T08:24:05+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3637,6 +4088,62 @@ } ], "time": "2020-10-01T07:46:32+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" } ], "packages-dev": [ @@ -5191,16 +5698,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.7.15", + "version": "1.8.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd0202ea1b1fc6d1bbe156c6e2e18a03e0ff160a" + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd0202ea1b1fc6d1bbe156c6e2e18a03e0ff160a", - "reference": "cd0202ea1b1fc6d1bbe156c6e2e18a03e0ff160a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c386ab2741e64cc9e21729f891b28b2b10fe6618", + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618", "shasum": "" }, "require": { @@ -5224,9 +5731,13 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.7.15" + "source": "https://github.com/phpstan/phpstan/tree/1.8.6" }, "funding": [ { @@ -5237,16 +5748,12 @@ "url": "https://github.com/phpstan", "type": "github" }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", "type": "tidelift" } ], - "time": "2022-06-20T08:29:01+00:00" + "time": "2022-09-23T09:54:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -6899,7 +7406,8 @@ "ext-json": "*", "ext-pcre": "*", "ext-pdo": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-dom": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/lib/classes/JsonApi/Middlewares/Language.php b/lib/classes/JsonApi/Middlewares/Language.php new file mode 100644 index 00000000000..8843445665d --- /dev/null +++ b/lib/classes/JsonApi/Middlewares/Language.php @@ -0,0 +1,89 @@ +<?php +namespace JsonApi\Middlewares; + +use Negotiation\LanguageNegotiator; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface as RequestHandler; + +/** + * This class defines a middleware that tries to set the language for Stud.IP + * by analyzing the HTTP header "Accept-Language". + * + * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> + */ +class Language +{ + /** + * @param Request $request das Request-Objekt + * @param RequestHandler $handler der PSR-15 Request Handler + * + * @return ResponseInterface das neue Response-Objekt + */ + public function __invoke(Request $request, RequestHandler $handler) + { + $language = $this->detectValidLanguageFromRequest($request); + + // Set language if detected + if ($language) { + $_SESSION['_language'] = $language; + setTempLanguage(false, $language); + } + + return $handler->handle($request); + } + + /** + * Tries to detect a valid installed language from the request. + * + * @param Request $request + * @return string|null The detected language (if any) + */ + private function detectValidLanguageFromRequest(Request $request): ?string + { + if (!$request->hasHeader('Accept-Language')) { + return null; + } + + $negotiator = new LanguageNegotiator(); + $best_language = $negotiator->getBest( + $request->getHeaderLine('Accept-Language'), + $this->getStudIPLanguagePriorities() + ); + + if (!$best_language) { + return null; + } + + return $this->normalizeLanguageForStudIP($best_language->getType()); + } + + /** + * Returns a list of the normalized installed languages for the Stud.IP + * system. + * + * @return array + */ + private function getStudIPLanguagePriorities(): array + { + return array_map( + function ($language) { + return str_replace('_', '-', $language); + }, + array_keys($GLOBALS['INSTALLED_LANGUAGES']) + ); + } + + /** + * Normalizes the given language string (<language>-<variety>, e.g. de-de) + * for Stud.IP (e.g. de_DE). + * + * @param string $language + * @return string + */ + private function normalizeLanguageForStudIP(string $language): string + { + $tags = explode('-', $language); + return $tags[0] . '_' . strtoupper($tags[1]); + } +} diff --git a/lib/classes/JsonApi/middleware.php b/lib/classes/JsonApi/middleware.php index 7a318d5b7ca..f65231c8861 100644 --- a/lib/classes/JsonApi/middleware.php +++ b/lib/classes/JsonApi/middleware.php @@ -26,6 +26,9 @@ return function (App $app) { // Add Routing Middleware $app->addRoutingMiddleware(); + // Add language middleware + $app->add(new Middlewares\Language()); + /** @var array|null */ $corsOrigin = \Config::get()->getValue('JSONAPI_CORS_ORIGIN'); if (is_array($corsOrigin) && count($corsOrigin)) { -- GitLab