diff --git a/composer.json b/composer.json index 696383c3c73b5c47cdec2fd7ff38435527e1c5b2..03104690821eecacafc0bc3769e2b445d7ecf8e2 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 6ee399c66cbcbbbd85f8530f37399aef101dd6e9..863f5548742d4eea358bb467d8b75c15a01d09b4 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 0000000000000000000000000000000000000000..8843445665d6bfc7b7f44645b3f5c9b779aac479 --- /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 7a318d5b7cad34007153df713b579536ffb2b75d..f65231c886184106d15a54f1a4a9b68df63f84cd 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)) {