xref: /plugin/oauth/Adapter.php (revision 04a78b879a81d3ef4632ad05e75190e070c61c1f)
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