From fb714b39cc6165e28d3f5418d1a56d8c00cad5d3 Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Tue, 19 Aug 2014 15:30:36 -0400
Subject: [PATCH] Fixed Voyager authentication using non-latin characters in
 password.

---
 .../VuFind/src/VuFind/ILS/Driver/Voyager.php  | 84 +++++++++++--------
 1 file changed, 51 insertions(+), 33 deletions(-)

diff --git a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
index ffd39a2d24c..16b8a8d60a0 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
@@ -1218,48 +1218,66 @@ class Voyager extends AbstractBase
         $login_field = isset($this->config['Catalog']['login_field'])
             ? $this->config['Catalog']['login_field'] : 'LAST_NAME';
         $login_field = preg_replace('/[^\w]/', '', $login_field);
-
-        $sql = "SELECT PATRON.PATRON_ID, PATRON.FIRST_NAME, PATRON.LAST_NAME " .
-               "FROM $this->dbName.PATRON, $this->dbName.PATRON_BARCODE " .
-               "WHERE PATRON.PATRON_ID = PATRON_BARCODE.PATRON_ID AND " .
-               "lower(PATRON_BARCODE.PATRON_BARCODE) = :barcode AND ";
-        if (isset($this->config['Catalog']['fallback_login_field'])) {
-            $fallback_login_field = preg_replace(
+        $fallback_login_field
+            = isset($this->config['Catalog']['fallback_login_field'])
+            ? preg_replace(
                 '/[^\w]/', '', $this->config['Catalog']['fallback_login_field']
-            );
-            $sql .= "(lower(PATRON.{$login_field}) = :login OR " .
-                    "(PATRON.{$login_field} IS NULL AND " .
-                    "lower(PATRON.{$fallback_login_field}) = :login))";
-        } else {
-            $sql .= "lower(PATRON.{$login_field}) = :login";
-        }
+            ) : '';
+
+        // Turns out it's difficult and inefficient to handle the mismatching
+        // character sets of the Voyager database in the query (in theory something
+        // like
+        // "UPPER(UTL_I18N.RAW_TO_NCHAR(UTL_RAW.CAST_TO_RAW(field), 'WE8ISO8859P1'))"
+        // could be used, but it's SLOW and ugly). We'll rely on the fact that the
+        // barcode shouldn't contain any characters outside the basic latin
+        // characters and check login verification fields here.
+
+        $sql = "SELECT PATRON.PATRON_ID, PATRON.FIRST_NAME, PATRON.LAST_NAME, " .
+               "PATRON.{$login_field} as LOGIN";
+        if ($fallback_login_field) {
+            $sql .= ", PATRON.{$fallback_login_field} as FALLBACK_LOGIN";
+        }
+        $sql .= " FROM $this->dbName.PATRON, $this->dbName.PATRON_BARCODE " .
+               "WHERE PATRON.PATRON_ID = PATRON_BARCODE.PATRON_ID AND " .
+               "lower(PATRON_BARCODE.PATRON_BARCODE) = :barcode";
 
         try {
-            $bindLogin = strtolower(utf8_decode($login));
             $bindBarcode = strtolower(utf8_decode($barcode));
+            $compareLogin = mb_strtolower($login, 'UTF-8');
 
-            $this->debugSQL(__FUNCTION__, $sql, array(':login' => $bindLogin));
+            $this->debugSQL(__FUNCTION__, $sql, array(':barcode' => $bindBarcode));
             $sqlStmt = $this->db->prepare($sql);
-            $sqlStmt->bindParam(':login', $bindLogin, PDO::PARAM_STR);
             $sqlStmt->bindParam(':barcode', $bindBarcode, PDO::PARAM_STR);
             $sqlStmt->execute();
-            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
-            if (isset($row['PATRON_ID']) && ($row['PATRON_ID'] != '')) {
-                return array(
-                    'id' => utf8_encode($row['PATRON_ID']),
-                    'firstname' => utf8_encode($row['FIRST_NAME']),
-                    'lastname' => utf8_encode($row['LAST_NAME']),
-                    'cat_username' => $barcode,
-                    'cat_password' => $login,
-                    // There's supposed to be a getPatronEmailAddress stored
-                    // procedure in Oracle, but I couldn't get it to work here;
-                    // might be worth investigating further if needed later.
-                    'email' => null,
-                    'major' => null,
-                    'college' => null);
-            } else {
-                return null;
+            // For some reason barcode is not unique, so evaluate all resulting
+            // rows just to be safe
+            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
+                $primary = !is_null($row['LOGIN'])
+                    ? mb_strtolower(utf8_encode($row['LOGIN']), 'UTF-8')
+                    : null;
+                $fallback = $fallback_login_field && is_null($row['LOGIN'])
+                    ? mb_strtolower(utf8_encode($row['FALLBACK_LOGIN']), 'UTF-8')
+                    : null;
+
+                if ((!is_null($primary) && $primary == $compareLogin)
+                    || ($fallback_login_field && is_null($primary)
+                    && $fallback == $compareLogin)
+                ) {
+                    return array(
+                        'id' => utf8_encode($row['PATRON_ID']),
+                        'firstname' => utf8_encode($row['FIRST_NAME']),
+                        'lastname' => utf8_encode($row['LAST_NAME']),
+                        'cat_username' => $barcode,
+                        'cat_password' => $login,
+                        // There's supposed to be a getPatronEmailAddress stored
+                        // procedure in Oracle, but I couldn't get it to work here;
+                        // might be worth investigating further if needed later.
+                        'email' => null,
+                        'major' => null,
+                        'college' => null);
+                }
             }
+            return null;
         } catch (PDOException $e) {
             throw new ILSException($e->getMessage());
         }
-- 
GitLab