From ff83c5ec035a1a38652e8d1ae71904b7026889ca Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 20:50:07 +0200 Subject: [PATCH 1/8] add image field to rex_ycom_user --- install/tablesets/yform_user.json | 18 ++++++++++++++++++ lang/de_de.lang | 1 + 2 files changed, 19 insertions(+) diff --git a/install/tablesets/yform_user.json b/install/tablesets/yform_user.json index b2015e1f..4b783668 100644 --- a/install/tablesets/yform_user.json +++ b/install/tablesets/yform_user.json @@ -390,6 +390,24 @@ "attributes": "", "relation_table": "", "filter": "" + }, + { + "table_name": "rex_ycom_user", + "prio": 25, + "type_id": "value", + "type_name": "text", + "db_type": "", + "list_hidden": 1, + "search": 1, + "name": "user_image", + "label": "translate:image", + "not_required": "", + "default": "", + "no_db": "", + "notice": "", + "attributes": "", + "prepend": "", + "append": "" } ] }, diff --git a/lang/de_de.lang b/lang/de_de.lang index 105c716f..333f6fe6 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -26,6 +26,7 @@ rex_ycom_user = User email = E-Mail status = Status firstname = Vorname +image = Bild activation_key = Aktivierungsschlüssel session_key = Sessionschlüssel last_login_time = Letzter erfolgreicher Login From 376330db669d1cab439da5aa30b8efcc6c108fb5 Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 21:52:32 +0200 Subject: [PATCH 2/8] Add twitch via OAuth2 auth as login method --- composer.json | 3 +- composer.lock | 59 ++++++- docs/08_extern_auth.md | 31 ++++ plugins/auth/boot.php | 3 + plugins/auth/install.php | 2 +- plugins/auth/install/oauth2_twitch.php | 8 + .../yform/trait_value_auth_oauth2_twitch.php | 164 ++++++++++++++++++ .../yform/value/ycom_auth_oauth2_twitch.php | 137 +++++++++++++++ .../value.ycom_auth_oauth2_twitch.tpl.php | 11 ++ 9 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 plugins/auth/install/oauth2_twitch.php create mode 100644 plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php create mode 100644 plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php create mode 100644 plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php diff --git a/composer.json b/composer.json index 83847ded..a135f28c 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "onelogin/php-saml": "^3", "apereo/phpcas": "^1", "league/oauth2-client": "^2", - "psr/log": "^1" + "psr/log": "^1", + "vertisan/oauth2-twitch-helix": "^2.0" }, "replace": { "psr/container": "*", diff --git a/composer.lock b/composer.lock index 2dbca8d3..32597f65 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": "19f9ec63bb47c63141499bf4c7d593f1", + "content-hash": "15976251497e431c1fe47602ba5a41c6", "packages": [ { "name": "apereo/phpcas", @@ -836,6 +836,63 @@ } ], "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "vertisan/oauth2-twitch-helix", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/vertisan/oauth2-twitch-helix.git", + "reference": "08d997ec0d2e1caf7bcb12ac820734d9c8a810a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vertisan/oauth2-twitch-helix/zipball/08d997ec0d2e1caf7bcb12ac820734d9c8a810a9", + "reference": "08d997ec0d2e1caf7bcb12ac820734d9c8a810a9", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.2.1", + "php": ">=7.1" + }, + "require-dev": { + "ext-json": "*", + "jakub-onderka/php-parallel-lint": "^1.0", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^10.0.5", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Vertisan\\OAuth2\\Client\\Provider\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paweł Farys", + "email": "pmg.farys@gmail.com", + "homepage": "https://github.com/vertisan" + } + ], + "description": "Twitch (new version Helix) OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "client", + "helix", + "league", + "oauth", + "package", + "twitch" + ], + "support": { + "issues": "https://github.com/vertisan/oauth2-twitch-helix/issues", + "source": "https://github.com/vertisan/oauth2-twitch-helix/tree/2.0.0" + }, + "time": "2023-06-19T21:11:30+00:00" } ], "packages-dev": [ diff --git a/docs/08_extern_auth.md b/docs/08_extern_auth.md index 5e828058..a7b80ab1 100644 --- a/docs/08_extern_auth.md +++ b/docs/08_extern_auth.md @@ -65,6 +65,37 @@ rex_extension::register('YCOM_AUTH_SAML_MATCHING', function (rex_extension_point ## OAuth2 +## OAuth2 mit twitch + +Mit der OAuth2 Authentifizierung via twitch ist es möglich, sich mit einem twitch-Account in der YCOM zu registrieren und einzuloggen. +Dazu muss dieser Provider entsprechend vorbereitet sein. + +### Einrichtung + +Im ersten Schritt muss man eine App anlegen bei twitch. Hierfür einmal zu https://dev.twitch.tv/console/apps wechseln. Dort über den Button "Deine Anwendung registrieren" eine neue App erstellen - Kategorie "Website integration" auswählen. + +Anschließend die App bearbeiten und die folgenden Einstellungen vornehmen: + +- Name: Name der App +- OAuth Redirect URLs: https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_twitch&rex_ycom_auth_func=code +- Category: Website Integration + +Anschließend die App speichern und die Client-ID kopieren. +Dann ein neues Secret erzeugen und dieses ebenfalls kopieren / sichern. + +In den Ordner `redaxo/data/addons/ycom/` sollte bereits die Datei `oauth2_twitch.php` kopiert worden sein. Diese Datei muss nun entsprechend angepasst werden mit den kopierten Daten. + +Damit die Authentifizierung funktioniert, muss im Loginformular von YCOM folgender String (angepasst auf die eigenen Bedürfnisse) eingefügt werden: + +```php +ycom_auth_oauth2_twitch|twitch|error_msg|[allowed returnTo domains: DomainA,DomainB]|{"ycom_groups": 1, "termsofuse_accepted": 1}|direct_link 0,1 +``` + +#### Scope + +Zusätzlice Scopes lassen sich in der Datei `oauth2_twitch.php` als Array in der Variable `scopes` eintragen. Die Scopes müssen mit einem Komma getrennt werden. Weitere Scopes findet man hier: https://dev.twitch.tv/docs/authentication/scopes/ - z.B. `user:read:email` um die E-Mail-Adresse des Users zu erhalten (diese bringt der Provider aber bereits nativ mit). + + ## Allgemeines ### Loginseite diff --git a/plugins/auth/boot.php b/plugins/auth/boot.php index ff755f47..7601b934 100644 --- a/plugins/auth/boot.php +++ b/plugins/auth/boot.php @@ -49,6 +49,9 @@ case 'oauth2': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_MATCHING', $data, ['Userdata' => $Userdata])); break; + case 'oauth2_twitch': + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_TWITCH_MATCHING', $data, ['Userdata' => $Userdata])); + break; case 'saml': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_SAML_MATCHING', $data, ['Userdata' => $Userdata])); break; diff --git a/plugins/auth/install.php b/plugins/auth/install.php index 0d7d19c0..3085e2ae 100644 --- a/plugins/auth/install.php +++ b/plugins/auth/install.php @@ -46,7 +46,7 @@ rex_sql::factory()->setQuery('UPDATE rex_article SET `ycom_auth_type` = `ycom_auth_type` -1'); } -foreach (['saml', 'oauth2', 'cas'] as $settingType) { +foreach (['saml', 'oauth2', 'ouath2_twitch', 'cas'] as $settingType) { $pathFrom = __DIR__ . '/install/' . $settingType . '.php'; $pathTo = rex_addon::get('ycom')->getDataPath($settingType . '.php'); if (!file_exists($pathTo)) { diff --git a/plugins/auth/install/oauth2_twitch.php b/plugins/auth/install/oauth2_twitch.php new file mode 100644 index 00000000..74757bcd --- /dev/null +++ b/plugins/auth/install/oauth2_twitch.php @@ -0,0 +1,8 @@ + 'someJibberish', // Go to https://dev.twitch.tv/console/apps and create an app. The client ID from your app + 'clientSecret' => 'moreJibbereish', // In your app at https://dev.twitch.tv/console/apps, click on "Manage" and then "New Secret". The client password from your app + 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_twitch&rex_ycom_auth_func=code' // do not fill out first and wait for the login error message to fill it out - add it to your allowed domains in your app at https://dev.twitch.tv/console/apps + //'scope' => 'user:read:email,user:read:follows', +]; \ No newline at end of file diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php new file mode 100644 index 00000000..10cbca49 --- /dev/null +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php @@ -0,0 +1,164 @@ +auth_ClassKey.'.php'; + $SettingsPath = rex_addon::get('ycom')->getDataPath($SettingFile); + if (!file_exists($SettingsPath)) { + throw new rex_exception($this->auth_ClassKey . '-Settings file not found ['.$SettingsPath.']'); + } + + $settings = []; + include $SettingsPath; + return $settings; + } + + private function auth_getReturnTo(): string + { + $returnTos = []; + $returnTos[] = rex_request('returnTo', 'string'); // wenn returnTo übergeben wurde, diesen nehmen + $returnTos[] = rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_ok'), '', [], '&'); // Auth Ok -> article_id_jump_ok / Current Language will be selected + return rex_ycom_auth::getReturnTo($returnTos, ('' == $this->getElement(3)) ? [] : explode(',', $this->getElement(3))); + } + + private function auth_FormOutput(string $url): void + { + if ($this->needsOutput()) { + $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey. '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ + 'url' => $url, + 'name' => '{{ ' . $this->auth_ClassKey. '_auth }}', + ]); + } + } + + private function auth_redirectToFailed(string $message = ''): string + { + if ($this->params['debug']) { + dump($message); + return $message; + } + if ($this->auth_directLink) { + rex_response::sendCacheControl(); + rex_response::sendRedirect(rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_not_ok'))); + } + return ''; + } + + /** + * @param array $Userdata + * @param string $returnTo + * @throws rex_exception + * @return void + */ + private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): void + { + $defaultUserAttributes = []; + if ('' != $this->getElement(4)) { + if (null == $defaultUserAttributes = json_decode($this->getElement(4), true)) { + throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json'.$this->getElement(4)); + } + } + + $data = []; + $data['email'] = ''; + foreach (['User.email', 'emailAddress', 'email'] as $Key) { + if (isset($Userdata[$Key])) { + $data['email'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['firstname'] = ''; + foreach (['login'] as $Key) { + if (isset($Userdata[$Key])) { + $data['firstname'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['name'] = ''; + foreach (['display_name'] as $Key) { + if (isset($Userdata[$Key])) { + $data['name'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['user_image'] = ''; + foreach (['profile_image_url'] as $Key) { + if (isset($Userdata[$Key])) { + $data['user_image'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + foreach ($defaultUserAttributes as $defaultUserAttributeKey => $defaultUserAttributeValue) { + $data[$defaultUserAttributeKey] = $defaultUserAttributeValue; + } + + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_MATCHING', $data, ['Userdata' => $Userdata, 'AuthType' => $this->auth_ClassKey])); + + self::auth_clearUserSession(); + + // not logged in - check if available + $params = [ + 'loginName' => $data['email'], + 'loginStay' => true, + 'filter' => 'status > 0', + 'ignorePassword' => true, + ]; + + $loginStatus = rex_ycom_auth::login($params); + if (2 == $loginStatus) { + // already logged in + rex_ycom_user::updateUser($data); + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + // if user not found, check if exists, but no permission + $user = rex_ycom_user::query()->where('email', $data['email'])->findOne(); + if ($user) { + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'; + return; + } + + $user = rex_ycom_user::createUserByEmail($data); + if (!$user || count($user->getMessages()) > 0) { + if ($user && $this->params['debug']) { + dump($user->getMessages()); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'; + return; + } + + $params = []; + $params['loginName'] = $user->getValue('email'); + $params['ignorePassword'] = true; + $loginStatus = rex_ycom_auth::login($params); + + if (2 != $loginStatus) { + if ($this->params['debug']) { + dump($loginStatus); + dump($user); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'; + return; + } + + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + private function auth_clearUserSession(): void + { + foreach ($this->auth_SessionVars as $SessionKey) { + rex_ycom_auth::unsetSessionVar($SessionKey); + } + } +} diff --git a/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php b/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php new file mode 100644 index 00000000..230b7722 --- /dev/null +++ b/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php @@ -0,0 +1,137 @@ +www.yakamara.de + */ + +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use Vertisan\OAuth2\Client\Provider\TwitchHelix; + +class rex_yform_value_ycom_auth_oauth2_twitch extends rex_yform_value_abstract +{ + use rex_yform_trait_value_auth_oauth2_twitch; + + /** + * @var array|string[] + */ + private array $auth_requestFunctions = ['init', 'code', 'state']; + private bool $auth_directLink = false; + /** + * @var array|string[] + */ + private array $auth_SessionVars = ['OAUTH2_oauth2state']; + private string $auth_ClassKey = 'oauth2_twitch'; + + public function enterObject(): void + { + if (rex::isFrontend()) { + $this->auth_directLink = 1 == $this->getElement(5) ? true : false; + } + + rex_login::startSession(); + + $settings = $this->auth_loadSettings(); + $returnTo = $this->auth_getReturnTo(); + $this->auth_FormOutput(rex_getUrl('', '', ['rex_ycom_auth_mode' => 'oauth2_twitch', 'rex_ycom_auth_func' => 'init', 'returnTo' => $returnTo])); + + $requestMode = rex_request('rex_ycom_auth_mode', 'string', ''); + $requestFunction = rex_request('rex_ycom_auth_func', 'string', ''); + if (!in_array($requestFunction, $this->auth_requestFunctions, true) || $this->auth_ClassKey != $requestMode) { + if ($this->auth_directLink) { + $requestFunction = 'init'; + } else { + return; + } + } + + if ('' == $settings['redirectUri']) { + echo 'use this URL for redirect'; + dump(rex_yrewrite::getFullUrlByArticleId(rex_article::getCurrentId(), '', ['rex_ycom_auth_mode' => 'oauth2_twitch', 'rex_ycom_auth_func' => 'code'], '&')); + return; + } + + $provider = new TwitchHelix($settings); + + $Userdata = []; + switch ($requestFunction) { + case 'code': + $code = rex_request('code', 'string'); + if ('' != $code) { + if ('' == rex_ycom_auth::getSessionVar('OAUTH2_oauth2state') || empty($_GET['state']) || $_GET['state'] != rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')) { + if ($this->params['debug']) { + echo 'OAuth session saved state != OAuth State'; + dump(rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')); + dump($_GET['state']); + } + $this->auth_redirectToFailed('{{ oauth.error.state_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.state_code }}'; + return; + } + + $accessToken = null; + try { + /** @var \League\OAuth2\Client\Token\AccessToken $accessToken */ + $accessToken = $provider->getAccessToken('authorization_code', [ + 'code' => $code, + ]); + + if ($accessToken->hasExpired()) { + $this->auth_redirectToFailed('{{ oauth.error.access_expired }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.access_expired }}'; + return; + } + $resourceOwner = $provider->getResourceOwner($accessToken); + $Userdata = $resourceOwner->toArray(); + // print_r($Userdata); + // exit; + $returnTo = rex_ycom_auth::getSessionVar('OAUTH2_oauth2returnTo'); + rex_ycom_auth::unsetSessionVar('OAUTH2_oauth2returnTo'); + } catch (IdentityProviderException $e) { + if ($this->params['debug']) { + dump($e); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } catch (Exception $e) { + if ($this->params['debug']) { + echo 'OAuth Error'; + dump($accessToken); + dump($e); + dump($e->getMessage()); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } + } else { + $this->auth_redirectToFailed('{{ oauth.error.no_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.no_code }}'; + return; + } + break; + + case 'init': + $authorizationUrl = $provider->getAuthorizationUrl(); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2state', $provider->getState()); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2returnTo', $returnTo); + rex_response::sendCacheControl(); + rex_response::sendRedirect($authorizationUrl); + } + + $this->auth_createOrUpdateYComUser($Userdata, $returnTo); + } + + public function getDescription(): string + { + return 'ycom_auth_oauth2_twitch|label|error_msg|[allowed returnTo domains: DomainA,DomainB]|default Userdata as Json{"ycom_groups": 3, "termsofuse_accepted": 1}|direct_link 0,1'; + } +} diff --git a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php new file mode 100644 index 00000000..8b64d548 --- /dev/null +++ b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php @@ -0,0 +1,11 @@ +name .'" href="'.$url.'">' . $this->name . ''; \ No newline at end of file From a05797b037999bbbbb063e86a72ef5d760a207f4 Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 21:58:44 +0200 Subject: [PATCH 3/8] fix install twitch --- plugins/auth/install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth/install.php b/plugins/auth/install.php index 3085e2ae..1771fe73 100644 --- a/plugins/auth/install.php +++ b/plugins/auth/install.php @@ -46,7 +46,7 @@ rex_sql::factory()->setQuery('UPDATE rex_article SET `ycom_auth_type` = `ycom_auth_type` -1'); } -foreach (['saml', 'oauth2', 'ouath2_twitch', 'cas'] as $settingType) { +foreach (['saml', 'oauth2', 'oauth2_twitch', 'cas'] as $settingType) { $pathFrom = __DIR__ . '/install/' . $settingType . '.php'; $pathTo = rex_addon::get('ycom')->getDataPath($settingType . '.php'); if (!file_exists($pathTo)) { From 09ee2c4a56dcbcdc6ffdaa3065ad55c9167c56ff Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 22:19:30 +0200 Subject: [PATCH 4/8] Add google via OAuth2 auth as login method --- composer.json | 3 +- composer.lock | 57 +++++- docs/08_extern_auth.md | 37 ++++ plugins/auth/boot.php | 3 + plugins/auth/install.php | 2 +- plugins/auth/install/oauth2_google.php | 8 + .../yform/trait_value_auth_oauth2_google.php | 164 ++++++++++++++++++ .../yform/value/ycom_auth_oauth2_google.php | 137 +++++++++++++++ .../value.ycom_auth_oauth2_google.tpl.php | 11 ++ 9 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 plugins/auth/install/oauth2_google.php create mode 100644 plugins/auth/lib/yform/trait_value_auth_oauth2_google.php create mode 100644 plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php create mode 100644 plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php diff --git a/composer.json b/composer.json index a135f28c..2d235cd3 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "apereo/phpcas": "^1", "league/oauth2-client": "^2", "psr/log": "^1", - "vertisan/oauth2-twitch-helix": "^2.0" + "vertisan/oauth2-twitch-helix": "^2.0", + "league/oauth2-google": "^4.0" }, "replace": { "psr/container": "*", diff --git a/composer.lock b/composer.lock index 32597f65..d5b3b024 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": "15976251497e431c1fe47602ba5a41c6", + "content-hash": "1cde028b4d7fa9dedc85a5c1ebc5cba7", "packages": [ { "name": "apereo/phpcas", @@ -472,6 +472,61 @@ }, "time": "2023-04-16T18:19:15+00:00" }, + { + "name": "league/oauth2-google", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "1b01ba18ba31b29e88771e3e0979e5c91d4afe76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/1b01ba18ba31b29e88771e3e0979e5c91d4afe76", + "reference": "1b01ba18ba31b29e88771e3e0979e5c91d4afe76", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^6.0 || ^7.1", + "phpunit/phpunit": "^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "hello@shadowhand.com", + "homepage": "https://shadowhand.com" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-google/issues", + "source": "https://github.com/thephpleague/oauth2-google/tree/4.0.1" + }, + "time": "2023-03-17T15:20:52+00:00" + }, { "name": "onelogin/php-saml", "version": "3.6.1", diff --git a/docs/08_extern_auth.md b/docs/08_extern_auth.md index a7b80ab1..429c0d9e 100644 --- a/docs/08_extern_auth.md +++ b/docs/08_extern_auth.md @@ -95,6 +95,43 @@ ycom_auth_oauth2_twitch|twitch|error_msg|[allowed returnTo domains: DomainA,Doma Zusätzlice Scopes lassen sich in der Datei `oauth2_twitch.php` als Array in der Variable `scopes` eintragen. Die Scopes müssen mit einem Komma getrennt werden. Weitere Scopes findet man hier: https://dev.twitch.tv/docs/authentication/scopes/ - z.B. `user:read:email` um die E-Mail-Adresse des Users zu erhalten (diese bringt der Provider aber bereits nativ mit). +## OAuth2 mit Google + +Mit der OAuth2 Authentifizierung via Google ist es möglich, sich mit einem Google-Account in der YCOM zu registrieren und einzuloggen. Dazu muss dieser Provider entsprechend vorbereitet sein. + +### Einrichtung + +Im ersten Schritt muss man eine App anlegen bei Google. Hierfür einmal zu https://console.developers.google.com/ wechseln. Dort über den Button "Projekt auswählen" eine neues Projekt erstellen. Anschließend die App bearbeiten und die folgenden Einstellungen vornehmen: + +- OAuth Zustimmungsbildschirm: Einstellungen vornehmen +-- Anwendungsname: Name der App +-- Nutzersupport-E-Mail: E-Mail-Adresse für Support +-- Anwendungslogo: Logo der App +-- Startseite der App: Deine Domain +-- Kontaktdaten des Entwicklers: E-Mail-Adresse für Support +-- Autorisierte Domains: Deine Domain (optional für GSuite Nutzer) +-- Bereiche: +--- ./auth/userinfo.email (E-Mail-Adresse des Users) +--- ./auth/userinfo.profile (Profilinformationen des Users) + +Anschließend auf Anmeldedaten klicken und die folgenden Einstellungen vornehmen: +- Anmeldedaten erstellen: OAuth 2.0 Client IDs erstellen +-- Autorisierte JavaScript-Quellen: Deine Domain +-- Autorisierte Weiterleitungs-URIs: https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_google&rex_ycom_auth_func=code + +Danach kann die Client ID und der Clientschlüssel kopiert oder als JSON Datei heruntergeladen werden. + +In den Ordner `redaxo/data/addons/ycom/` sollte bereits die Datei `oauth2_google.php` kopiert worden sein. Diese Datei muss nun entsprechend angepasst werden mit den kopierten Daten. + +Damit die Authentifizierung funktioniert, muss im Loginformular von YCOM folgender String (angepasst auf die eigenen Bedürfnisse) eingefügt werden: + +```php +ycom_auth_oauth2_google|label|error_msg|[allowed returnTo domains: DomainA,DomainB]|default Userdata as Json{"ycom_groups": 3, "termsofuse_accepted": 1}|direct_link 0,1 +``` + +#### GSuite / Google Workspace Nutzer +In der Datei `oauth2_google.php` kann die Variable `hostedDomain` mit der Domain des GSuite / Google Workspace Accounts befüllt werden. Damit wird die Anmeldung auf Nutzer mit dieser Domain beschränkt. + ## Allgemeines diff --git a/plugins/auth/boot.php b/plugins/auth/boot.php index 7601b934..b4848362 100644 --- a/plugins/auth/boot.php +++ b/plugins/auth/boot.php @@ -52,6 +52,9 @@ case 'oauth2_twitch': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_TWITCH_MATCHING', $data, ['Userdata' => $Userdata])); break; + case 'oauth2_google': + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_GOOGLE_MATCHING', $data, ['Userdata' => $Userdata])); + break; case 'saml': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_SAML_MATCHING', $data, ['Userdata' => $Userdata])); break; diff --git a/plugins/auth/install.php b/plugins/auth/install.php index 1771fe73..547d96fd 100644 --- a/plugins/auth/install.php +++ b/plugins/auth/install.php @@ -46,7 +46,7 @@ rex_sql::factory()->setQuery('UPDATE rex_article SET `ycom_auth_type` = `ycom_auth_type` -1'); } -foreach (['saml', 'oauth2', 'oauth2_twitch', 'cas'] as $settingType) { +foreach (['saml', 'oauth2', 'oauth2_twitch', 'oauth2_google', 'cas'] as $settingType) { $pathFrom = __DIR__ . '/install/' . $settingType . '.php'; $pathTo = rex_addon::get('ycom')->getDataPath($settingType . '.php'); if (!file_exists($pathTo)) { diff --git a/plugins/auth/install/oauth2_google.php b/plugins/auth/install/oauth2_google.php new file mode 100644 index 00000000..023534ea --- /dev/null +++ b/plugins/auth/install/oauth2_google.php @@ -0,0 +1,8 @@ + 'someJibberish', // Go to https://console.cloud.google.com/ and setup a project. Setup an OAuth consent screen and choose scopes + 'clientSecret' => 'moreJibbereish', // In your project at https://console.cloud.google.com/, click on "Credential" and then "Create credentials => OAuth client ID". Fill out, add return url, get ID and secret + 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_google&rex_ycom_auth_func=code' // do not fill out first and wait for the login error message to fill it out + //'hostedDomain' => 'your-url.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts +]; diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php new file mode 100644 index 00000000..fde25810 --- /dev/null +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php @@ -0,0 +1,164 @@ +auth_ClassKey.'.php'; + $SettingsPath = rex_addon::get('ycom')->getDataPath($SettingFile); + if (!file_exists($SettingsPath)) { + throw new rex_exception($this->auth_ClassKey . '-Settings file not found ['.$SettingsPath.']'); + } + + $settings = []; + include $SettingsPath; + return $settings; + } + + private function auth_getReturnTo(): string + { + $returnTos = []; + $returnTos[] = rex_request('returnTo', 'string'); // wenn returnTo übergeben wurde, diesen nehmen + $returnTos[] = rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_ok'), '', [], '&'); // Auth Ok -> article_id_jump_ok / Current Language will be selected + return rex_ycom_auth::getReturnTo($returnTos, ('' == $this->getElement(3)) ? [] : explode(',', $this->getElement(3))); + } + + private function auth_FormOutput(string $url): void + { + if ($this->needsOutput()) { + $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey. '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ + 'url' => $url, + 'name' => '{{ ' . $this->auth_ClassKey. '_auth }}', + ]); + } + } + + private function auth_redirectToFailed(string $message = ''): string + { + if ($this->params['debug']) { + dump($message); + return $message; + } + if ($this->auth_directLink) { + rex_response::sendCacheControl(); + rex_response::sendRedirect(rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_not_ok'))); + } + return ''; + } + + /** + * @param array $Userdata + * @param string $returnTo + * @throws rex_exception + * @return void + */ + private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): void + { + $defaultUserAttributes = []; + if ('' != $this->getElement(4)) { + if (null == $defaultUserAttributes = json_decode($this->getElement(4), true)) { + throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json'.$this->getElement(4)); + } + } + + $data = []; + $data['email'] = ''; + foreach (['User.email', 'emailAddress', 'email'] as $Key) { + if (isset($Userdata[$Key])) { + $data['email'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['firstname'] = ''; + foreach (['given_name'] as $Key) { + if (isset($Userdata[$Key])) { + $data['firstname'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['name'] = ''; + foreach (['family_name'] as $Key) { + if (isset($Userdata[$Key])) { + $data['name'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['user_image'] = ''; + foreach (['picture'] as $Key) { + if (isset($Userdata[$Key])) { + $data['user_image'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + foreach ($defaultUserAttributes as $defaultUserAttributeKey => $defaultUserAttributeValue) { + $data[$defaultUserAttributeKey] = $defaultUserAttributeValue; + } + + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_MATCHING', $data, ['Userdata' => $Userdata, 'AuthType' => $this->auth_ClassKey])); + + self::auth_clearUserSession(); + + // not logged in - check if available + $params = [ + 'loginName' => $data['email'], + 'loginStay' => true, + 'filter' => 'status > 0', + 'ignorePassword' => true, + ]; + + $loginStatus = rex_ycom_auth::login($params); + if (2 == $loginStatus) { + // already logged in + rex_ycom_user::updateUser($data); + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + // if user not found, check if exists, but no permission + $user = rex_ycom_user::query()->where('email', $data['email'])->findOne(); + if ($user) { + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'; + return; + } + + $user = rex_ycom_user::createUserByEmail($data); + if (!$user || count($user->getMessages()) > 0) { + if ($user && $this->params['debug']) { + dump($user->getMessages()); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'; + return; + } + + $params = []; + $params['loginName'] = $user->getValue('email'); + $params['ignorePassword'] = true; + $loginStatus = rex_ycom_auth::login($params); + + if (2 != $loginStatus) { + if ($this->params['debug']) { + dump($loginStatus); + dump($user); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'; + return; + } + + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + private function auth_clearUserSession(): void + { + foreach ($this->auth_SessionVars as $SessionKey) { + rex_ycom_auth::unsetSessionVar($SessionKey); + } + } +} diff --git a/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php b/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php new file mode 100644 index 00000000..352d95a5 --- /dev/null +++ b/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php @@ -0,0 +1,137 @@ +www.yakamara.de + */ + +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Provider\Google; + +class rex_yform_value_ycom_auth_oauth2_google extends rex_yform_value_abstract +{ + use rex_yform_trait_value_auth_oauth2_google; + + /** + * @var array|string[] + */ + private array $auth_requestFunctions = ['init', 'code', 'state']; + private bool $auth_directLink = false; + /** + * @var array|string[] + */ + private array $auth_SessionVars = ['OAUTH2_oauth2state']; + private string $auth_ClassKey = 'oauth2_google'; + + public function enterObject(): void + { + if (rex::isFrontend()) { + $this->auth_directLink = 1 == $this->getElement(5) ? true : false; + } + + rex_login::startSession(); + + $settings = $this->auth_loadSettings(); + $returnTo = $this->auth_getReturnTo(); + $this->auth_FormOutput(rex_getUrl('', '', ['rex_ycom_auth_mode' => 'oauth2_google', 'rex_ycom_auth_func' => 'init', 'returnTo' => $returnTo])); + + $requestMode = rex_request('rex_ycom_auth_mode', 'string', ''); + $requestFunction = rex_request('rex_ycom_auth_func', 'string', ''); + if (!in_array($requestFunction, $this->auth_requestFunctions, true) || $this->auth_ClassKey != $requestMode) { + if ($this->auth_directLink) { + $requestFunction = 'init'; + } else { + return; + } + } + + if ('' == $settings['redirectUri']) { + echo 'use this URL for redirect'; + dump(rex_yrewrite::getFullUrlByArticleId(rex_article::getCurrentId(), '', ['rex_ycom_auth_mode' => 'oauth2_google', 'rex_ycom_auth_func' => 'code'], '&')); + return; + } + + $provider = new Google($settings); + + $Userdata = []; + switch ($requestFunction) { + case 'code': + $code = rex_request('code', 'string'); + if ('' != $code) { + if ('' == rex_ycom_auth::getSessionVar('OAUTH2_oauth2state') || empty($_GET['state']) || $_GET['state'] != rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')) { + if ($this->params['debug']) { + echo 'OAuth session saved state != OAuth State'; + dump(rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')); + dump($_GET['state']); + } + $this->auth_redirectToFailed('{{ oauth.error.state_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.state_code }}'; + return; + } + + $accessToken = null; + try { + /** @var \League\OAuth2\Client\Token\AccessToken $accessToken */ + $accessToken = $provider->getAccessToken('authorization_code', [ + 'code' => $code, + ]); + + if ($accessToken->hasExpired()) { + $this->auth_redirectToFailed('{{ oauth.error.access_expired }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.access_expired }}'; + return; + } + $resourceOwner = $provider->getResourceOwner($accessToken); + $Userdata = $resourceOwner->toArray(); + // print_r($Userdata); + // exit; + $returnTo = rex_ycom_auth::getSessionVar('OAUTH2_oauth2returnTo'); + rex_ycom_auth::unsetSessionVar('OAUTH2_oauth2returnTo'); + } catch (IdentityProviderException $e) { + if ($this->params['debug']) { + dump($e); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } catch (Exception $e) { + if ($this->params['debug']) { + echo 'OAuth Error'; + dump($accessToken); + dump($e); + dump($e->getMessage()); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } + } else { + $this->auth_redirectToFailed('{{ oauth.error.no_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.no_code }}'; + return; + } + break; + + case 'init': + $authorizationUrl = $provider->getAuthorizationUrl(); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2state', $provider->getState()); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2returnTo', $returnTo); + rex_response::sendCacheControl(); + rex_response::sendRedirect($authorizationUrl); + } + + $this->auth_createOrUpdateYComUser($Userdata, $returnTo); + } + + public function getDescription(): string + { + return 'ycom_auth_oauth2_google|label|error_msg|[allowed returnTo domains: DomainA,DomainB]|default Userdata as Json{"ycom_groups": 3, "termsofuse_accepted": 1}|direct_link 0,1'; + } +} diff --git a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php new file mode 100644 index 00000000..8b64d548 --- /dev/null +++ b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php @@ -0,0 +1,11 @@ +name .'" href="'.$url.'">' . $this->name . ''; \ No newline at end of file From 3ec4bfe0a8a338b9cc5c47ce7eec0f0f1ce8e42b Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 23:47:13 +0200 Subject: [PATCH 5/8] cs fixes --- plugins/auth/install/oauth2_google.php | 8 ++++---- plugins/auth/install/oauth2_twitch.php | 6 +++--- .../lib/yform/trait_value_auth_oauth2_google.php | 12 +++++------- .../lib/yform/trait_value_auth_oauth2_twitch.php | 12 +++++------- .../auth/lib/yform/value/ycom_auth_oauth2_google.php | 8 ++------ .../auth/lib/yform/value/ycom_auth_oauth2_twitch.php | 8 ++------ .../bootstrap/value.ycom_auth_oauth2_google.tpl.php | 6 +++--- .../bootstrap/value.ycom_auth_oauth2_twitch.tpl.php | 6 +++--- 8 files changed, 27 insertions(+), 39 deletions(-) diff --git a/plugins/auth/install/oauth2_google.php b/plugins/auth/install/oauth2_google.php index 023534ea..d3bb6c8a 100644 --- a/plugins/auth/install/oauth2_google.php +++ b/plugins/auth/install/oauth2_google.php @@ -1,8 +1,8 @@ 'someJibberish', // Go to https://console.cloud.google.com/ and setup a project. Setup an OAuth consent screen and choose scopes - 'clientSecret' => 'moreJibbereish', // In your project at https://console.cloud.google.com/, click on "Credential" and then "Create credentials => OAuth client ID". Fill out, add return url, get ID and secret - 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_google&rex_ycom_auth_func=code' // do not fill out first and wait for the login error message to fill it out - //'hostedDomain' => 'your-url.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts + 'clientId' => 'someJibberish', // Go to https://console.cloud.google.com/ and setup a project. Setup an OAuth consent screen and choose scopes + 'clientSecret' => 'moreJibbereish', // In your project at https://console.cloud.google.com/, click on "Credential" and then "Create credentials => OAuth client ID". Fill out, add return url, get ID and secret + 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_google&rex_ycom_auth_func=code', // do not fill out first and wait for the login error message to fill it out + // 'hostedDomain' => 'your-url.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts ]; diff --git a/plugins/auth/install/oauth2_twitch.php b/plugins/auth/install/oauth2_twitch.php index 74757bcd..e38cdd07 100644 --- a/plugins/auth/install/oauth2_twitch.php +++ b/plugins/auth/install/oauth2_twitch.php @@ -3,6 +3,6 @@ $settings = [ 'clientId' => 'someJibberish', // Go to https://dev.twitch.tv/console/apps and create an app. The client ID from your app 'clientSecret' => 'moreJibbereish', // In your app at https://dev.twitch.tv/console/apps, click on "Manage" and then "New Secret". The client password from your app - 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_twitch&rex_ycom_auth_func=code' // do not fill out first and wait for the login error message to fill it out - add it to your allowed domains in your app at https://dev.twitch.tv/console/apps - //'scope' => 'user:read:email,user:read:follows', -]; \ No newline at end of file + 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_twitch&rex_ycom_auth_func=code', // do not fill out first and wait for the login error message to fill it out - add it to your allowed domains in your app at https://dev.twitch.tv/console/apps + // 'scope' => 'user:read:email,user:read:follows', +]; diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php index fde25810..ae88e869 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php @@ -8,10 +8,10 @@ trait rex_yform_trait_value_auth_oauth2_google */ private function auth_loadSettings(): array { - $SettingFile = $this->auth_ClassKey.'.php'; + $SettingFile = $this->auth_ClassKey . '.php'; $SettingsPath = rex_addon::get('ycom')->getDataPath($SettingFile); if (!file_exists($SettingsPath)) { - throw new rex_exception($this->auth_ClassKey . '-Settings file not found ['.$SettingsPath.']'); + throw new rex_exception($this->auth_ClassKey . '-Settings file not found [' . $SettingsPath . ']'); } $settings = []; @@ -30,9 +30,9 @@ private function auth_getReturnTo(): string private function auth_FormOutput(string $url): void { if ($this->needsOutput()) { - $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey. '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ + $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey . '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ 'url' => $url, - 'name' => '{{ ' . $this->auth_ClassKey. '_auth }}', + 'name' => '{{ ' . $this->auth_ClassKey . '_auth }}', ]); } } @@ -52,16 +52,14 @@ private function auth_redirectToFailed(string $message = ''): string /** * @param array $Userdata - * @param string $returnTo * @throws rex_exception - * @return void */ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): void { $defaultUserAttributes = []; if ('' != $this->getElement(4)) { if (null == $defaultUserAttributes = json_decode($this->getElement(4), true)) { - throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json'.$this->getElement(4)); + throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json' . $this->getElement(4)); } } diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php index 10cbca49..c7d840d9 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php @@ -8,10 +8,10 @@ trait rex_yform_trait_value_auth_oauth2_twitch */ private function auth_loadSettings(): array { - $SettingFile = $this->auth_ClassKey.'.php'; + $SettingFile = $this->auth_ClassKey . '.php'; $SettingsPath = rex_addon::get('ycom')->getDataPath($SettingFile); if (!file_exists($SettingsPath)) { - throw new rex_exception($this->auth_ClassKey . '-Settings file not found ['.$SettingsPath.']'); + throw new rex_exception($this->auth_ClassKey . '-Settings file not found [' . $SettingsPath . ']'); } $settings = []; @@ -30,9 +30,9 @@ private function auth_getReturnTo(): string private function auth_FormOutput(string $url): void { if ($this->needsOutput()) { - $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey. '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ + $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey . '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ 'url' => $url, - 'name' => '{{ ' . $this->auth_ClassKey. '_auth }}', + 'name' => '{{ ' . $this->auth_ClassKey . '_auth }}', ]); } } @@ -52,16 +52,14 @@ private function auth_redirectToFailed(string $message = ''): string /** * @param array $Userdata - * @param string $returnTo * @throws rex_exception - * @return void */ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): void { $defaultUserAttributes = []; if ('' != $this->getElement(4)) { if (null == $defaultUserAttributes = json_decode($this->getElement(4), true)) { - throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json'.$this->getElement(4)); + throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json' . $this->getElement(4)); } } diff --git a/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php b/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php index 352d95a5..28cd5fe9 100644 --- a/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php +++ b/plugins/auth/lib/yform/value/ycom_auth_oauth2_google.php @@ -19,14 +19,10 @@ class rex_yform_value_ycom_auth_oauth2_google extends rex_yform_value_abstract { use rex_yform_trait_value_auth_oauth2_google; - /** - * @var array|string[] - */ + /** @var array|string[] */ private array $auth_requestFunctions = ['init', 'code', 'state']; private bool $auth_directLink = false; - /** - * @var array|string[] - */ + /** @var array|string[] */ private array $auth_SessionVars = ['OAUTH2_oauth2state']; private string $auth_ClassKey = 'oauth2_google'; diff --git a/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php b/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php index 230b7722..1ae63c81 100644 --- a/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php +++ b/plugins/auth/lib/yform/value/ycom_auth_oauth2_twitch.php @@ -19,14 +19,10 @@ class rex_yform_value_ycom_auth_oauth2_twitch extends rex_yform_value_abstract { use rex_yform_trait_value_auth_oauth2_twitch; - /** - * @var array|string[] - */ + /** @var array|string[] */ private array $auth_requestFunctions = ['init', 'code', 'state']; private bool $auth_directLink = false; - /** - * @var array|string[] - */ + /** @var array|string[] */ private array $auth_SessionVars = ['OAUTH2_oauth2state']; private string $auth_ClassKey = 'oauth2_twitch'; diff --git a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php index 8b64d548..541e687e 100644 --- a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php +++ b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_google.tpl.php @@ -5,7 +5,7 @@ * @psalm-scope-this rex_yform_value_abstract */ -$url = $url ?? ''; -$name = $name ?? ''; +$url ??= ''; +$name ??= ''; -echo '' . $this->name . ''; \ No newline at end of file +echo '' . $this->name . ''; diff --git a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php index 8b64d548..541e687e 100644 --- a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php +++ b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_twitch.tpl.php @@ -5,7 +5,7 @@ * @psalm-scope-this rex_yform_value_abstract */ -$url = $url ?? ''; -$name = $name ?? ''; +$url ??= ''; +$name ??= ''; -echo '' . $this->name . ''; \ No newline at end of file +echo '' . $this->name . ''; From 2d9accb608e65f1fff77863f91e0c9c30af6e0ac Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 23:54:32 +0200 Subject: [PATCH 6/8] rexstan fixes --- plugins/auth/lib/yform/trait_value_auth_oauth2_google.php | 3 +++ plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php index ae88e869..db668ff4 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php @@ -103,6 +103,7 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): // not logged in - check if available $params = [ 'loginName' => $data['email'], + 'loginPassword' => '', 'loginStay' => true, 'filter' => 'status > 0', 'ignorePassword' => true, @@ -137,6 +138,8 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): $params = []; $params['loginName'] = $user->getValue('email'); $params['ignorePassword'] = true; + $params['loginStay'] = false; + $params['filter'] = 'status > 0'; $loginStatus = rex_ycom_auth::login($params); if (2 != $loginStatus) { diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php index c7d840d9..1bf4abff 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php @@ -103,6 +103,7 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): // not logged in - check if available $params = [ 'loginName' => $data['email'], + 'loginPassword' => '', 'loginStay' => true, 'filter' => 'status > 0', 'ignorePassword' => true, @@ -137,6 +138,8 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): $params = []; $params['loginName'] = $user->getValue('email'); $params['ignorePassword'] = true; + $params['loginStay'] = false; + $params['filter'] = 'status > 0'; $loginStatus = rex_ycom_auth::login($params); if (2 != $loginStatus) { From fbf059de01abd519e9e23949343a7293d735943a Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sat, 23 Sep 2023 23:56:35 +0200 Subject: [PATCH 7/8] rexstan fixes --- plugins/auth/lib/yform/trait_value_auth_oauth2_google.php | 1 + plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php index db668ff4..03a8048a 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_google.php @@ -140,6 +140,7 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): $params['ignorePassword'] = true; $params['loginStay'] = false; $params['filter'] = 'status > 0'; + $params['loginPassword'] = ''; $loginStatus = rex_ycom_auth::login($params); if (2 != $loginStatus) { diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php index 1bf4abff..b7772bfc 100644 --- a/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_twitch.php @@ -140,6 +140,7 @@ private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): $params['ignorePassword'] = true; $params['loginStay'] = false; $params['filter'] = 'status > 0'; + $params['loginPassword'] = ''; $loginStatus = rex_ycom_auth::login($params); if (2 != $loginStatus) { From 61c7bec88fa82c1fb9eb165b5a909ae64f4aa590 Mon Sep 17 00:00:00 2001 From: Tim Filler Date: Sun, 24 Sep 2023 00:45:17 +0200 Subject: [PATCH 8/8] Add github via OAuth2 auth as login method - adds value for yform Formbuilder - adds settings file for oauth2_github - add special Provider for github - add documentation --- composer.json | 3 +- composer.lock | 68 ++++++- docs/08_extern_auth.md | 25 +++ plugins/auth/boot.php | 3 + plugins/auth/install.php | 2 +- plugins/auth/install/oauth2_github.php | 7 + .../yform/trait_value_auth_oauth2_github.php | 166 ++++++++++++++++++ .../yform/value/ycom_auth_oauth2_github.php | 133 ++++++++++++++ .../value.ycom_auth_oauth2_github.tpl.php | 11 ++ 9 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 plugins/auth/install/oauth2_github.php create mode 100644 plugins/auth/lib/yform/trait_value_auth_oauth2_github.php create mode 100644 plugins/auth/lib/yform/value/ycom_auth_oauth2_github.php create mode 100644 plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_github.tpl.php diff --git a/composer.json b/composer.json index 2d235cd3..3f86b8b9 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "league/oauth2-client": "^2", "psr/log": "^1", "vertisan/oauth2-twitch-helix": "^2.0", - "league/oauth2-google": "^4.0" + "league/oauth2-google": "^4.0", + "league/oauth2-github": "^3.1" }, "replace": { "psr/container": "*", diff --git a/composer.lock b/composer.lock index d5b3b024..aa33b0b5 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": "1cde028b4d7fa9dedc85a5c1ebc5cba7", + "content-hash": "68acfc6b8f4014c56ff44220b1b07e88", "packages": [ { "name": "apereo/phpcas", @@ -472,6 +472,72 @@ }, "time": "2023-04-16T18:19:15+00:00" }, + { + "name": "league/oauth2-github", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-github.git", + "reference": "97f31cd70e76f81e8f5b4e2ab6f3708e2db7ac18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-github/zipball/97f31cd70e76f81e8f5b4e2ab6f3708e2db7ac18", + "reference": "97f31cd70e76f81e8f5b4e2ab6f3708e2db7ac18", + "shasum": "" + }, + "require": { + "ext-json": "*", + "league/oauth2-client": "^2.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "https://github.com/shadowhand" + } + ], + "description": "Github OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "github", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-github/issues", + "source": "https://github.com/thephpleague/oauth2-github/tree/3.1.0" + }, + "time": "2022-11-04T14:01:49+00:00" + }, { "name": "league/oauth2-google", "version": "4.0.1", diff --git a/docs/08_extern_auth.md b/docs/08_extern_auth.md index 429c0d9e..390bc84f 100644 --- a/docs/08_extern_auth.md +++ b/docs/08_extern_auth.md @@ -132,6 +132,31 @@ ycom_auth_oauth2_google|label|error_msg|[allowed returnTo domains: DomainA,Domai #### GSuite / Google Workspace Nutzer In der Datei `oauth2_google.php` kann die Variable `hostedDomain` mit der Domain des GSuite / Google Workspace Accounts befüllt werden. Damit wird die Anmeldung auf Nutzer mit dieser Domain beschränkt. +## OAuth2 mit GitHub + +Mit der OAuth2 Authentifizierung via GitHub ist es möglich, sich mit einem GitHub-Account in der YCOM zu registrieren und einzuloggen. Dazu muss dieser Provider entsprechend vorbereitet sein. + +### Einrichtung + +Im ersten Schritt muss man eine App anlegen bei GitHub. Hierfür einmal zu https://github.com/settings/apps wechseln. Dort über den Button "New GitHub App" eine neue App erstellen. Anschließend die App bearbeiten und die folgenden Einstellungen vornehmen: + +- Name: Name der App +- Callback-URL: https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_github&rex_ycom_auth_func=code +- Haken setzen bei "Request user authorization (OAuth) during installation" +- Webhook kann ausgemacht werden +- Account Permissions: Email adresses => "Read-only" und Profile=> "Read & write" auswählen + +App speichern und die Client-ID kopieren. +Dann ein neues Secret erzeugen und dieses ebenfalls kopieren / sichern. + +In den Ordner `redaxo/data/addons/ycom/` sollte bereits die Datei `oauth2_github.php` kopiert worden sein. Diese Datei muss nun entsprechend angepasst werden mit den kopierten Daten. + +Damit die Authentifizierung funktioniert, muss im Loginformular von YCOM folgender String (angepasst auf die eigenen Bedürfnisse) eingefügt werden: + +```php +ycom_auth_oauth2_github|label|error_msg|[allowed returnTo domains: DomainA,DomainB]|default Userdata as Json{"ycom_groups": 2, "termsofuse_accepted": 1}|direct_link 0,1 +``` + ## Allgemeines diff --git a/plugins/auth/boot.php b/plugins/auth/boot.php index b4848362..161bdcdc 100644 --- a/plugins/auth/boot.php +++ b/plugins/auth/boot.php @@ -55,6 +55,9 @@ case 'oauth2_google': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_GOOGLE_MATCHING', $data, ['Userdata' => $Userdata])); break; + case 'oauth2_github': + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_OAUTH2_GITHUB_MATCHING', $data, ['Userdata' => $Userdata])); + break; case 'saml': $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_SAML_MATCHING', $data, ['Userdata' => $Userdata])); break; diff --git a/plugins/auth/install.php b/plugins/auth/install.php index 547d96fd..c6c5d91a 100644 --- a/plugins/auth/install.php +++ b/plugins/auth/install.php @@ -46,7 +46,7 @@ rex_sql::factory()->setQuery('UPDATE rex_article SET `ycom_auth_type` = `ycom_auth_type` -1'); } -foreach (['saml', 'oauth2', 'oauth2_twitch', 'oauth2_google', 'cas'] as $settingType) { +foreach (['saml', 'oauth2', 'oauth2_twitch', 'oauth2_google', 'oauth2_github', 'cas'] as $settingType) { $pathFrom = __DIR__ . '/install/' . $settingType . '.php'; $pathTo = rex_addon::get('ycom')->getDataPath($settingType . '.php'); if (!file_exists($pathTo)) { diff --git a/plugins/auth/install/oauth2_github.php b/plugins/auth/install/oauth2_github.php new file mode 100644 index 00000000..73e5883b --- /dev/null +++ b/plugins/auth/install/oauth2_github.php @@ -0,0 +1,7 @@ + 'someJibberish', // Go to https://github.com/settings/apps and setup a Github app. Fill out, add return url, set scopes in Account permissions: Email addresses => Read only, Profile => Read and write + 'clientSecret' => 'moreJibbereish', // In your project at https://github.com/settings/apps, click on your created project and then "Generate a new client secret". + 'redirectUri' => 'https://your-url.com/maybe-a-subpage/?rex_ycom_auth_mode=oauth2_google&rex_ycom_auth_func=code', // do not fill out first and wait for the login error message to fill it out +]; diff --git a/plugins/auth/lib/yform/trait_value_auth_oauth2_github.php b/plugins/auth/lib/yform/trait_value_auth_oauth2_github.php new file mode 100644 index 00000000..3bd1ae2f --- /dev/null +++ b/plugins/auth/lib/yform/trait_value_auth_oauth2_github.php @@ -0,0 +1,166 @@ +auth_ClassKey . '.php'; + $SettingsPath = rex_addon::get('ycom')->getDataPath($SettingFile); + if (!file_exists($SettingsPath)) { + throw new rex_exception($this->auth_ClassKey . '-Settings file not found [' . $SettingsPath . ']'); + } + + $settings = []; + include $SettingsPath; + return $settings; + } + + private function auth_getReturnTo(): string + { + $returnTos = []; + $returnTos[] = rex_request('returnTo', 'string'); // wenn returnTo übergeben wurde, diesen nehmen + $returnTos[] = rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_ok'), '', [], '&'); // Auth Ok -> article_id_jump_ok / Current Language will be selected + return rex_ycom_auth::getReturnTo($returnTos, ('' == $this->getElement(3)) ? [] : explode(',', $this->getElement(3))); + } + + private function auth_FormOutput(string $url): void + { + if ($this->needsOutput()) { + $this->params['form_output'][$this->getId()] = $this->parse(['value.ycom_auth_' . $this->auth_ClassKey . '.tpl.php', 'value.ycom_auth_extern.tpl.php'], [ + 'url' => $url, + 'name' => '{{ ' . $this->auth_ClassKey . '_auth }}', + ]); + } + } + + private function auth_redirectToFailed(string $message = ''): string + { + if ($this->params['debug']) { + dump($message); + return $message; + } + if ($this->auth_directLink) { + rex_response::sendCacheControl(); + rex_response::sendRedirect(rex_getUrl(rex_config::get('ycom/auth', 'article_id_jump_not_ok'))); + } + return ''; + } + + /** + * @param array $Userdata + * @throws rex_exception + */ + private function auth_createOrUpdateYComUser(array $Userdata, string $returnTo): void + { + $defaultUserAttributes = []; + if ('' != $this->getElement(4)) { + if (null == $defaultUserAttributes = json_decode($this->getElement(4), true)) { + throw new rex_exception($this->auth_ClassKey . '-DefaultUserAttributes is not a json' . $this->getElement(4)); + } + } + + $data = []; + $data['email'] = ''; + foreach (['User.email', 'emailAddress', 'email'] as $Key) { + if (isset($Userdata[$Key])) { + $data['email'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['firstname'] = ''; + foreach (['login'] as $Key) { + if (isset($Userdata[$Key])) { + $data['firstname'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['name'] = ''; + foreach (['name'] as $Key) { + if (isset($Userdata[$Key])) { + $data['name'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + $data['user_image'] = ''; + foreach (['avatar_url'] as $Key) { + if (isset($Userdata[$Key])) { + $data['user_image'] = is_array($Userdata[$Key]) ? implode(' ', $Userdata[$Key]) : $Userdata[$Key]; + } + } + + foreach ($defaultUserAttributes as $defaultUserAttributeKey => $defaultUserAttributeValue) { + $data[$defaultUserAttributeKey] = $defaultUserAttributeValue; + } + + $data = rex_extension::registerPoint(new rex_extension_point('YCOM_AUTH_MATCHING', $data, ['Userdata' => $Userdata, 'AuthType' => $this->auth_ClassKey])); + + self::auth_clearUserSession(); + + // not logged in - check if available + $params = [ + 'loginName' => $data['email'], + 'loginPassword' => '', + 'loginStay' => true, + 'filter' => 'status > 0', + 'ignorePassword' => true, + ]; + + $loginStatus = rex_ycom_auth::login($params); + if (2 == $loginStatus) { + // already logged in + rex_ycom_user::updateUser($data); + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + // if user not found, check if exists, but no permission + $user = rex_ycom_user::query()->where('email', $data['email'])->findOne(); + if ($user) { + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_failed }}'; + return; + } + + $user = rex_ycom_user::createUserByEmail($data); + if (!$user || count($user->getMessages()) > 0) { + if ($user && $this->params['debug']) { + dump($user->getMessages()); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_create_user }}'; + return; + } + + $params = []; + $params['loginName'] = $user->getValue('email'); + $params['ignorePassword'] = true; + $params['loginStay'] = false; + $params['filter'] = 'status > 0'; + $params['loginPassword'] = ''; + $loginStatus = rex_ycom_auth::login($params); + + if (2 != $loginStatus) { + if ($this->params['debug']) { + dump($loginStatus); + dump($user); + } + $this->auth_redirectToFailed('{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(2)) ? $this->getElement(2) : '{{ ' . $this->auth_ClassKey . '.error.ycom_login_created_user }}'; + return; + } + + rex_response::sendCacheControl(); + rex_response::sendRedirect($returnTo); + } + + private function auth_clearUserSession(): void + { + foreach ($this->auth_SessionVars as $SessionKey) { + rex_ycom_auth::unsetSessionVar($SessionKey); + } + } +} diff --git a/plugins/auth/lib/yform/value/ycom_auth_oauth2_github.php b/plugins/auth/lib/yform/value/ycom_auth_oauth2_github.php new file mode 100644 index 00000000..4ce78b3e --- /dev/null +++ b/plugins/auth/lib/yform/value/ycom_auth_oauth2_github.php @@ -0,0 +1,133 @@ +www.yakamara.de + */ + +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Provider\Github; + +class rex_yform_value_ycom_auth_oauth2_github extends rex_yform_value_abstract +{ + use rex_yform_trait_value_auth_oauth2_github; + + /** @var array|string[] */ + private array $auth_requestFunctions = ['init', 'code', 'state']; + private bool $auth_directLink = false; + /** @var array|string[] */ + private array $auth_SessionVars = ['OAUTH2_oauth2state']; + private string $auth_ClassKey = 'oauth2_github'; + + public function enterObject(): void + { + if (rex::isFrontend()) { + $this->auth_directLink = 1 == $this->getElement(5) ? true : false; + } + + rex_login::startSession(); + + $settings = $this->auth_loadSettings(); + $returnTo = $this->auth_getReturnTo(); + $this->auth_FormOutput(rex_getUrl('', '', ['rex_ycom_auth_mode' => 'oauth2_github', 'rex_ycom_auth_func' => 'init', 'returnTo' => $returnTo])); + + $requestMode = rex_request('rex_ycom_auth_mode', 'string', ''); + $requestFunction = rex_request('rex_ycom_auth_func', 'string', ''); + if (!in_array($requestFunction, $this->auth_requestFunctions, true) || $this->auth_ClassKey != $requestMode) { + if ($this->auth_directLink) { + $requestFunction = 'init'; + } else { + return; + } + } + + if ('' == $settings['redirectUri']) { + echo 'use this URL for redirect'; + dump(rex_yrewrite::getFullUrlByArticleId(rex_article::getCurrentId(), '', ['rex_ycom_auth_mode' => 'oauth2_github', 'rex_ycom_auth_func' => 'code'], '&')); + return; + } + + $provider = new Github($settings); + + $Userdata = []; + switch ($requestFunction) { + case 'code': + $code = rex_request('code', 'string'); + if ('' != $code) { + if ('' == rex_ycom_auth::getSessionVar('OAUTH2_oauth2state') || empty($_GET['state']) || $_GET['state'] != rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')) { + if ($this->params['debug']) { + echo 'OAuth session saved state != OAuth State'; + dump(rex_ycom_auth::getSessionVar('OAUTH2_oauth2state')); + dump($_GET['state']); + } + $this->auth_redirectToFailed('{{ oauth.error.state_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.state_code }}'; + return; + } + + $accessToken = null; + try { + /** @var \League\OAuth2\Client\Token\AccessToken $accessToken */ + $accessToken = $provider->getAccessToken('authorization_code', [ + 'code' => $code, + ]); + + if ($accessToken->hasExpired()) { + $this->auth_redirectToFailed('{{ oauth.error.access_expired }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.access_expired }}'; + return; + } + $resourceOwner = $provider->getResourceOwner($accessToken); + $Userdata = $resourceOwner->toArray(); + // print_r($Userdata); + // exit; + $returnTo = rex_ycom_auth::getSessionVar('OAUTH2_oauth2returnTo'); + rex_ycom_auth::unsetSessionVar('OAUTH2_oauth2returnTo'); + } catch (IdentityProviderException $e) { + if ($this->params['debug']) { + dump($e); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } catch (Exception $e) { + if ($this->params['debug']) { + echo 'OAuth Error'; + dump($accessToken); + dump($e); + dump($e->getMessage()); + } + $this->auth_redirectToFailed('{{ oauth.error.code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.code }}'; + return; + } + } else { + $this->auth_redirectToFailed('{{ oauth.error.no_code }}'); + $this->params['warning_messages'][] = ('' != $this->getElement(3)) ? $this->getElement(3) : '{{ oauth.error.no_code }}'; + return; + } + break; + + case 'init': + $authorizationUrl = $provider->getAuthorizationUrl(); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2state', $provider->getState()); + rex_ycom_auth::setSessionVar('OAUTH2_oauth2returnTo', $returnTo); + rex_response::sendCacheControl(); + rex_response::sendRedirect($authorizationUrl); + } + + $this->auth_createOrUpdateYComUser($Userdata, $returnTo); + } + + public function getDescription(): string + { + return 'ycom_auth_oauth2_github|label|error_msg|[allowed returnTo domains: DomainA,DomainB]|default Userdata as Json{"ycom_groups": 3, "termsofuse_accepted": 1}|direct_link 0,1'; + } +} diff --git a/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_github.tpl.php b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_github.tpl.php new file mode 100644 index 00000000..541e687e --- /dev/null +++ b/plugins/auth/ytemplates/bootstrap/value.ycom_auth_oauth2_github.tpl.php @@ -0,0 +1,11 @@ +name . '" href="' . $url . '">' . $this->name . '';