[ ['onPluginsInitialized', 0] ], ]; } /** * [onPluginsInitialized:100000] Composer autoload. * * @return ClassLoader */ public function autoload(): ClassLoader { return require __DIR__ . '/vendor/autoload.php'; } public function onTwigLoader(): void { $media_paths = $this->grav['locator']->findResources('plugins://login-oauth2/media'); foreach(array_reverse($media_paths) as $images_path) { $this->grav['twig']->addPath($images_path, 'oauth2-media'); } } /** * [onTwigTemplatePaths] Add twig paths to plugin templates. */ public function onTwigTemplatePaths(): void { $twig = $this->grav['twig']; $twig->twig_paths[] = __DIR__ . '/templates'; } public function onTwigSiteVariables(): void { // add CSS for frontend if required if ((!$this->isAdmin() && $this->config->get('plugins.login-oauth2.built_in_css')) || ($this->admin && $this->config->get('plugins.login-oauth2.admin.built_in_css'))) { $this->grav['assets']->add('plugin://login-oauth2/css/login-oauth2.css'); } } /** * Initialize the plugin */ public function onPluginsInitialized(): void { if ($this->isAdmin()) { if (!$this->grav['config']->get('plugins.login-oauth2.admin.enabled')) { // Don't proceed if we are in the admin plugin return; } $this->admin = true; } $this->enable([ 'onTask.login.oauth2' => ['loginRedirect', 0], 'onTask.callback.oauth2' => ['loginCallback', 0], 'onTask.delete.oauth2' => ['loginDataDelete', 0], 'onTwigLoader' => ['onTwigLoader', 0], 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 'onTwigSiteVariables' => ['onTwigSiteVariables', 0], 'onLoginPage' => ['onLoginPage', 10], 'onUserLoginAuthenticate' => ['userLoginAuthenticate', 1000], 'onUserLoginFailure' => ['userLoginFailure', 0], 'onUserLogin' => ['userLogin', 0], 'onUserLogout' => ['userLogout', 0], 'onOAuth2Username' => ['onOAuth2Username', 0], ] ); // Check to ensure login plugin is enabled. if (!$this->config->get('plugins.login.enabled')) { throw new RuntimeException('The Login plugin needs to be installed and enabled'); } $this->debug = $this->config->get('plugins.login-oauth2.debug', false); $isAdmin = $this->admin; $this->grav['oauth2'] = static function () use ($isAdmin) { // Add OAuth2 object to Grav $oauth2 = new OAuth2($isAdmin); $oauth2->addEnabledProviders(); return $oauth2; }; } /** * Add navigation item to the admin plugin */ public function onLoginPage(): void { if ($this->grav['oauth2']->getProviders()) { $this->grav['login']->addProviderLoginTemplate('login-oauth2/login-oauth2.html.twig'); } } /** * Task: login.oauth2 */ public function loginRedirect(): void { /** @var OAuth2 $oauth2 */ $oauth2 = $this->grav['oauth2']; $user = $this->grav['user'] ?? null; if ($user && $user->authorized) { throw new RuntimeException('You have already been logged in', 403); } $provider_name = isset($_POST['oauth2']) ? htmlspecialchars(strip_tags($_POST['oauth2']), ENT_QUOTES, 'UTF-8') : null; if (!isset($provider_name)) { throw new RuntimeException('Bad Request', 400); } if ($oauth2->isValidProvider($provider_name)) { $provider = ProviderFactory::create($provider_name, $oauth2->getProviderOptions($provider_name)); /** @var Session $session */ $session = $this->grav['session']; $session->oauth2_state = $provider->getState(); $session->oauth2_provider = $provider_name; if ($this->isAdmin()) { $redirect = (string)$this->grav['admin']->request->getUri(); } else { if ($this->config->get('plugins.login.redirect_after_login')) { $redirect = (string) $this->config->get('plugins.login.route_after_login'); } else { /** @var Uri $uri */ $request = $this->grav['request']; $redirect = (string) $request->getUri(); } } $session->redirect_after_login = $redirect; $authorizationUrl = $provider->getAuthorizationUrl(); $this->grav->redirect($authorizationUrl); } } /** * Task: callback.oauth2 */ public function loginCallback(): void { /** @var Login $login */ $login = $this->grav['login']; /** @var OAuth2 $oauth2 */ $oauth2 = $this->grav['oauth2']; /** @var Session $session */ $session = $this->grav['session']; $this->debug("session: " . json_encode($session->getAll())); $provider_name = $session->oauth2_provider; $login_redirect = $session->redirect_after_login; /** @var Language $t */ $t = $this->grav['language']; /** @var Message $messages */ $messages = $this->grav['messages']; $is_valid = $oauth2->isValidProvider($provider_name); $this->debug("provider: $provider_name - redirect: $login_redirect - is_valid: $is_valid"); if ($provider_name && $oauth2->isValidProvider($provider_name)) { $state = isset($_GET['state']) ? htmlspecialchars(strip_tags($_GET['state']), ENT_QUOTES, 'UTF-8') : null; if (empty($state)) { $state = isset($_POST['state']) ? htmlspecialchars(strip_tags($_POST['state']), ENT_QUOTES, 'UTF-8') : null; } $this->debug("sent state: $state, stored state: $session->oauth2_state"); if (empty($state) || ($state !== $session->oauth2_state)) { unset($session->oauth2_state); $this->debug("Error: $session->oauth2_state != $state"); $messages->add($t->translate('PLUGIN_LOGIN.LOGIN_FAILED'), 'error'); } else { // Fire Login process. $event = $login->login( ['rememberme' => true], ['admin' => $this->isAdmin(), 'remember_me' => true, 'oauth2' => true, 'provider' => $provider_name], ['authorize' => $this->isAdmin() ? 'admin.login' : 'site.login', 'return_event' => true]); // Note: session variables have been reset! $user = $event->getUser(); if ($user->authorized) { $event->defMessage('PLUGIN_LOGIN.LOGIN_SUCCESSFUL', 'info'); if ($this->isAdmin()) { $event->defRedirect($login_redirect ?? '/'); } else { $event->defRedirect( $login_redirect ?: LoginPlugin::defaultRedirectAfterLogin() ?: $this->grav['uri']->referrer('/') ); } } elseif ($user->authenticated) { $event->defMessage('PLUGIN_LOGIN.ACCESS_DENIED', 'error'); if ($this->isAdmin()) { $event->defRedirect($login_redirect ?? '/'); } else { $event->defRedirect($this->grav['config']->get('plugins.login.route_unauthorized', '/')); } } else { $event->defMessage('PLUGIN_LOGIN.LOGIN_FAILED', 'error'); if ($this->isAdmin()) { $event->defRedirect($login_redirect ?? '/'); } else { $event->defRedirect($this->grav['config']->get('plugins.login.route', '/')); } } $message = $event->getMessage(); if ($message) { /** @var Debugger $debugger */ $debugger = $this->grav['debugger']; $debugger->addMessage($t->translate($message), 'debug'); $messages->add($t->translate($message), $event->getMessageType()); } $redirect = $event->getRedirect(); if ($redirect) { $this->grav->redirect($redirect, $event->getRedirectCode()); } } } else { $this->grav->redirect($login_redirect ?? '/'); } $uri = $this->grav['uri']; $redirect = $uri->url(true); $this->grav->redirect($redirect); } function loginDataDelete() { /** @var Login $login */ $login = $this->grav['login']; /** @var OAuth2 $oauth2 */ $oauth2 = $this->grav['oauth2']; /** @var Session $session */ $session = $this->grav['session']; $this->debug("session: " . json_encode($session->getAll())); $provider_name = $session->oauth2_provider; $login_redirect = $session->redirect_after_login; /** @var Language $t */ $t = $this->grav['language']; /** @var Message $messages */ $messages = $this->grav['messages']; $is_valid = $oauth2->isValidProvider($provider_name); $this->debug("provider: $provider_name - redirect: $login_redirect - is_valid: $is_valid"); } public function userLoginAuthenticate(UserLoginEvent $event): void { // Second parameter of Login::login() call. $options = $event->getOptions(); if (isset($options['oauth2'])) { $code = isset($_GET['code']) ? htmlspecialchars(strip_tags($_GET['code']), ENT_QUOTES, 'UTF-8') : null; if (!$code) { $code = isset($_POST['code']) ? htmlspecialchars(strip_tags($_POST['code']), ENT_QUOTES, 'UTF-8') : null; } $provider_name = $options['provider']; $provider = ProviderFactory::create($provider_name, $options); try { // Try to get an access token (using the authorization code grant) $token = $provider->getAccessToken('authorization_code', ['code' => $code]); // We got an access token, let's now get the user's details $user = $provider->getResourceOwner($token); $userdata = $provider->getUserData($user); $userdata_event = new Event( [ 'userdata' => $userdata, 'oauth2user' => $user, 'provider' => $provider, 'token' => $token ] ); $this->grav->fireEvent('onOAuth2Userdata', $userdata_event); // Set again with any event-based modifications $userdata = $userdata_event['userdata']; $username_event = new Event( [ 'userdata' => $userdata, 'oauth2user' => $user, 'provider' => $provider, 'token' => $token ] ); // Get username from an event to allow you to modify oauth2 filename $this->grav->fireEvent('onOAuth2Username', $username_event); $username = $username_event['username']; /** @var UserCollectionInterface $accounts */ $accounts = $this->grav['accounts']; $grav_user = $accounts->load($username); // If username cannot be found, fall back to email address. $exists = $grav_user->exists(); if (!$exists) { $found_user = $accounts->find($userdata['email'], ['email']); if ($found_user->exists()) { $grav_user = $found_user; $exists = true; } } // Make sure we're using the same provider, multiple providers are not supported. if ($exists) { $provider_test = $grav_user->get('provider'); if ($provider_test && $provider_test !== $provider_name) { throw new RuntimeException($this->translate('PLUGIN_LOGIN_OAUTH2.ERROR_EXISTING_ACCOUNT', $provider_test)); } } if ($this->config->get('plugins.login-oauth2.require_grav_user', false) && !$exists) { throw new RuntimeException($this->translate('PLUGIN_LOGIN_OAUTH2.ERROR_NO_ACCOUNT', $username)); } // Add token to user $grav_user->set('token', json_encode($token, JSON_THROW_ON_ERROR)); // Set provider $grav_user->set('provider', $provider_name); // Default Access levels $current_access = $grav_user->get('access'); if (!$current_access) { $access = $this->config->get('plugins.login-oauth2.default_access_levels.access', []); if (count($access) > 0) { $grav_user->set('access', $access); } } // Default Groups $current_groups = $grav_user->get('groups'); if (!$current_groups) { $groups = $this->config->get('plugins.login-oauth2.default_groups', []); if (count($groups) > 0) { $grav_user->set('groups', $groups); } } // Remove Provider Userdata if configured if (!$this->config->get('plugins.login-oauth2.store_provider_data', false)) { unset($userdata[$provider_name]); } $grav_user->merge($userdata); $this->debug("userdata: " . json_encode($userdata)); // Save Grav user if so configured if ($this->config->get('plugins.login-oauth2.save_grav_user', false)) { $grav_user->save(); } $event->setUser($grav_user); // Do something... $event->setStatus($event::AUTHENTICATION_SUCCESS); $event->stopPropagation(); } catch (Exception $e) { $event->setMessage($this->translate('PLUGIN_LOGIN_OAUTH2.OAUTH2_LOGIN_FAILED', ucfirst($provider_name), $e->getMessage()), 'error'); $event->setStatus($event::AUTHENTICATION_FAILURE); } } } public function onOAuth2Username(Event $event): void { $userdata = $event['userdata']; $provider = $event['provider']; $provider_name = strtolower($provider->getName()); $username_parts = [$provider_name, $userdata['id'], $userdata['login']]; $username = implode('.', $username_parts); $event['username'] = $username; $event->stopPropagation(); } public function userLoginFailure(UserLoginEvent $event): void { // This gets fired if user fails to log in. } public function userLogin(UserLoginEvent $event): void { } public function userLogout(UserLoginEvent $event): void { // This gets fired on user logout. } /** * @param mixed ...$args * @return string */ private function translate(...$args): string { /** @var Language $language */ $language = $this->grav['language']; return $language->translate($args); } private function debug($message): void { if ($this->debug) { $this->grav['log']->debug($message); } } }