104a78b87SAndreas Gohr<?php 204a78b87SAndreas Gohr 304a78b87SAndreas Gohrnamespace dokuwiki\plugin\oauth; 404a78b87SAndreas Gohr 504a78b87SAndreas Gohruse dokuwiki\Extension\ActionPlugin; 604a78b87SAndreas Gohruse OAuth\Common\Consumer\Credentials; 704a78b87SAndreas Gohruse OAuth\Common\Http\Exception\TokenResponseException; 804a78b87SAndreas Gohruse OAuth\OAuth1\Service\AbstractService as Abstract1Service; 904a78b87SAndreas Gohruse OAuth\OAuth1\Token\TokenInterface; 1004a78b87SAndreas Gohruse OAuth\OAuth2\Service\AbstractService as Abstract2Service; 1104a78b87SAndreas Gohruse OAuth\OAuth2\Service\Exception\InvalidAuthorizationStateException; 1204a78b87SAndreas Gohruse OAuth\ServiceFactory; 1304a78b87SAndreas Gohr 1404a78b87SAndreas Gohr/** 1504a78b87SAndreas Gohr * Base class to implement a Backend Service for the oAuth Plugin 1604a78b87SAndreas Gohr */ 1704a78b87SAndreas Gohrabstract class Adapter extends ActionPlugin 1804a78b87SAndreas Gohr{ 1904a78b87SAndreas Gohr /** 2004a78b87SAndreas Gohr * @var Abstract2Service|Abstract1Service 2104a78b87SAndreas Gohr * @see getOAuthService() use this to ensure it's intialized 2204a78b87SAndreas Gohr */ 2304a78b87SAndreas Gohr protected $oAuth; 2404a78b87SAndreas Gohr 2504a78b87SAndreas Gohr // region internal methods 2604a78b87SAndreas Gohr 2704a78b87SAndreas Gohr /** 2804a78b87SAndreas Gohr * Auto register this plugin with the oAuth authentication plugin 2904a78b87SAndreas Gohr * 3004a78b87SAndreas Gohr * @inheritDoc 3104a78b87SAndreas Gohr */ 3204a78b87SAndreas Gohr public function register(\Doku_Event_Handler $controller) 3304a78b87SAndreas Gohr { 3404a78b87SAndreas Gohr $controller->register_hook('PLUGIN_OAUTH_BACKEND_REGISTER', 'AFTER', $this, 'handleRegister'); 3504a78b87SAndreas Gohr } 3604a78b87SAndreas Gohr 3704a78b87SAndreas Gohr /** 3804a78b87SAndreas Gohr * Auto register this plugin with the oAuth authentication plugin 3904a78b87SAndreas Gohr */ 4004a78b87SAndreas Gohr public function handleRegister(\Doku_Event $event, $param) 4104a78b87SAndreas Gohr { 4204a78b87SAndreas Gohr $event->data[$this->getServiceID()] = $this; 4304a78b87SAndreas Gohr } 4404a78b87SAndreas Gohr 4504a78b87SAndreas Gohr /** 4604a78b87SAndreas Gohr * Initialize the oAuth service 4704a78b87SAndreas Gohr * 4804a78b87SAndreas Gohr * @param string $guid UIID for the user to authenticate 4904a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 5004a78b87SAndreas Gohr */ 5104a78b87SAndreas Gohr public function initOAuthService($guid) 5204a78b87SAndreas Gohr { 5304a78b87SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 5404a78b87SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 5504a78b87SAndreas Gohr 5604a78b87SAndreas Gohr $credentials = new Credentials( 5704a78b87SAndreas Gohr $this->getKey(), 5804a78b87SAndreas Gohr $this->getSecret(), 5904a78b87SAndreas Gohr $hlp->redirectURI() 6004a78b87SAndreas Gohr ); 6104a78b87SAndreas Gohr 6204a78b87SAndreas Gohr $serviceFactory = new ServiceFactory(); 6304a78b87SAndreas Gohr $serviceFactory->setHttpClient(new HTTPClient()); 6404a78b87SAndreas Gohr $servicename = $this->getServiceID(); 6504a78b87SAndreas Gohr $serviceclass = $this->registerServiceClass(); 6604a78b87SAndreas Gohr if ($serviceclass) { 6704a78b87SAndreas Gohr $serviceFactory->registerService($servicename, $serviceclass); 6804a78b87SAndreas Gohr } 6904a78b87SAndreas Gohr 7004a78b87SAndreas Gohr $this->oAuth = $serviceFactory->createService( 7104a78b87SAndreas Gohr $servicename, 7204a78b87SAndreas Gohr $credentials, 7304a78b87SAndreas Gohr new Storage($guid), 7404a78b87SAndreas Gohr $this->getScopes() 7504a78b87SAndreas Gohr ); 7604a78b87SAndreas Gohr 7704a78b87SAndreas Gohr if ($this->oAuth === null) { 7804a78b87SAndreas Gohr throw new Exception('Failed to initialize Service ' . $this->getLabel()); 7904a78b87SAndreas Gohr } 8004a78b87SAndreas Gohr } 8104a78b87SAndreas Gohr 8204a78b87SAndreas Gohr /** 8304a78b87SAndreas Gohr * @return Abstract2Service|Abstract1Service 8404a78b87SAndreas Gohr * @throws Exception 8504a78b87SAndreas Gohr */ 8604a78b87SAndreas Gohr public function getOAuthService() 8704a78b87SAndreas Gohr { 8804a78b87SAndreas Gohr if ($this->oAuth === null) throw new Exception('OAuth Service not properly initialized'); 8904a78b87SAndreas Gohr return $this->oAuth; 9004a78b87SAndreas Gohr } 9104a78b87SAndreas Gohr 9204a78b87SAndreas Gohr /** 9304a78b87SAndreas Gohr * Redirects to the service for requesting access 9404a78b87SAndreas Gohr * 9504a78b87SAndreas Gohr * This is the first step of oAuth authentication 9604a78b87SAndreas Gohr * 9704a78b87SAndreas Gohr * This implementation tries to abstract away differences between oAuth1 and oAuth2, 9804a78b87SAndreas Gohr * but might need to be overwritten for specific services 9904a78b87SAndreas Gohr * 10004a78b87SAndreas Gohr * @throws TokenResponseException 10104a78b87SAndreas Gohr * @throws Exception 10204a78b87SAndreas Gohr */ 10304a78b87SAndreas Gohr public function login() 10404a78b87SAndreas Gohr { 10504a78b87SAndreas Gohr $oauth = $this->getOAuthService(); 10604a78b87SAndreas Gohr 10704a78b87SAndreas Gohr // store Farmer animal in oAuth state parameter 10804a78b87SAndreas Gohr /** @var \helper_plugin_farmer $farmer */ 10904a78b87SAndreas Gohr $farmer = plugin_load('helper', 'farmer'); 11004a78b87SAndreas Gohr $parameters = []; 11104a78b87SAndreas Gohr if ($farmer && $animal = $farmer->getAnimal()) { 11204a78b87SAndreas Gohr $parameters['state'] = urlencode(base64_encode(json_encode( 11304a78b87SAndreas Gohr [ 11404a78b87SAndreas Gohr 'animal' => $animal, 11504a78b87SAndreas Gohr 'state' => md5(rand()), 11604a78b87SAndreas Gohr ] 11704a78b87SAndreas Gohr ))); 11804a78b87SAndreas Gohr $oauth->getStorage()->storeAuthorizationState($oauth->service(), $parameters['state']); 11904a78b87SAndreas Gohr } 12004a78b87SAndreas Gohr 12104a78b87SAndreas Gohr if (is_a($oauth, Abstract1Service::class)) { /* oAuth1 handling */ 12204a78b87SAndreas Gohr // extra request needed for oauth1 to request a request token 12304a78b87SAndreas Gohr $token = $oauth->requestRequestToken(); 12404a78b87SAndreas Gohr $parameters['oauth_token'] = $token->getRequestToken(); 12504a78b87SAndreas Gohr } 12604a78b87SAndreas Gohr $url = $oauth->getAuthorizationUri($parameters); 12704a78b87SAndreas Gohr 12804a78b87SAndreas Gohr send_redirect($url); 12904a78b87SAndreas Gohr } 13004a78b87SAndreas Gohr 13104a78b87SAndreas Gohr /** 13204a78b87SAndreas Gohr * Request access token 13304a78b87SAndreas Gohr * 13404a78b87SAndreas Gohr * This is the second step of oAuth authentication 13504a78b87SAndreas Gohr * 13604a78b87SAndreas Gohr * This implementation tries to abstract away differences between oAuth1 and oAuth2, 13704a78b87SAndreas Gohr * but might need to be overwritten for specific services 13804a78b87SAndreas Gohr * 13904a78b87SAndreas Gohr * Thrown exceptions indicate a non-successful login because of some error, appropriate messages 14004a78b87SAndreas Gohr * should be shown to the user. A return of false with no exceptions indicates that there was no 14104a78b87SAndreas Gohr * oauth data at all. This can probably be silently ignored. 14204a78b87SAndreas Gohr * 14304a78b87SAndreas Gohr * @return bool true if authentication was successful 14404a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 14504a78b87SAndreas Gohr * @throws InvalidAuthorizationStateException 14604a78b87SAndreas Gohr */ 14704a78b87SAndreas Gohr public function checkToken() 14804a78b87SAndreas Gohr { 14904a78b87SAndreas Gohr global $INPUT; 15004a78b87SAndreas Gohr 15104a78b87SAndreas Gohr $oauth = $this->getOAuthService(); 15204a78b87SAndreas Gohr 15304a78b87SAndreas Gohr if (is_a($oauth, Abstract2Service::class)) { 15404a78b87SAndreas Gohr /** @var Abstract2Service $oauth */ 15504a78b87SAndreas Gohr if (!$INPUT->get->has('code')) return false; 15604a78b87SAndreas Gohr $state = $INPUT->get->str('state', null); 157*eae50416SAndreas Gohr $accessToken = $oauth->requestAccessToken($INPUT->get->str('code'), $state); 15804a78b87SAndreas Gohr } else { 15904a78b87SAndreas Gohr /** @var Abstract1Service $oauth */ 16004a78b87SAndreas Gohr if (!$INPUT->get->has('oauth_token')) return false; 16104a78b87SAndreas Gohr /** @var TokenInterface $token */ 16204a78b87SAndreas Gohr $token = $oauth->getStorage()->retrieveAccessToken($this->getServiceID()); 163*eae50416SAndreas Gohr $accessToken = $oauth->requestAccessToken( 16404a78b87SAndreas Gohr $INPUT->get->str('oauth_token'), 16504a78b87SAndreas Gohr $INPUT->get->str('oauth_verifier'), 16604a78b87SAndreas Gohr $token->getRequestTokenSecret() 16704a78b87SAndreas Gohr ); 16804a78b87SAndreas Gohr } 169*eae50416SAndreas Gohr 170*eae50416SAndreas Gohr if ( 171*eae50416SAndreas Gohr $accessToken->getEndOfLife() !== $accessToken::EOL_NEVER_EXPIRES && 172*eae50416SAndreas Gohr !$accessToken->getRefreshToken()) { 173*eae50416SAndreas Gohr msg('Service did not provide a Refresh Token. You will be logged out when the session expires.'); 174*eae50416SAndreas Gohr } 175*eae50416SAndreas Gohr 17604a78b87SAndreas Gohr return true; 17704a78b87SAndreas Gohr } 17804a78b87SAndreas Gohr 17904a78b87SAndreas Gohr /** 18004a78b87SAndreas Gohr * Return the Service Login Button 18104a78b87SAndreas Gohr * 18204a78b87SAndreas Gohr * @return string 18304a78b87SAndreas Gohr */ 18404a78b87SAndreas Gohr public function loginButton() 18504a78b87SAndreas Gohr { 18604a78b87SAndreas Gohr global $ID; 18704a78b87SAndreas Gohr 18804a78b87SAndreas Gohr $attr = buildAttributes([ 18904a78b87SAndreas Gohr 'href' => wl($ID, array('oauthlogin' => $this->getServiceID()), false, '&'), 19004a78b87SAndreas Gohr 'class' => 'plugin_oauth_' . $this->getServiceID(), 19104a78b87SAndreas Gohr 'style' => 'background-color: ' . $this->getColor(), 19204a78b87SAndreas Gohr ]); 19304a78b87SAndreas Gohr 19404a78b87SAndreas Gohr return '<a ' . $attr . '>' . $this->getSvgLogo() . '<span>' . $this->getLabel() . '</span></a> '; 19504a78b87SAndreas Gohr } 19604a78b87SAndreas Gohr // endregion 19704a78b87SAndreas Gohr 19804a78b87SAndreas Gohr // region overridable methods 19904a78b87SAndreas Gohr 20004a78b87SAndreas Gohr /** 20104a78b87SAndreas Gohr * Retrieve the user's data via API 20204a78b87SAndreas Gohr * 20304a78b87SAndreas Gohr * The returned array needs to contain at least 'email', 'name', 'user' and optionally 'grps' 20404a78b87SAndreas Gohr * 20504a78b87SAndreas Gohr * Use the request() method of the oauth object to talk to the API 20604a78b87SAndreas Gohr * 20704a78b87SAndreas Gohr * @return array 20804a78b87SAndreas Gohr * @throws Exception 20904a78b87SAndreas Gohr * @see getOAuthService() 21004a78b87SAndreas Gohr */ 21104a78b87SAndreas Gohr abstract public function getUser(); 21204a78b87SAndreas Gohr 21304a78b87SAndreas Gohr /** 21404a78b87SAndreas Gohr * Return the scopes to request 21504a78b87SAndreas Gohr * 21604a78b87SAndreas Gohr * This should return the minimal scopes needed for accessing the user's data 21704a78b87SAndreas Gohr * 21804a78b87SAndreas Gohr * @return string[] 21904a78b87SAndreas Gohr */ 22004a78b87SAndreas Gohr public function getScopes() 22104a78b87SAndreas Gohr { 22204a78b87SAndreas Gohr return []; 22304a78b87SAndreas Gohr } 22404a78b87SAndreas Gohr 22504a78b87SAndreas Gohr /** 22604a78b87SAndreas Gohr * Return the user friendly name of the service 22704a78b87SAndreas Gohr * 22804a78b87SAndreas Gohr * Defaults to ServiceID. You may want to override this. 22904a78b87SAndreas Gohr * 23004a78b87SAndreas Gohr * @return string 23104a78b87SAndreas Gohr */ 23204a78b87SAndreas Gohr public function getLabel() 23304a78b87SAndreas Gohr { 23404a78b87SAndreas Gohr return ucfirst($this->getServiceID()); 23504a78b87SAndreas Gohr } 23604a78b87SAndreas Gohr 23704a78b87SAndreas Gohr /** 23804a78b87SAndreas Gohr * Return the internal name of the Service 23904a78b87SAndreas Gohr * 24004a78b87SAndreas Gohr * Defaults to the plugin name (without oauth prefix). This has to match the Service class name in 24104a78b87SAndreas Gohr * the appropriate lusitantian oauth Service namespace 24204a78b87SAndreas Gohr * 24304a78b87SAndreas Gohr * @return string 24404a78b87SAndreas Gohr */ 24504a78b87SAndreas Gohr public function getServiceID() 24604a78b87SAndreas Gohr { 24704a78b87SAndreas Gohr $name = $this->getPluginName(); 24804a78b87SAndreas Gohr if (substr($name, 0, 5) === 'oauth') { 24904a78b87SAndreas Gohr $name = substr($name, 5); 25004a78b87SAndreas Gohr } 25104a78b87SAndreas Gohr 25204a78b87SAndreas Gohr return $name; 25304a78b87SAndreas Gohr } 25404a78b87SAndreas Gohr 25504a78b87SAndreas Gohr /** 25604a78b87SAndreas Gohr * Register a new Service 25704a78b87SAndreas Gohr * 25804a78b87SAndreas Gohr * @return string A fully qualified class name to register as new Service for your ServiceID 25904a78b87SAndreas Gohr */ 26004a78b87SAndreas Gohr public function registerServiceClass() 26104a78b87SAndreas Gohr { 26204a78b87SAndreas Gohr return null; 26304a78b87SAndreas Gohr } 26404a78b87SAndreas Gohr 26504a78b87SAndreas Gohr /** 26604a78b87SAndreas Gohr * Return the button color to use 26704a78b87SAndreas Gohr * 26804a78b87SAndreas Gohr * @return string 26904a78b87SAndreas Gohr */ 27004a78b87SAndreas Gohr public function getColor() 27104a78b87SAndreas Gohr { 27204a78b87SAndreas Gohr return '#999'; 27304a78b87SAndreas Gohr } 27404a78b87SAndreas Gohr 27504a78b87SAndreas Gohr /** 27604a78b87SAndreas Gohr * Return the SVG of the logo for this service 27704a78b87SAndreas Gohr * 27804a78b87SAndreas Gohr * Defaults to a logo.svg in the plugin directory 27904a78b87SAndreas Gohr * 28004a78b87SAndreas Gohr * @return string 28104a78b87SAndreas Gohr */ 28204a78b87SAndreas Gohr public function getSvgLogo() 28304a78b87SAndreas Gohr { 28404a78b87SAndreas Gohr $logo = DOKU_PLUGIN . $this->getPluginName() . '/logo.svg'; 28504a78b87SAndreas Gohr if (file_exists($logo)) return inlineSVG($logo); 28604a78b87SAndreas Gohr return ''; 28704a78b87SAndreas Gohr } 28804a78b87SAndreas Gohr 28904a78b87SAndreas Gohr /** 29004a78b87SAndreas Gohr * The oauth key 29104a78b87SAndreas Gohr * 29204a78b87SAndreas Gohr * @return string 29304a78b87SAndreas Gohr */ 29404a78b87SAndreas Gohr public function getKey() 29504a78b87SAndreas Gohr { 29604a78b87SAndreas Gohr return $this->getConf('key'); 29704a78b87SAndreas Gohr } 29804a78b87SAndreas Gohr 29904a78b87SAndreas Gohr /** 30004a78b87SAndreas Gohr * The oauth secret 30104a78b87SAndreas Gohr * 30204a78b87SAndreas Gohr * @return string 30304a78b87SAndreas Gohr */ 30404a78b87SAndreas Gohr public function getSecret() 30504a78b87SAndreas Gohr { 30604a78b87SAndreas Gohr return $this->getConf('secret'); 30704a78b87SAndreas Gohr } 30804a78b87SAndreas Gohr 30904a78b87SAndreas Gohr // endregion 31004a78b87SAndreas Gohr} 311