Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Facebook
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 6
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validateConfig
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 authenticate
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 getSessionInitiator
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getAccessTokenFromCode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getDetailsFromAccessToken
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Facebook authentication module.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
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  Authentication
25 * @author   Franck Borel <franck.borel@gbv.de>
26 * @author   Demian Katz <demian.katz@villanova.edu>
27 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
28 * @link     https://vufind.org Main Page
29 */
30
31namespace VuFind\Auth;
32
33use VuFind\Db\Entity\UserEntityInterface;
34use VuFind\Exception\Auth as AuthException;
35
36/**
37 * Facebook authentication module.
38 *
39 * @category VuFind
40 * @package  Authentication
41 * @author   Franck Borel <franck.borel@gbv.de>
42 * @author   Demian Katz <demian.katz@villanova.edu>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     https://vufind.org Main Page
45 */
46class Facebook extends AbstractBase implements
47    \VuFindHttp\HttpServiceAwareInterface
48{
49    use \VuFindHttp\HttpServiceAwareTrait;
50
51    /**
52     * Session container
53     *
54     * @var \Laminas\Session\Container
55     */
56    protected $session;
57
58    /**
59     * Constructor
60     *
61     * @param \Laminas\Session\Container $container Session container for persisting
62     * state information.
63     */
64    public function __construct(\Laminas\Session\Container $container)
65    {
66        $this->session = $container;
67    }
68
69    /**
70     * Validate configuration parameters. This is a support method for getConfig(),
71     * so the configuration MUST be accessed using $this->config; do not call
72     * $this->getConfig() from within this method!
73     *
74     * @throws AuthException
75     * @return void
76     */
77    protected function validateConfig()
78    {
79        // Throw an exception if the required username setting is missing.
80        $fb = $this->config->Facebook;
81        if (!isset($fb->appId) || empty($fb->appId)) {
82            throw new AuthException(
83                'Facebook app ID is missing in your configuration file.'
84            );
85        }
86
87        if (!isset($fb->secret) || empty($fb->secret)) {
88            throw new AuthException(
89                'Facebook app secret is missing in your configuration file.'
90            );
91        }
92    }
93
94    /**
95     * Attempt to authenticate the current user. Throws exception if login fails.
96     *
97     * @param \Laminas\Http\PhpEnvironment\Request $request Request object containing
98     * account credentials.
99     *
100     * @throws AuthException
101     * @return UserEntityInterface Object representing logged-in user.
102     */
103    public function authenticate($request)
104    {
105        $code = $request->getQuery()->get('code');
106        if (empty($code)) {
107            throw new AuthException('authentication_error_admin');
108        }
109        $accessToken = $this->getAccessTokenFromCode($code);
110        if (empty($accessToken)) {
111            throw new AuthException('authentication_error_admin');
112        }
113        $details = $this->getDetailsFromAccessToken($accessToken);
114        if (empty($details->id)) {
115            throw new AuthException('authentication_error_admin');
116        }
117
118        // If we made it this far, we should log in the user!
119        $userService = $this->getUserService();
120        $user = $this->getOrCreateUserByUsername($details->id);
121        if (isset($details->first_name)) {
122            $user->setFirstname($details->first_name);
123        }
124        if (isset($details->last_name)) {
125            $user->setLastname($details->last_name);
126        }
127        if (isset($details->email)) {
128            $userService->updateUserEmail($user, $details->email);
129        }
130
131        // Save and return the user object:
132        $userService->persistEntity($user);
133        return $user;
134    }
135
136    /**
137     * Get the URL to establish a session (needed when the internal VuFind login
138     * form is inadequate). Returns false when no session initiator is needed.
139     *
140     * @param string $target Full URL where external authentication method should
141     * send user after login (some drivers may override this).
142     *
143     * @return bool|string
144     */
145    public function getSessionInitiator($target)
146    {
147        $base = 'https://www.facebook.com/dialog/oauth';
148        // Adding the auth_method setting makes it possible to handle logins when
149        // using an auth method that proxies others (e.g. ChoiceAuth)
150        $target .= ((str_contains($target, '?')) ? '&' : '?')
151            . 'auth_method=Facebook';
152        $this->session->lastUri = $target;
153        return $base . '?client_id='
154            . urlencode($this->getConfig()->Facebook->appId)
155            . '&redirect_uri=' . urlencode($target)
156            . '&scope=public_profile,email';
157    }
158
159    /**
160     * Obtain an access token from a code.
161     *
162     * @param string $code Code to look up.
163     *
164     * @return string
165     */
166    protected function getAccessTokenFromCode($code)
167    {
168        $requestUrl = 'https://graph.facebook.com/oauth/access_token?'
169            . 'client_id=' . urlencode($this->getConfig()->Facebook->appId)
170            . '&redirect_uri=' . urlencode($this->session->lastUri)
171            . '&client_secret=' . urlencode($this->getConfig()->Facebook->secret)
172            . '&code=' . urlencode($code);
173        $response = $this->httpService->get($requestUrl);
174        $parts = explode('&', $response->getBody(), 2);
175        $parts = explode('=', $parts[0], 2);
176        return $parts[1] ?? null;
177    }
178
179    /**
180     * Given an access token, look up user details.
181     *
182     * @param string $accessToken Access token
183     *
184     * @return object
185     */
186    protected function getDetailsFromAccessToken($accessToken)
187    {
188        $request = 'https://graph.facebook.com/v2.2/me?'
189            . '&access_token=' . urlencode($accessToken);
190        $response = $this->httpService->get($request);
191        $json = json_decode($response->getBody());
192        return $json;
193    }
194}