1*04a78b87SAndreas Gohr<?php 2*04a78b87SAndreas Gohr 3*04a78b87SAndreas Gohrnamespace dokuwiki\plugin\oauth; 4*04a78b87SAndreas Gohr 5*04a78b87SAndreas Gohruse dokuwiki\Extension\ActionPlugin; 6*04a78b87SAndreas Gohruse OAuth\Common\Consumer\Credentials; 7*04a78b87SAndreas Gohruse OAuth\Common\Http\Exception\TokenResponseException; 8*04a78b87SAndreas Gohruse OAuth\OAuth1\Service\AbstractService as Abstract1Service; 9*04a78b87SAndreas Gohruse OAuth\OAuth1\Token\TokenInterface; 10*04a78b87SAndreas Gohruse OAuth\OAuth2\Service\AbstractService as Abstract2Service; 11*04a78b87SAndreas Gohruse OAuth\OAuth2\Service\Exception\InvalidAuthorizationStateException; 12*04a78b87SAndreas Gohruse OAuth\ServiceFactory; 13*04a78b87SAndreas Gohr 14*04a78b87SAndreas Gohr/** 15*04a78b87SAndreas Gohr * Base class to implement a Backend Service for the oAuth Plugin 16*04a78b87SAndreas Gohr */ 17*04a78b87SAndreas Gohrabstract class Adapter extends ActionPlugin 18*04a78b87SAndreas Gohr{ 19*04a78b87SAndreas Gohr /** 20*04a78b87SAndreas Gohr * @var Abstract2Service|Abstract1Service 21*04a78b87SAndreas Gohr * @see getOAuthService() use this to ensure it's intialized 22*04a78b87SAndreas Gohr */ 23*04a78b87SAndreas Gohr protected $oAuth; 24*04a78b87SAndreas Gohr 25*04a78b87SAndreas Gohr // region internal methods 26*04a78b87SAndreas Gohr 27*04a78b87SAndreas Gohr /** 28*04a78b87SAndreas Gohr * Auto register this plugin with the oAuth authentication plugin 29*04a78b87SAndreas Gohr * 30*04a78b87SAndreas Gohr * @inheritDoc 31*04a78b87SAndreas Gohr */ 32*04a78b87SAndreas Gohr public function register(\Doku_Event_Handler $controller) 33*04a78b87SAndreas Gohr { 34*04a78b87SAndreas Gohr $controller->register_hook('PLUGIN_OAUTH_BACKEND_REGISTER', 'AFTER', $this, 'handleRegister'); 35*04a78b87SAndreas Gohr } 36*04a78b87SAndreas Gohr 37*04a78b87SAndreas Gohr /** 38*04a78b87SAndreas Gohr * Auto register this plugin with the oAuth authentication plugin 39*04a78b87SAndreas Gohr */ 40*04a78b87SAndreas Gohr public function handleRegister(\Doku_Event $event, $param) 41*04a78b87SAndreas Gohr { 42*04a78b87SAndreas Gohr $event->data[$this->getServiceID()] = $this; 43*04a78b87SAndreas Gohr } 44*04a78b87SAndreas Gohr 45*04a78b87SAndreas Gohr /** 46*04a78b87SAndreas Gohr * Initialize the oAuth service 47*04a78b87SAndreas Gohr * 48*04a78b87SAndreas Gohr * @param string $guid UIID for the user to authenticate 49*04a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 50*04a78b87SAndreas Gohr */ 51*04a78b87SAndreas Gohr public function initOAuthService($guid) 52*04a78b87SAndreas Gohr { 53*04a78b87SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 54*04a78b87SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 55*04a78b87SAndreas Gohr 56*04a78b87SAndreas Gohr $credentials = new Credentials( 57*04a78b87SAndreas Gohr $this->getKey(), 58*04a78b87SAndreas Gohr $this->getSecret(), 59*04a78b87SAndreas Gohr $hlp->redirectURI() 60*04a78b87SAndreas Gohr ); 61*04a78b87SAndreas Gohr 62*04a78b87SAndreas Gohr $serviceFactory = new ServiceFactory(); 63*04a78b87SAndreas Gohr $serviceFactory->setHttpClient(new HTTPClient()); 64*04a78b87SAndreas Gohr $servicename = $this->getServiceID(); 65*04a78b87SAndreas Gohr $serviceclass = $this->registerServiceClass(); 66*04a78b87SAndreas Gohr if ($serviceclass) { 67*04a78b87SAndreas Gohr $serviceFactory->registerService($servicename, $serviceclass); 68*04a78b87SAndreas Gohr } 69*04a78b87SAndreas Gohr 70*04a78b87SAndreas Gohr $this->oAuth = $serviceFactory->createService( 71*04a78b87SAndreas Gohr $servicename, 72*04a78b87SAndreas Gohr $credentials, 73*04a78b87SAndreas Gohr new Storage($guid), 74*04a78b87SAndreas Gohr $this->getScopes() 75*04a78b87SAndreas Gohr ); 76*04a78b87SAndreas Gohr 77*04a78b87SAndreas Gohr if ($this->oAuth === null) { 78*04a78b87SAndreas Gohr throw new Exception('Failed to initialize Service ' . $this->getLabel()); 79*04a78b87SAndreas Gohr } 80*04a78b87SAndreas Gohr } 81*04a78b87SAndreas Gohr 82*04a78b87SAndreas Gohr /** 83*04a78b87SAndreas Gohr * @return Abstract2Service|Abstract1Service 84*04a78b87SAndreas Gohr * @throws Exception 85*04a78b87SAndreas Gohr */ 86*04a78b87SAndreas Gohr public function getOAuthService() 87*04a78b87SAndreas Gohr { 88*04a78b87SAndreas Gohr if ($this->oAuth === null) throw new Exception('OAuth Service not properly initialized'); 89*04a78b87SAndreas Gohr return $this->oAuth; 90*04a78b87SAndreas Gohr } 91*04a78b87SAndreas Gohr 92*04a78b87SAndreas Gohr /** 93*04a78b87SAndreas Gohr * Redirects to the service for requesting access 94*04a78b87SAndreas Gohr * 95*04a78b87SAndreas Gohr * This is the first step of oAuth authentication 96*04a78b87SAndreas Gohr * 97*04a78b87SAndreas Gohr * This implementation tries to abstract away differences between oAuth1 and oAuth2, 98*04a78b87SAndreas Gohr * but might need to be overwritten for specific services 99*04a78b87SAndreas Gohr * 100*04a78b87SAndreas Gohr * @throws TokenResponseException 101*04a78b87SAndreas Gohr * @throws Exception 102*04a78b87SAndreas Gohr */ 103*04a78b87SAndreas Gohr public function login() 104*04a78b87SAndreas Gohr { 105*04a78b87SAndreas Gohr $oauth = $this->getOAuthService(); 106*04a78b87SAndreas Gohr 107*04a78b87SAndreas Gohr // store Farmer animal in oAuth state parameter 108*04a78b87SAndreas Gohr /** @var \helper_plugin_farmer $farmer */ 109*04a78b87SAndreas Gohr $farmer = plugin_load('helper', 'farmer'); 110*04a78b87SAndreas Gohr $parameters = []; 111*04a78b87SAndreas Gohr if ($farmer && $animal = $farmer->getAnimal()) { 112*04a78b87SAndreas Gohr $parameters['state'] = urlencode(base64_encode(json_encode( 113*04a78b87SAndreas Gohr [ 114*04a78b87SAndreas Gohr 'animal' => $animal, 115*04a78b87SAndreas Gohr 'state' => md5(rand()), 116*04a78b87SAndreas Gohr ] 117*04a78b87SAndreas Gohr ))); 118*04a78b87SAndreas Gohr $oauth->getStorage()->storeAuthorizationState($oauth->service(), $parameters['state']); 119*04a78b87SAndreas Gohr } 120*04a78b87SAndreas Gohr 121*04a78b87SAndreas Gohr if (is_a($oauth, Abstract1Service::class)) { /* oAuth1 handling */ 122*04a78b87SAndreas Gohr // extra request needed for oauth1 to request a request token 123*04a78b87SAndreas Gohr $token = $oauth->requestRequestToken(); 124*04a78b87SAndreas Gohr $parameters['oauth_token'] = $token->getRequestToken(); 125*04a78b87SAndreas Gohr } 126*04a78b87SAndreas Gohr $url = $oauth->getAuthorizationUri($parameters); 127*04a78b87SAndreas Gohr 128*04a78b87SAndreas Gohr send_redirect($url); 129*04a78b87SAndreas Gohr } 130*04a78b87SAndreas Gohr 131*04a78b87SAndreas Gohr /** 132*04a78b87SAndreas Gohr * Request access token 133*04a78b87SAndreas Gohr * 134*04a78b87SAndreas Gohr * This is the second step of oAuth authentication 135*04a78b87SAndreas Gohr * 136*04a78b87SAndreas Gohr * This implementation tries to abstract away differences between oAuth1 and oAuth2, 137*04a78b87SAndreas Gohr * but might need to be overwritten for specific services 138*04a78b87SAndreas Gohr * 139*04a78b87SAndreas Gohr * Thrown exceptions indicate a non-successful login because of some error, appropriate messages 140*04a78b87SAndreas Gohr * should be shown to the user. A return of false with no exceptions indicates that there was no 141*04a78b87SAndreas Gohr * oauth data at all. This can probably be silently ignored. 142*04a78b87SAndreas Gohr * 143*04a78b87SAndreas Gohr * @return bool true if authentication was successful 144*04a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 145*04a78b87SAndreas Gohr * @throws InvalidAuthorizationStateException 146*04a78b87SAndreas Gohr */ 147*04a78b87SAndreas Gohr public function checkToken() 148*04a78b87SAndreas Gohr { 149*04a78b87SAndreas Gohr global $INPUT; 150*04a78b87SAndreas Gohr 151*04a78b87SAndreas Gohr $oauth = $this->getOAuthService(); 152*04a78b87SAndreas Gohr 153*04a78b87SAndreas Gohr if (is_a($oauth, Abstract2Service::class)) { 154*04a78b87SAndreas Gohr /** @var Abstract2Service $oauth */ 155*04a78b87SAndreas Gohr if (!$INPUT->get->has('code')) return false; 156*04a78b87SAndreas Gohr $state = $INPUT->get->str('state', null); 157*04a78b87SAndreas Gohr $oauth->requestAccessToken($INPUT->get->str('code'), $state); 158*04a78b87SAndreas Gohr } else { 159*04a78b87SAndreas Gohr /** @var Abstract1Service $oauth */ 160*04a78b87SAndreas Gohr if (!$INPUT->get->has('oauth_token')) return false; 161*04a78b87SAndreas Gohr /** @var TokenInterface $token */ 162*04a78b87SAndreas Gohr $token = $oauth->getStorage()->retrieveAccessToken($this->getServiceID()); 163*04a78b87SAndreas Gohr $oauth->requestAccessToken( 164*04a78b87SAndreas Gohr $INPUT->get->str('oauth_token'), 165*04a78b87SAndreas Gohr $INPUT->get->str('oauth_verifier'), 166*04a78b87SAndreas Gohr $token->getRequestTokenSecret() 167*04a78b87SAndreas Gohr ); 168*04a78b87SAndreas Gohr } 169*04a78b87SAndreas Gohr return true; 170*04a78b87SAndreas Gohr } 171*04a78b87SAndreas Gohr 172*04a78b87SAndreas Gohr /** 173*04a78b87SAndreas Gohr * Return the Service Login Button 174*04a78b87SAndreas Gohr * 175*04a78b87SAndreas Gohr * @return string 176*04a78b87SAndreas Gohr */ 177*04a78b87SAndreas Gohr public function loginButton() 178*04a78b87SAndreas Gohr { 179*04a78b87SAndreas Gohr global $ID; 180*04a78b87SAndreas Gohr 181*04a78b87SAndreas Gohr $attr = buildAttributes([ 182*04a78b87SAndreas Gohr 'href' => wl($ID, array('oauthlogin' => $this->getServiceID()), false, '&'), 183*04a78b87SAndreas Gohr 'class' => 'plugin_oauth_' . $this->getServiceID(), 184*04a78b87SAndreas Gohr 'style' => 'background-color: ' . $this->getColor(), 185*04a78b87SAndreas Gohr ]); 186*04a78b87SAndreas Gohr 187*04a78b87SAndreas Gohr return '<a ' . $attr . '>' . $this->getSvgLogo() . '<span>' . $this->getLabel() . '</span></a> '; 188*04a78b87SAndreas Gohr } 189*04a78b87SAndreas Gohr // endregion 190*04a78b87SAndreas Gohr 191*04a78b87SAndreas Gohr // region overridable methods 192*04a78b87SAndreas Gohr 193*04a78b87SAndreas Gohr /** 194*04a78b87SAndreas Gohr * Retrieve the user's data via API 195*04a78b87SAndreas Gohr * 196*04a78b87SAndreas Gohr * The returned array needs to contain at least 'email', 'name', 'user' and optionally 'grps' 197*04a78b87SAndreas Gohr * 198*04a78b87SAndreas Gohr * Use the request() method of the oauth object to talk to the API 199*04a78b87SAndreas Gohr * 200*04a78b87SAndreas Gohr * @return array 201*04a78b87SAndreas Gohr * @throws Exception 202*04a78b87SAndreas Gohr * @see getOAuthService() 203*04a78b87SAndreas Gohr */ 204*04a78b87SAndreas Gohr abstract public function getUser(); 205*04a78b87SAndreas Gohr 206*04a78b87SAndreas Gohr /** 207*04a78b87SAndreas Gohr * Return the scopes to request 208*04a78b87SAndreas Gohr * 209*04a78b87SAndreas Gohr * This should return the minimal scopes needed for accessing the user's data 210*04a78b87SAndreas Gohr * 211*04a78b87SAndreas Gohr * @return string[] 212*04a78b87SAndreas Gohr */ 213*04a78b87SAndreas Gohr public function getScopes() 214*04a78b87SAndreas Gohr { 215*04a78b87SAndreas Gohr return []; 216*04a78b87SAndreas Gohr } 217*04a78b87SAndreas Gohr 218*04a78b87SAndreas Gohr /** 219*04a78b87SAndreas Gohr * Return the user friendly name of the service 220*04a78b87SAndreas Gohr * 221*04a78b87SAndreas Gohr * Defaults to ServiceID. You may want to override this. 222*04a78b87SAndreas Gohr * 223*04a78b87SAndreas Gohr * @return string 224*04a78b87SAndreas Gohr */ 225*04a78b87SAndreas Gohr public function getLabel() 226*04a78b87SAndreas Gohr { 227*04a78b87SAndreas Gohr return ucfirst($this->getServiceID()); 228*04a78b87SAndreas Gohr } 229*04a78b87SAndreas Gohr 230*04a78b87SAndreas Gohr /** 231*04a78b87SAndreas Gohr * Return the internal name of the Service 232*04a78b87SAndreas Gohr * 233*04a78b87SAndreas Gohr * Defaults to the plugin name (without oauth prefix). This has to match the Service class name in 234*04a78b87SAndreas Gohr * the appropriate lusitantian oauth Service namespace 235*04a78b87SAndreas Gohr * 236*04a78b87SAndreas Gohr * @return string 237*04a78b87SAndreas Gohr */ 238*04a78b87SAndreas Gohr public function getServiceID() 239*04a78b87SAndreas Gohr { 240*04a78b87SAndreas Gohr $name = $this->getPluginName(); 241*04a78b87SAndreas Gohr if (substr($name, 0, 5) === 'oauth') { 242*04a78b87SAndreas Gohr $name = substr($name, 5); 243*04a78b87SAndreas Gohr } 244*04a78b87SAndreas Gohr 245*04a78b87SAndreas Gohr return $name; 246*04a78b87SAndreas Gohr } 247*04a78b87SAndreas Gohr 248*04a78b87SAndreas Gohr /** 249*04a78b87SAndreas Gohr * Register a new Service 250*04a78b87SAndreas Gohr * 251*04a78b87SAndreas Gohr * @return string A fully qualified class name to register as new Service for your ServiceID 252*04a78b87SAndreas Gohr */ 253*04a78b87SAndreas Gohr public function registerServiceClass() 254*04a78b87SAndreas Gohr { 255*04a78b87SAndreas Gohr return null; 256*04a78b87SAndreas Gohr } 257*04a78b87SAndreas Gohr 258*04a78b87SAndreas Gohr /** 259*04a78b87SAndreas Gohr * Return the button color to use 260*04a78b87SAndreas Gohr * 261*04a78b87SAndreas Gohr * @return string 262*04a78b87SAndreas Gohr */ 263*04a78b87SAndreas Gohr public function getColor() 264*04a78b87SAndreas Gohr { 265*04a78b87SAndreas Gohr return '#999'; 266*04a78b87SAndreas Gohr } 267*04a78b87SAndreas Gohr 268*04a78b87SAndreas Gohr /** 269*04a78b87SAndreas Gohr * Return the SVG of the logo for this service 270*04a78b87SAndreas Gohr * 271*04a78b87SAndreas Gohr * Defaults to a logo.svg in the plugin directory 272*04a78b87SAndreas Gohr * 273*04a78b87SAndreas Gohr * @return string 274*04a78b87SAndreas Gohr */ 275*04a78b87SAndreas Gohr public function getSvgLogo() 276*04a78b87SAndreas Gohr { 277*04a78b87SAndreas Gohr $logo = DOKU_PLUGIN . $this->getPluginName() . '/logo.svg'; 278*04a78b87SAndreas Gohr if (file_exists($logo)) return inlineSVG($logo); 279*04a78b87SAndreas Gohr return ''; 280*04a78b87SAndreas Gohr } 281*04a78b87SAndreas Gohr 282*04a78b87SAndreas Gohr /** 283*04a78b87SAndreas Gohr * The oauth key 284*04a78b87SAndreas Gohr * 285*04a78b87SAndreas Gohr * @return string 286*04a78b87SAndreas Gohr */ 287*04a78b87SAndreas Gohr public function getKey() 288*04a78b87SAndreas Gohr { 289*04a78b87SAndreas Gohr return $this->getConf('key'); 290*04a78b87SAndreas Gohr } 291*04a78b87SAndreas Gohr 292*04a78b87SAndreas Gohr /** 293*04a78b87SAndreas Gohr * The oauth secret 294*04a78b87SAndreas Gohr * 295*04a78b87SAndreas Gohr * @return string 296*04a78b87SAndreas Gohr */ 297*04a78b87SAndreas Gohr public function getSecret() 298*04a78b87SAndreas Gohr { 299*04a78b87SAndreas Gohr return $this->getConf('secret'); 300*04a78b87SAndreas Gohr } 301*04a78b87SAndreas Gohr 302*04a78b87SAndreas Gohr // endregion 303*04a78b87SAndreas Gohr} 304