1<?php
2/*
3 * vim:set softtabstop=4 shiftwidth=4 expandtab:
4 *
5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
6 * Copyright 2001 - 2020 Ampache.org
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 *
21 */
22
23declare(strict_types=0);
24
25namespace Ampache\Module\Util\OAuth;
26
27use Ampache\Module\Util\OAuth\Exception\OAuthException;
28
29/**
30 * Class OAuthServer
31 *
32 * @deprecated Not in use
33 */
34class OAuthServer
35{
36    protected $timestamp_threshold = 300; // in seconds, five minutes
37    protected $version             = '1.0';
38    protected $signature_methods   = array();
39
40    protected $data_store;
41
42    /**
43     * OAuthServer constructor.
44     * @param $data_store
45     */
46    public function __construct($data_store)
47    {
48        $this->data_store = $data_store;
49    }
50
51    /**
52     * @param $signature_method
53     */
54    public function add_signature_method($signature_method)
55    {
56        $this->signature_methods[$signature_method->get_name()] = $signature_method;
57    }
58
59    // high level functions
60
61    /**
62     * process a request_token request
63     * returns the request token on success
64     * @param $request
65     * @return
66     * @throws OAuthException
67     */
68    public function fetch_request_token(&$request)
69    {
70        $this->get_version($request);
71
72        $consumer = $this->get_consumer($request);
73
74        // no token required for the initial token request
75        $token = null;
76
77        $this->check_signature($request, $consumer, $token);
78
79        // Rev A change
80        $callback = $request->get_parameter('oauth_callback');
81
82        return $this->data_store->new_request_token($consumer, $callback);
83    }
84
85    /**
86     * process an access_token request
87     * returns the access token on success
88     * @param $request
89     * @return mixed
90     * @throws OAuthException
91     */
92    public function fetch_access_token(&$request)
93    {
94        $this->get_version($request);
95
96        $consumer = $this->get_consumer($request);
97
98        // requires authorized request token
99        $token = $this->get_token($request, $consumer, "request");
100
101        $this->check_signature($request, $consumer, $token);
102
103        // Rev A change
104        $verifier = $request->get_parameter('oauth_verifier');
105
106        return $this->data_store->new_access_token($token, $consumer, $verifier);
107    }
108
109    /**
110     * verify an api call, checks all the parameters
111     * @param $request
112     * @return array
113     * @throws OAuthException
114     */
115    public function verify_request(&$request)
116    {
117        $this->get_version($request);
118        $consumer = $this->get_consumer($request);
119        $token    = $this->get_token($request, $consumer);
120        $this->check_signature($request, $consumer, $token);
121
122        return array($consumer, $token);
123    }
124
125    // Internals from here
126
127    /**
128     * version 1
129     * @param $request
130     * @return string
131     * @throws OAuthException
132     */
133    private function get_version(&$request)
134    {
135        $version = $request->get_parameter("oauth_version");
136        if (!$version) {
137            // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
138            // Chapter 7.0 ("Accessing Protected Resources")
139            $version = '1.0';
140        }
141        if ($version !== $this->version) {
142            throw new OAuthException("OAuth version '$version' not supported");
143        }
144
145        return $version;
146    }
147
148    /**
149     * figure out the signature with some defaults
150     * @param $request
151     * @return mixed
152     * @throws OAuthException
153     */
154    private function get_signature_method($request)
155    {
156        $signature_method = $request instanceof OAuthRequest ? $request->get_parameter("oauth_signature_method") : null;
157
158        if (!$signature_method) {
159            // According to chapter 7 ("Accessing Protected Resources") the signature-method
160            // parameter is required, and we can't just fallback to PLAINTEXT
161            throw new OAuthException('No signature method parameter. This parameter is required');
162        }
163
164        if (!in_array($signature_method, array_keys($this->signature_methods))) {
165            throw new OAuthException("Signature method '$signature_method' not supported " . "try one of the following: " . implode(", ",
166                    array_keys($this->signature_methods)));
167        }
168
169        return $this->signature_methods[$signature_method];
170    }
171
172    /**
173     * try to find the consumer for the provided request's consumer key
174     * @param $request
175     * @return
176     * @throws OAuthException
177     */
178    private function get_consumer($request)
179    {
180        $consumer_key = $request instanceof OAuthRequest ? $request->get_parameter("oauth_consumer_key") : null;
181
182        if (!$consumer_key) {
183            throw new OAuthException("Invalid consumer key");
184        }
185
186        $consumer = $this->data_store->lookup_consumer($consumer_key);
187        if (!$consumer) {
188            throw new OAuthException("Invalid consumer");
189        }
190
191        return $consumer;
192    }
193
194    /**
195     * try to find the token for the provided request's token key
196     * @param $request
197     * @param $consumer
198     * @param string $token_type
199     * @return
200     * @throws OAuthException
201     */
202    private function get_token($request, $consumer, $token_type = "access")
203    {
204        $token_field = $request instanceof OAuthRequest ? $request->get_parameter('oauth_token') : null;
205        $token       = $this->data_store->lookup_token($consumer, $token_type, $token_field);
206        if (!$token) {
207            throw new OAuthException("Invalid $token_type token: $token_field");
208        }
209
210        return $token;
211    }
212
213    /**
214     * all-in-one function to check the signature on a request
215     * should guess the signature method appropriately
216     * @param $request
217     * @param $consumer
218     * @param $token
219     * @throws OAuthException
220     */
221    private function check_signature($request, $consumer, $token)
222    {
223        // this should probably be in a different method
224        $timestamp = $request instanceof OAuthRequest ? $request->get_parameter('oauth_timestamp') : null;
225        $nonce     = $request instanceof OAuthRequest ? $request->get_parameter('oauth_nonce') : null;
226
227        $this->check_timestamp($timestamp);
228        $this->check_nonce($consumer, $token, $nonce, $timestamp);
229
230        $signature_method = $this->get_signature_method($request);
231
232        $signature = $request->get_parameter('oauth_signature');
233        $valid_sig = $signature_method->check_signature($request, $consumer, $token, $signature);
234
235        if (!$valid_sig) {
236            throw new OAuthException("Invalid signature");
237        }
238    }
239
240    /**
241     * check that the timestamp is new enough
242     * @param $timestamp
243     * @throws OAuthException
244     */
245    private function check_timestamp($timestamp)
246    {
247        if (!$timestamp) {
248            throw new OAuthException('Missing timestamp parameter. The parameter is required');
249        }
250
251        // verify that timestamp is recent
252        $now = time();
253        if (abs($now - $timestamp) > $this->timestamp_threshold) {
254            throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
255        }
256    }
257
258    /**
259     * check that the nonce is not repeated
260     * @param $consumer
261     * @param $token
262     * @param $nonce
263     * @param $timestamp
264     * @throws OAuthException
265     */
266    private function check_nonce($consumer, $token, $nonce, $timestamp)
267    {
268        if (!$nonce) {
269            throw new OAuthException('Missing nonce parameter. The parameter is required');
270        }
271
272        // verify that the nonce is unique
273        $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
274        if ($found) {
275            throw new OAuthException("Nonce already used: $nonce");
276        }
277    }
278}
279