From a643943d4c7e75b4889713a605f7861c5f056519 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Wed, 7 Jan 2015 13:18:37 -0500
Subject: [PATCH] Added Facebook authentication support.

---
 config/vufind/config.ini                   |  12 +-
 module/VuFind/config/module.config.php     |   3 +-
 module/VuFind/src/VuFind/Auth/Facebook.php | 203 +++++++++++++++++++++
 3 files changed, 216 insertions(+), 2 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Auth/Facebook.php

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index c98e827d7f5..84079bfc43b 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -202,7 +202,8 @@ title_level_holds_mode = "disabled"
 ; This section allows you to determine how the users will authenticate.
 ; You can use an LDAP directory, the local ILS (or multiple ILSes through
 ; the MultiILS option), the VuFind database (Database), Shibboleth, SIP2,
-; CAS or some combination of these (via the MultiAuth or ChoiceAuth options).
+; CAS, Facebook or some combination of these (via the MultiAuth or ChoiceAuth
+; options).
 [Authentication]
 ;method          = LDAP
 ;method         = ILS
@@ -213,6 +214,7 @@ method         = Database
 ;method         = MultiAuth
 ;method         = ChoiceAuth
 ;method         = MultiILS
+;method         = Facebook
 
 ; This setting only applies when method is set to ILS.  It determines which
 ; field of the ILS driver's patronLogin() return array is used as the username
@@ -489,6 +491,14 @@ database          = mysql://root@localhost/vufind
 ;major = major1
 ;home_library = library
 
+; Facebook may be used for authentication; fill in this section in addition to
+; turning it on in [Authentication] above to use it. You must register your
+; VuFind instance as an application at http://developers.facebook.com to obtain
+; credentials.
+;[Facebook]
+;appId = "your app ID"
+;secret = "your app secret"
+
 ; External Content is Optional.
 ; To use multiple, separate with a comma.  Priority will be given by the order listed
 ; Account id is separated with a colon, if no id is used then no colon is necessary
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 6862ddff496..d71720ff9ae 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -234,12 +234,13 @@ $config = array(
                     'multiils' => 'VuFind\Auth\Factory::getMultiILS',
                 ),
                 'invokables' => array(
+                    'cas' => 'VuFind\Auth\CAS',
                     'choiceauth' => 'VuFind\Auth\ChoiceAuth',
                     'database' => 'VuFind\Auth\Database',
+                    'facebook' => 'VuFind\Auth\Facebook',
                     'ldap' => 'VuFind\Auth\LDAP',
                     'multiauth' => 'VuFind\Auth\MultiAuth',
                     'shibboleth' => 'VuFind\Auth\Shibboleth',
-                    'cas' => 'VuFind\Auth\CAS',
                     'sip2' => 'VuFind\Auth\SIP2',
                 ),
                 'aliases' => array(
diff --git a/module/VuFind/src/VuFind/Auth/Facebook.php b/module/VuFind/src/VuFind/Auth/Facebook.php
new file mode 100644
index 00000000000..e0d913f952d
--- /dev/null
+++ b/module/VuFind/src/VuFind/Auth/Facebook.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Facebook authentication module.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Authentication
+ * @author   Franck Borel <franck.borel@gbv.de>
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFind\Auth;
+use VuFind\Exception\Auth as AuthException;
+
+/**
+ * Facebook authentication module.
+ *
+ * @category VuFind2
+ * @package  Authentication
+ * @author   Franck Borel <franck.borel@gbv.de>
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class Facebook extends AbstractBase implements
+    \VuFindHttp\HttpServiceAwareInterface
+{
+    /**
+     * HTTP service
+     *
+     * @var \VuFindHttp\HttpServiceInterface
+     */
+    protected $httpService = null;
+
+    /**
+     * Session container
+     *
+     * @var \Zend\Session\Container
+     */
+    protected $session;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->session = new \Zend\Session\Container('Facebook');
+    }
+
+    /**
+     * Set the HTTP service to be used for HTTP requests.
+     *
+     * @param HttpServiceInterface $service HTTP service
+     *
+     * @return void
+     */
+    public function setHttpService(\VuFindHttp\HttpServiceInterface $service)
+    {
+        $this->httpService = $service;
+    }
+
+    /**
+     * Validate configuration parameters.  This is a support method for getConfig(),
+     * so the configuration MUST be accessed using $this->config; do not call
+     * $this->getConfig() from within this method!
+     *
+     * @throws AuthException
+     * @return void
+     */
+    protected function validateConfig()
+    {
+        // Throw an exception if the required username setting is missing.
+        $fb = $this->config->Facebook;
+        if (!isset($fb->appId) || empty($fb->appId)) {
+            throw new AuthException(
+                'Facebook app ID is missing in your configuration file.'
+            );
+        }
+
+        if (!isset($fb->secret) || empty($fb->secret)) {
+            throw new AuthException(
+                'Facebook app secret is missing in your configuration file.'
+            );
+        }
+    }
+
+    /**
+     * Attempt to authenticate the current user.  Throws exception if login fails.
+     *
+     * @param \Zend\Http\PhpEnvironment\Request $request Request object containing
+     * account credentials.
+     *
+     * @throws AuthException
+     * @return \VuFind\Db\Row\User Object representing logged-in user.
+     */
+    public function authenticate($request)
+    {
+        $code = $request->getQuery()->get('code');
+        if (empty($code)) {
+            throw new AuthException('authentication_error_admin');
+        }
+        $accessToken = $this->getAccessTokenFromCode($code);
+        if (empty($accessToken)) {
+            throw new AuthException('authentication_error_admin');
+        }
+        $details = $this->getDetailsFromAccessToken($accessToken);
+        if (empty($details->id)) {
+            throw new AuthException('authentication_error_admin');
+        }
+
+        // If we made it this far, we should log in the user!
+        $user = $this->getUserTable()->getByUsername($details->id);
+        if (isset($details->first_name)) {
+            $user->firstname = $details->first_name;
+        }
+        if (isset($details->last_name)) {
+            $user->lastname = $details->last_name;
+        }
+        if (isset($details->email)) {
+            $user->email = $details->email;
+        }
+
+        // Save and return the user object:
+        $user->save();
+        return $user;
+    }
+
+    /**
+     * Get the URL to establish a session (needed when the internal VuFind login
+     * form is inadequate).  Returns false when no session initiator is needed.
+     *
+     * @param string $target Full URL where external authentication method should
+     * send user after login (some drivers may override this).
+     *
+     * @return bool|string
+     */
+    public function getSessionInitiator($target)
+    {
+        $base = 'https://www.facebook.com/dialog/oauth';
+        // Adding the auth_method setting makes it possible to handle logins when
+        // using an auth method that proxies others (e.g. ChoiceAuth)
+        $target .= ((strpos($target, '?') !== false) ? '&' : '?')
+            . 'auth_method=Facebook';
+        $this->session->lastUri = $target;
+        return $base . '?client_id='
+            . urlencode($this->config->Facebook->appId)
+            . '&redirect_uri=' . urlencode($target)
+            . '&scope=public_profile,email';
+    }
+
+    /**
+     * Obtain an access token from a code.
+     *
+     * @param string $code Code to look up.
+     *
+     * @return string
+     */
+    protected function getAccessTokenFromCode($code)
+    {
+        $requestUrl = 'https://graph.facebook.com/oauth/access_token?'
+            . 'client_id=' . urlencode($this->config->Facebook->appId)
+            . '&redirect_uri=' . urlencode($this->session->lastUri)
+            . '&client_secret=' . urlencode($this->config->Facebook->secret)
+            . '&code=' . urlencode($code);
+        $response = $this->httpService->get($requestUrl);
+        $parts = explode('&', $response->getBody(), 2);
+        $parts = explode('=', $parts[0], 2);
+        return isset($parts[1]) ? $parts[1] : null;
+    }
+
+    /**
+     * Given an access token, look up user details.
+     *
+     * @param string $accessToken Access token
+     *
+     * @return array
+     */
+    protected function getDetailsFromAccessToken($accessToken)
+    {
+        $request = 'https://graph.facebook.com/v2.2/me?'
+            . '&access_token=' . urlencode($accessToken);
+        $response = $this->httpService->get($request);
+        $json = json_decode($response->getBody());
+        return $json;
+    }
+}
-- 
GitLab