From cb8f44cc0c392480e3867c637b252f1f79782852 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Thu, 12 Jul 2012 10:31:12 -0400
Subject: [PATCH] Added remaining authentication options (Shibboleth still
 needs some work).

---
 module/VuFind/src/VuFind/Auth/MultiAuth.php  | 185 +++++++++++++++++
 module/VuFind/src/VuFind/Auth/Shibboleth.php | 203 +++++++++++++++++++
 2 files changed, 388 insertions(+)
 create mode 100644 module/VuFind/src/VuFind/Auth/MultiAuth.php
 create mode 100644 module/VuFind/src/VuFind/Auth/Shibboleth.php

diff --git a/module/VuFind/src/VuFind/Auth/MultiAuth.php b/module/VuFind/src/VuFind/Auth/MultiAuth.php
new file mode 100644
index 00000000000..bd5b1976e4e
--- /dev/null
+++ b/module/VuFind/src/VuFind/Auth/MultiAuth.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * MultiAuth Authentication plugin
+ *
+ * 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   Sam Moffatt <vufind-tech@lists.sourceforge.net>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/building_an_authentication_handler Wiki
+ */
+namespace VuFind\Auth;
+use VuFind\Exception\Auth as AuthException;
+
+/**
+ * MultiAuth Authentication plugin
+ *
+ * This module enables chaining of multiple authentication plugins.  Authentication
+ * plugins are executed in order, and the first successful authentication is
+ * returned with the rest ignored.  The last error message is used to be returned
+ * to the calling function.
+ *
+ * The plugin works by being defined as the authentication handler for the system
+ * and then defining its own order for plugins.  For example, you could edit
+ * config.ini like this:
+ *
+ * [Authentication]
+ * method = MultiAuth
+ *
+ * [MultiAuth]
+ * method_order = "ILS,LDAP"
+ * filters = "username:strtoupper,username:trim,password:trim"
+ *
+ * This example uses a combination of ILS and LDAP authentication, checking the ILS
+ * first and then failing over to LDAP.
+ *
+ * The filters follow the format fieldname:PHP string function, where fieldname is
+ * either "username" or "password."  In the example, we uppercase the username and
+ * trim the username and password fields. This is done to enable common filtering
+ * before handing off to the authentication handlers.
+ *
+ * @category VuFind2
+ * @package  Authentication
+ * @author   Sam Moffatt <vufind-tech@lists.sourceforge.net>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/building_an_authentication_handler Wiki
+ */
+class MultiAuth extends AbstractBase
+{
+    protected $filters = array();
+    protected $methods;
+    protected $username;
+    protected $password;
+
+    /**
+     * Constructor
+     *
+     * @param object $config Optional configuration object to pass through (loads
+     * default configuration if none specified).
+     */
+    public function __construct($config = null)
+    {
+        parent::__construct($config);
+
+        if (!isset($config->MultiAuth) || !isset($config->MultiAuth->method_order)
+            || !strlen($config->MultiAuth->method_order)
+        ) {
+            throw new AuthException(
+                "One or more MultiAuth parameters are missing. " .
+                "Check your config.ini!"
+            );
+        }
+        $this->methods = explode(',', $config->MultiAuth->method_order);
+        if (isset($config->MultiAuth->filters)
+            && strlen($config->MultiAuth->filters)
+        ) {
+            $this->filters = explode(',', $config->MultiAuth->filters);
+        }
+    }
+
+    /**
+     * 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)
+    {
+        $this->filterCredentials($request);
+
+        // Check for empty credentials before we do any extra work:
+        if ($this->username == '' || $this->password == '') {
+            throw new AuthException('authentication_error_blank');
+        }
+
+        // Update request with our filtered credentials:
+        $request->getPost()->set('username', $this->username);
+        $request->getPost()->set('password', $this->password);
+
+        // Do the actual authentication work:
+        return $this->authUser($request);
+    }
+
+    /**
+     * Load credentials into the object and apply internal filter settings to them.
+     *
+     * @param \Zend\Http\PhpEnvironment\Request $request Request object containing
+     * account credentials.
+     *
+     * @return void
+     */
+    protected function filterCredentials($request)
+    {
+        $this->username = $request->getPost()->get('username');
+        $this->password = $request->getPost()->get('password');
+
+        foreach ($this->filters as $filter) {
+            $parts = explode(':', $filter);
+            $property = trim($parts[0]);
+            if (isset($this->$property)) {
+                $this->$property = call_user_func(trim($parts[1]), $this->$property);
+            }
+        }
+    }
+
+    /**
+     * Do the actual work of authenticating the user (support method for
+     * authenticate()).
+     *
+     * @param \Zend\Http\PhpEnvironment\Request $request Request object containing
+     * account credentials.
+     *
+     * @throws AuthException
+     * @return \VuFind\Db\Row\User Object representing logged-in user.
+     */
+    protected function authUser($request)
+    {
+        // Try authentication methods until we find one that works:
+        foreach ($this->methods as $method) {
+            $authenticator = Factory::getAuth(trim($method), $this->config);
+            try {
+                $user = $authenticator->authenticate($request);
+
+                // If we got this far without throwing an exception, we can break
+                // out of the loop -- we are logged in!
+                break;
+            } catch (AuthException $exception) {
+                // Do nothing -- just keep looping!  We'll deal with the exception
+                // below if we don't find a successful login anywhere.
+            }
+        }
+
+        // At this point, there are three possibilities: $user is a valid,
+        // logged-in user; $exception is an Exception that we need to forward
+        // along; or both variables are undefined, indicating that $this->methods
+        // is empty and thus something is wrong!
+        if (!isset($user)) {
+            if (isset($exception)) {
+                throw $exception;
+            } else {
+                throw new AuthException('authentication_error_technical');
+            }
+        }
+        return $user;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Auth/Shibboleth.php b/module/VuFind/src/VuFind/Auth/Shibboleth.php
new file mode 100644
index 00000000000..911a02cc1eb
--- /dev/null
+++ b/module/VuFind/src/VuFind/Auth/Shibboleth.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Shibboleth 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\Db\Table\User as UserTable, VuFind\Exception\Auth as AuthException;
+
+/**
+ * Shibboleth 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 Shibboleth extends AbstractBase
+{
+    /**
+     * 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)
+    {
+        // Throw an exception if the required username setting is missing.
+        $shib = $this->config->Shibboleth;
+        if (!isset($shib->username) || empty($shib->username)) {
+            throw new AuthException(
+                "Shibboleth username is missing in your configuration file."
+            );
+        }
+
+        // Check if username is set.
+        $username = $request->getServer()->get($shib->username);
+        if (empty($username)) {
+            throw new AuthException('authentication_error_admin');
+        }
+
+        // Check if required attributes match up:
+        foreach ($this->getRequiredAttributes() as $key => $value) {
+            if (!preg_match('/'. $value .'/', $request->getServer()->get($key))) {
+                throw new AuthException('authentication_error_denied');
+            }
+        }
+
+        // If we made it this far, we should log in the user!
+        $table = new UserTable();
+        $user = $table->getByUsername($username);
+
+        // Has the user configured attributes to use for populating the user table?
+        $attribsToCheck = array(
+            "cat_username", "email", "lastname", "firstname", "college", "major",
+            "home_library"
+        );
+        foreach ($attribsToCheck as $attribute) {
+            if (isset($shib->$attribute)) {
+                $user->$attribute = $request->getServer()->get($shib->$attribute);
+            }
+        }
+
+        // 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.
+     *
+     * @return bool|string
+     */
+    public function getSessionInitiator()
+    {
+        if (!isset($this->config->Shibboleth->login)) {
+            throw new AuthException(
+                'Shibboleth login configuration parameter is not set.'
+            );
+        }
+
+        if (isset($this->config->Shibboleth->target)) {
+            $shibTarget = $this->config->Shibboleth->target;
+        } else {
+            /* TODO -- perhaps this should be passed in as a parameter
+            $myRes = isset($this->config->Site->defaultLoggedInModule)
+                ? $this->config->Site->defaultLoggedInModule : 'MyResearch';
+            $urlOptions = array('controller' => $myRes, 'action' => 'Home');
+            $router = Zend_Controller_Front::getInstance()->getRouter();
+            $shibTarget = VF_Url::getBaseUrl()
+                . $router->assemble($urlOptions, 'default', false, false);
+             */
+        }
+        $sessionInitiator = $this->config->Shibboleth->login
+            . '?target=' . urlencode($shibTarget);
+
+        if (isset($this->config->Shibboleth->provider_id)) {
+            $sessionInitiator = $sessionInitiator . '&providerId=' .
+                urlencode($this->config->Shibboleth->provider_id);
+        }
+
+        return $sessionInitiator;
+    }
+
+    /**
+     * Has the user's login expired?
+     *
+     * @return bool
+     */
+    public function isExpired()
+    {
+        if (isset($this->config->Shibboleth->username)
+            && isset($this->config->Shibboleth->logout)
+        ) {
+            // It would be more proper to call getServer on a Zend request
+            // object... except that the request object doesn't exist yet when
+            // this routine gets called.
+            $username = isset($_SERVER[$this->config->Shibboleth->username])
+                ? $_SERVER[$this->config->Shibboleth->username] : null;
+            return empty($username);
+        }
+        return false;
+    }
+
+    /**
+     * Perform cleanup at logout time.
+     *
+     * @param string $url URL to redirect user to after logging out.
+     *
+     * @return string     Redirect URL (usually same as $url, but modified in
+     * some authentication modules).
+     */
+    public function logout($url)
+    {
+        // If single log-out is enabled, use a special URL:
+        if (isset($this->config->Shibboleth->logout)
+            && !empty($this->config->Shibboleth->logout)
+        ) {
+            $url = $this->config->Shibboleth->logout . '?return=' . urlencode($url);
+        }
+
+        // Send back the redirect URL (possibly modified):
+        return $url;
+    }
+
+    /**
+     * Extract required user attributes from the configuration.
+     *
+     * @return array      Only username and attribute-related values
+     */
+    protected function getRequiredAttributes()
+    {
+        // Special case -- store username as-is to establish return array:
+        $sortedUserAttributes = array();
+
+        // Now extract user attribute values:
+        $shib = $this->config->Shibboleth;
+        foreach ($shib as $key => $value) {
+            if (preg_match("/userattribute_[0-9]{1,}/", $key)) {
+                $valueKey = 'userattribute_value_' . substr($key, 14);
+                $sortedUserAttributes[$value] = isset($shib->$valueKey)
+                    ? $shib->$valueKey : null;
+
+                // Throw an exception if attributes are missing/empty.
+                if (empty($sortedUserAttributes[$value])) {
+                    throw new AuthException(
+                        "User attribute value of " . $value. " is missing!"
+                    );
+                }
+            }
+        }
+
+        return $sortedUserAttributes;
+    }
+}
\ No newline at end of file
-- 
GitLab