Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.00% covered (warning)
64.00%
32 / 50
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
OauthServiceTrait
64.00% covered (warning)
64.00%
32 / 50
50.00% covered (danger)
50.00%
2 / 4
16.65
0.00% covered (danger)
0.00%
0 / 1
 authenticateWithClientCredentials
63.04% covered (warning)
63.04%
29 / 46
0.00% covered (danger)
0.00%
0 / 1
9.47
 oauthServiceTraitDebug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 oauthServiceTraitError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 oauthServiceTraitLog
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * Helper trait for OAuth 2.0 connections.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2023.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Connection
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @author   Brent Palmer <brent-palmer@icpl.org>
27 * @author   Maccabee Levine <msl321@lehigh.edu>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org
30 */
31
32namespace VuFind\Connection;
33
34use function func_get_args;
35
36/**
37 * Helper trait for OAuth 2.0 connections.
38 *
39 * Classes which use this trait should also use LoggerAwareTrait.
40 *
41 * Closely adapted from VuFind\DigitalContent\OverdriveConnector.
42 *
43 * @category VuFind
44 * @package  Connection
45 * @author   Demian Katz <demian.katz@villanova.edu>
46 * @author   Brent Palmer <brent-palmer@icpl.org>
47 * @author   Maccabee Levine <msl321@lehigh.edu>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org
50 */
51trait OauthServiceTrait
52{
53    /**
54     * Current OAuth token
55     *
56     * @var stdClass
57     */
58    protected $tokenData = null;
59
60    /**
61     * Authenticate via the OAuth Client Credentials grant type.
62     *
63     * @param string $oauthUrl     URL of thee OAuth service
64     * @param string $clientId     client_id for a client_credentials grant
65     * @param string $clientSecret client_secret for a client_credentials grant
66     *
67     * @return stdClass|bool token for the session or false
68     *     if the token request failed
69     *
70     * @link https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/
71     */
72    public function authenticateWithClientCredentials(
73        $oauthUrl,
74        $clientId,
75        $clientSecret
76    ) {
77        $this->oauthServiceTraitDebug('connecting to API');
78        $tokenData = $this->tokenData;
79        $this->oauthServiceTraitDebug('Last API Token: ' . $this->varDump($tokenData));
80        if (
81            $tokenData == null
82            || !isset($tokenData->access_token)
83            || time() >= $tokenData->expirationTime
84        ) {
85            $authHeader = base64_encode(
86                $clientId . ':' . $clientSecret
87            );
88            $headers = [
89                'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
90                "Authorization: Basic $authHeader",
91            ];
92
93            $this->client->setHeaders($headers);
94            $this->client->setMethod('POST');
95            $this->client->setRawBody('grant_type=client_credentials');
96            $response = $this->client
97                ->setUri($oauthUrl)
98                ->send();
99
100            if ($response->isServerError()) {
101                $this->oauthServiceTraitError(
102                    'API HTTP Error: ' .
103                    $response->getStatusCode()
104                );
105                $this->oauthServiceTraitDebug('Request: ' . $this->client->getRequest());
106                return false;
107            }
108
109            $body = $response->getBody();
110            $tokenData = json_decode($body);
111            $this->oauthServiceTraitDebug(
112                'TokenData returned from API Call: ' . $this->varDump($tokenData)
113            );
114            if ($tokenData != null) {
115                if (isset($tokenData->errorCode)) {
116                    // In some cases, this should be returned perhaps...
117                    $this->oauthServiceTraitError('API Error: ' . $tokenData->errorCode);
118                    return false;
119                } else {
120                    $tokenData->expirationTime = time()
121                        + ($tokenData->expires_in ?? 0);
122                    $this->tokenData = $tokenData;
123                    return $tokenData;
124                }
125            } else {
126                $this->oauthServiceTraitError(
127                    'Error: Nothing returned from API call.'
128                );
129                $this->oauthServiceTraitDebug(
130                    'Body return from API Call: ' . $this->varDump($body)
131                );
132            }
133        }
134        return $tokenData;
135    }
136
137    /**
138     * Log a debug message, if $this->log exists.
139     *
140     * @param string $msg Log message
141     *
142     * @return void
143     */
144    protected function oauthServiceTraitDebug($msg)
145    {
146        $this->oauthServiceTraitLog('debug', $msg);
147    }
148
149    /**
150     * Log an error message, if $this->log exists.
151     *
152     * @param string $msg Log message
153     *
154     * @return void
155     */
156    protected function oauthServiceTraitError($msg)
157    {
158        $this->oauthServiceTraitLog('err', $msg);
159    }
160
161    /**
162     * Log a message, if $this->log exists.
163     *
164     * @param string $level Logging level
165     * @param string $msg   Log message
166     *
167     * @return void
168     */
169    protected function oauthServiceTraitLog($level, $msg)
170    {
171        if (method_exists($this, 'log')) {
172            $this->log(...func_get_args());
173        }
174    }
175}