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