1<?php
2
3namespace OAuth\Plugin;
4
5use OAuth\Common\Consumer\Credentials;
6use OAuth\Common\Http\Exception\TokenResponseException;
7use OAuth\Common\Storage\Exception\TokenNotFoundException;
8use OAuth\ServiceFactory;
9
10/**
11 * Class AbstractAdapter
12 *
13 * For each service that shall be used for logging into DokuWiki a subclass of this abstract
14 * class has to be created. It defines how to talk to the Service's API to retrieve user
15 * information
16 *
17 * @package OAuth\Plugin
18 */
19abstract class AbstractAdapter {
20
21    /** @var \OAuth\Common\Service\AbstractService|\OAuth\OAuth2\Service\AbstractService|\OAuth\OAuth2\Service\AbstractService */
22    public $oAuth = null;
23    /** @var \helper_plugin_evesso */
24    protected $hlp = null;
25    /** @var \OAuth\Plugin\oAuthStorage */
26    protected $storage = null;
27
28    /**
29     * Constructor
30     *
31     * @param $url
32     */
33    public function __construct($url) {
34        $this->hlp = plugin_load('helper', 'evesso');
35
36        $credentials = new Credentials(
37            $this->hlp->getKey($this->getAdapterName()),
38            $this->hlp->getSecret($this->getAdapterName()),
39            $url
40        );
41
42        $this->storage = new oAuthStorage();
43
44        $serviceFactory = new ServiceFactory();
45        $serviceFactory->setHttpClient(new oAuthHTTPClient());
46        $this->oAuth = $serviceFactory->createService(
47            $this->getServiceName(),
48            $credentials,
49            $this->storage,
50            $this->getScope()
51        );
52    }
53
54    /**
55     * Check if the initialization worked
56     *
57     * @return bool
58     */
59    public function isInitialized() {
60        if(is_null($this->oAuth)) {
61            return false;
62        }
63        return true;
64    }
65
66    /**
67     * Redirects to the service for requesting access
68     *
69     * This is the first step of oAuth authentication
70     *
71     * This implementation tries to abstract away differences between oAuth1 and oAuth2,
72     * but might need to be overwritten for specific services
73     */
74    public function login() {
75        if(is_a($this->oAuth, 'OAuth\OAuth2\Service\AbstractService')) { /* oAuth2 handling */
76
77            $url = $this->oAuth->getAuthorizationUri();
78        } else { /* oAuth1 handling */
79
80            // extra request needed for oauth1 to request a request token :-)
81            $token = $this->oAuth->requestRequestToken();
82
83            $url = $this->oAuth->getAuthorizationUri(array('oauth_token' => $token->getRequestToken()));
84        }
85
86        send_redirect($url);
87    }
88
89    /**
90     * Clear storage token for user
91     *
92     */
93    public function logout() {
94        if ($this->isInitialized()) {
95            $this->oAuth->getStorage()->clearToken($this->oAuth->service());
96        }
97    }
98
99    /**
100     * Check access_token
101     *
102     * Update as needed
103     *
104     * @return bool true if access_token is valid. false otherwise
105     */
106    public function checkToken() {
107        global $INPUT;
108
109        if ($INPUT->get->has('code')) {
110            if (!$this->requestAccessToken()) { //Request access token (Second step of oAuth authentication)
111                return false;
112            }
113        } else {
114            //Check if access token is still valid, if not, refresh the access_token
115            if (!$this->checkAccessToken() && !$this->refreshAccessToken()) {
116                return false;
117            }
118        }
119
120        $validDomains = $this->hlp->getValidDomains();
121        if (count($validDomains) > 0) {
122            $userData = $this->getUser();
123            if (!$this->hlp->checkMail($userData['mail'])) {
124                msg(sprintf($this->hlp->getLang("rejectedEMail"),join(', ', $validDomains)),-1);
125                send_redirect(wl('', array('do' => 'login',),false,'&'));
126            }
127        }
128        return true;
129    }
130
131    /**
132     * Request access token
133     *
134     * Second step of oAuth authentication
135     *
136     * @global type $INPUT
137     * @global \OAuth\Plugin\type $conf
138     * @return boolean true if successful. false otherwise
139     */
140    private function requestAccessToken() {
141        global $INPUT, $conf;
142
143        try {
144            $this->oAuth->requestAccessToken($INPUT->get->str('code'), $INPUT->get->str('state', null));
145            $this->oAuth->getStorage()->clearAuthorizationState($this->oAuth->service());
146            return true;
147        } catch (TokenResponseException $e) {
148            msg($e->getMessage(), -1);
149            if ($conf['allowdebug']) {
150                msg('<pre>' . hsc($e->getTraceAsString()) . '</pre>', -1);
151            }
152            return false;
153        }
154    }
155
156    /**
157     * Check if access token is still valid
158     *
159     * @global type $conf
160     * @return boolean true if access_token is vaild. false otherwise
161     */
162    private function checkAccessToken() {
163        global $conf;
164        try {
165            if ($this->oAuth->getStorage()->retrieveAccessToken($this->oAuth->service())->getEndOfLife() - 90 > time()) {
166                return true; // access_token is still vaild - already validated
167            }
168        } catch (TokenNotFoundException $e) {
169            msg($e->getMessage(), -1);
170            if ($conf['allowdebug']) {
171                msg('<pre>' . hsc($e->getTraceAsString()) . '</pre>', -1);
172            }
173            return false; // oAuth storage have no token
174        }
175    }
176
177    /**
178     * Refresh access_token (from refresh_token)
179     *
180     * @global \OAuth\Plugin\type $conf
181     * @return boolean true if successful. false otherwise
182     */
183    private function refreshAccessToken() {
184        global $conf;
185        try {
186            $this->oAuth->refreshAccessToken($this->oAuth->getStorage()->retrieveAccessToken($this->oAuth->service()));
187            return true;
188        } catch (TokenNotFoundException | TokenResponseException $e) {
189            msg($e->getMessage(), -1);
190            if ($conf['allowdebug']) {
191                msg('<pre>' . hsc($e->getTraceAsString()) . '</pre>', -1);
192            }
193            return false;
194        }
195    }
196
197    /**
198     * Return the name of the oAuth service class to use
199     *
200     * This should match with one of the files in
201     * phpoauth/src/oAuth/oAuth[12]/Service/*
202     *
203     * By default extracts the name from the class name
204     *
205     * @return string
206     */
207    public function getServiceName() {
208        return $this->getAdapterName();
209    }
210
211    /**
212     * Retrun the name of this Adapter
213     *
214     * It specifies which configuration setting should be used
215     *
216     * @return string
217     */
218    public function getAdapterName() {
219        $name = preg_replace('/Adapter$/', '', get_called_class());
220        $name = str_replace('OAuth\\Plugin\\', '', $name);
221        return $name;
222    }
223
224    /**
225     * Return the scope to request
226     *
227     * This should return the minimal scope needed for accessing the user's data
228     *
229     * @return array
230     */
231    public function getScope() {
232        return array();
233    }
234
235    /**
236     * Retrieve the user's data
237     *
238     * The array needs to contain at least 'email', 'name', 'user', 'grps'
239     *
240     * @return array
241     */
242    abstract public function getUser();
243}
244