From 7552ef1baba78c71a9281e7e06a6b90a9cb6faed Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Tue, 4 Oct 2016 16:15:01 +0300
Subject: [PATCH] EZproxy ticket authentication handler. (#818)

- This allows a user to be authorized into EZproxy via VuFind login.
---
 config/vufind/config.ini                      |  18 +++
 config/vufind/permissions.ini                 |   6 +
 languages/en-gb.ini                           |   1 +
 languages/en.ini                              |   4 +
 languages/fi.ini                              |   4 +
 languages/sv.ini                              |   4 +
 module/VuFind/config/module.config.php        |   1 +
 .../Controller/ExternalAuthController.php     | 119 ++++++++++++++++++
 .../templates/externalauth/ezproxylogin.phtml |  18 +++
 .../templates/externalauth/ezproxylogin.phtml |  19 +++
 10 files changed, 194 insertions(+)
 create mode 100644 module/VuFind/src/VuFind/Controller/ExternalAuthController.php
 create mode 100644 themes/bootstrap3/templates/externalauth/ezproxylogin.phtml
 create mode 100644 themes/jquerymobile/templates/externalauth/ezproxylogin.phtml

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 91923724fb6..414f72574b5 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -994,6 +994,24 @@ replace_other_urls = true
 ; are using EZProxy to provide off-site access to online materials.
 ;[EZproxy]
 ;host            = http://proxy.myuniversity.edu
+; Uncomment the following line and change the password to something secret to enable 
+; EZproxy ticket authentication.
+;secret = "verysecretpassword"
+;
+; To enable ticket authentication in EZproxy, you will also need the following in 
+; EZproxy's user.txt or ezproxy.usr for older versions (without the leading 
+; semicolons and spaces):
+;
+; ::CGI=https://vufind-server/ExternalAuth/EzproxyLogin?url=^R 
+; ::Ticket
+; TimeValid 10
+; SHA512 verysecretpassword
+;
+; Uncomment and modify the following line to use another hashing algorithm with the
+; EZproxy authentication if necessary. SHA512 is the default, but it requires at 
+; least EZproxy version 6.1. Use "SHA1" for older EZproxy versions, and remember to
+; replace SHA512 with SHA1 also in EZproxy's configuration file.
+;secret_hash_method = "SHA512"
 
 ; These settings affect RefWorks record exports.  They rarely need to be changed.
 [RefWorks]
diff --git a/config/vufind/permissions.ini b/config/vufind/permissions.ini
index 2395196637a..d61244aa0fd 100644
--- a/config/vufind/permissions.ini
+++ b/config/vufind/permissions.ini
@@ -139,3 +139,9 @@ permission = access.StaffViewTab
 ;require = ANY
 ;ipRange[] = '127.0.0.1'
 ;ipRange[] = '::1'
+
+; Example EZproxy authorization permission.
+; See https://vufind.org/wiki/configuration:ezproxy for more information.
+[ezproxy.authorized]
+permission = ezproxy.authorized
+role = loggedin
diff --git a/languages/en-gb.ini b/languages/en-gb.ini
index a6287be7592..8902ab2bf53 100644
--- a/languages/en-gb.ini
+++ b/languages/en-gb.ini
@@ -18,6 +18,7 @@ delete_selected_favorites = "Delete Selected Favourites"
 email_selected_favorites = "Email Selected Favourites"
 Export Favorites = "Export Favourites"
 export_selected_favorites = "Export Selected Favourites"
+external_auth_unauthorized = "You are not authorised to access licensed material"
 fav_delete = "Delete Selected Favourites"
 fav_delete_deleting = "Your favourite(s) are being deleted."
 fav_delete_fail = "Sorry, an error has occurred. Your favourite(s) were not deleted."
diff --git a/languages/en.ini b/languages/en.ini
index d2940ce5040..829b42c7c1f 100644
--- a/languages/en.ini
+++ b/languages/en.ini
@@ -346,6 +346,10 @@ export_selected = "Export Selected"
 export_selected_favorites = "Export Selected Favorites"
 export_success = "Export Complete"
 export_unsupported_format = "Unsupported Export Format"
+external_auth_heading = "Access to licensed material"
+external_auth_login_message = "Login to access licensed material"
+external_auth_unauthorized = "You are not authorized to access licensed material"
+external_auth_unauthorized_desc = "Your login method does not provide access to licensed material. Please log out and then log in using another method."
 FAQs = "FAQs"
 fav_delete = "Delete Selected Favorites"
 fav_delete_deleting = "Your favorite(s) are being deleted."
diff --git a/languages/fi.ini b/languages/fi.ini
index ff74fc95490..42c03d555f1 100644
--- a/languages/fi.ini
+++ b/languages/fi.ini
@@ -350,6 +350,10 @@ export_selected = "Vie valitut"
 export_selected_favorites = "Vie valitut suosikit"
 export_success = "Vienti valmis"
 export_unsupported_format = "Vienti valitussa muodossa ei onnistu"
+external_auth_heading = "Pääsy lisensioituun aineistoon"
+external_auth_login_message = "Kirjaudu sisään päästäksesi lisensioituun aineistoon"
+external_auth_unauthorized = "Sinulla ei ole käyttöoikeutta lisensioituun aineistoon"
+external_auth_unauthorized_desc = "Käyttämälläsi kirjautumistavalla ei ole pääsyä lisensioituun aineistoon. Kirjaudu ensin ulos ja käytä sitten toista kirjautumistapaa."
 FAQs = "UKK:t"
 fav_delete = "Poista valitut suosikit"
 fav_delete_deleting = "Suosikkejasi poistetaan"
diff --git a/languages/sv.ini b/languages/sv.ini
index 412a9cadda0..9642a6e99ae 100644
--- a/languages/sv.ini
+++ b/languages/sv.ini
@@ -345,6 +345,10 @@ export_selected = "Exportera valda"
 export_selected_favorites = "Exportera valda favoriter"
 export_success = "Exporten färdig"
 export_unsupported_format = "Export i det valda formatet är inte möjlig"
+external_auth_heading = "Behörighet att komma åt licensierat material"
+external_auth_login_message = "Logga in för att komma åt licensierat material"
+external_auth_unauthorized = "Du har inte behörighet att komma åt licensierat material"
+external_auth_unauthorized_desc = "Du kan inte komma åt licensierat material med inloggningsmetoden du har använt. Logga ut först och sedan logga in med en annan metod."
 FAQs = "Vanliga frågor"
 fav_delete = "Radera valda favoriter"
 fav_delete_deleting = "Dina favoriter raderas"
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 10ee1fafd90..554018c4786 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -112,6 +112,7 @@ $config = [
             'eit' => 'VuFind\Controller\EITController',
             'eitrecord' => '\VuFind\Controller\EITrecordController',
             'error' => 'VuFind\Controller\ErrorController',
+            'externalauth' => 'VuFind\Controller\ExternalAuthController',
             'feedback' => 'VuFind\Controller\FeedbackController',
             'help' => 'VuFind\Controller\HelpController',
             'hierarchy' => 'VuFind\Controller\HierarchyController',
diff --git a/module/VuFind/src/VuFind/Controller/ExternalAuthController.php b/module/VuFind/src/VuFind/Controller/ExternalAuthController.php
new file mode 100644
index 00000000000..76f03f03466
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/ExternalAuthController.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * External Authentication/Authorization Controller
+ *
+ * PHP Version 5
+ *
+ * Copyright (C) The National Library of Finland 2016.
+ *
+ * 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 VuFind
+ * @package  Controller
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
+ */
+namespace VuFind\Controller;
+
+/**
+ * External Authentication/Authorization Controller
+ *
+ * Provides authorization support for external systems, e.g. EZproxy
+ *
+ * @category VuFind
+ * @package  Controller
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
+ */
+class ExternalAuthController extends AbstractBase
+{
+    /**
+     * Permission from permissions.ini required for EZProxy authorization.
+     *
+     * @var string
+     */
+    protected $ezproxyRequiredPermission = 'ezproxy.authorized';
+
+    /**
+     * Provides an EZproxy session to an authorized user
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function ezproxyLoginAction()
+    {
+        $config = $this->getConfig();
+        if (empty($config->EZproxy->host)) {
+            throw new \Exception('EZproxy host not defined in configuration');
+        }
+
+        $user = $this->getUser();
+        if ($user) {
+            // Logged in, check for authorization
+            $authService = $this->getServiceLocator()
+                ->get('ZfcRbac\Service\AuthorizationService');
+            if (!$authService->isGranted($this->ezproxyRequiredPermission)) {
+                $view = $this->createViewModel();
+                $view->unauthorized = true;
+                $this->flashMessenger()->addErrorMessage(
+                    'external_auth_unauthorized'
+                );
+                return $view;
+            }
+            $username = end(explode(':', $user->username, 2));
+            $url = $this->params()->fromPost(
+                'url', $this->params()->fromQuery('url')
+            );
+            return $this->redirect()->toUrl(
+                $this->createEzproxyTicketUrl($username, $url)
+            );
+        }
+        return $this->forceLogin('external_auth_login_message');
+    }
+
+    /**
+     * Create a ticket login URL for EZproxy
+     *
+     * @param string $user User name to pass on to EZproxy
+     * @param string $url  The original URL
+     *
+     * @return string EZproxy URL
+     *
+     * @throws \Exception
+     * @see    https://www.oclc.org/support/services/ezproxy/documentation/usr
+     * /ticket/php.en.html
+     */
+    protected function createEzproxyTicketUrl($user, $url)
+    {
+        $config = $this->getConfig();
+        if (empty($config->EZproxy->secret)) {
+            throw new \Exception('EZproxy secret not defined in configuration');
+        }
+
+        $packet = '$u' . time() . '$e';
+        $hash = new \Zend\Crypt\Hash();
+        $algorithm = !empty($config->EZproxy->secret_hash_method)
+            ? $config->EZproxy->secret_hash_method : 'SHA512';
+        $ticket = $config->EZproxy->secret . $user . $packet;
+        $ticket = $hash->compute($algorithm, $ticket);
+        $ticket .= $packet;
+        $params = http_build_query(
+            ['user' => $user, 'ticket' => $ticket, 'url' => $url]
+        );
+        return $config->EZproxy->host . "/login?$params";
+    }
+}
diff --git a/themes/bootstrap3/templates/externalauth/ezproxylogin.phtml b/themes/bootstrap3/templates/externalauth/ezproxylogin.phtml
new file mode 100644
index 00000000000..3d3b404fcec
--- /dev/null
+++ b/themes/bootstrap3/templates/externalauth/ezproxylogin.phtml
@@ -0,0 +1,18 @@
+<?
+    // Set page title
+    $this->headTitle($this->translate('external_auth_heading'));
+?>
+
+<div class="row external-content-access">
+  <div class="col-sm-12">
+    <?=$this->flashmessages()?>
+    <? if ($this->unauthorized): ?>
+      <div class="unauthorized-description">
+        <p><?=$this->transEsc('external_auth_unauthorized_desc'); ?></p>
+      </div>
+      <div>
+        <a href="<?=$this->url('myresearch-logout')?>" class="logout btn btn-primary" title="<?=$this->transEsc("Log Out")?>"><strong><?=$this->transEsc("Log Out")?></strong></a>
+      </div>
+    <? endif; ?>
+  </div>
+</div>
diff --git a/themes/jquerymobile/templates/externalauth/ezproxylogin.phtml b/themes/jquerymobile/templates/externalauth/ezproxylogin.phtml
new file mode 100644
index 00000000000..15ad9488cc7
--- /dev/null
+++ b/themes/jquerymobile/templates/externalauth/ezproxylogin.phtml
@@ -0,0 +1,19 @@
+<?
+    // Set up page title:
+    $this->headTitle($this->translate('external_auth_heading'));
+?>
+<div data-role="page" id="EzProxy-login">
+  <?=$this->mobileMenu()->header()?>
+  <div data-role="content">
+    <?=$this->flashmessages()?>
+    <? if ($this->unauthorized): ?>
+      <div class="unauthorized-description">
+        <p><?=$this->transEsc('external_auth_unauthorized_desc'); ?></p>
+      </div>
+      <div>
+        <a href="<?=$this->url('myresearch-logout')?>" data-role="button" title="<?=$this->transEsc("Log Out")?>"><strong><?=$this->transEsc("Log Out")?></strong></a>
+      </div>
+    <? endif; ?>
+  </div>
+  <?=$this->mobileMenu()->footer()?>
+</div>
\ No newline at end of file
-- 
GitLab