From 07a4b68a0e825d505520691208b1237c277c28cc Mon Sep 17 00:00:00 2001 From: Tom <1525182@uni-wuppertal.de> Date: Wed, 15 Jul 2020 15:54:39 +0200 Subject: [PATCH 1/5] Added PDOBasicAuth Backend --- lib/DAV/Auth/Backend/PDOBasicAuth.php | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 lib/DAV/Auth/Backend/PDOBasicAuth.php diff --git a/lib/DAV/Auth/Backend/PDOBasicAuth.php b/lib/DAV/Auth/Backend/PDOBasicAuth.php new file mode 100644 index 0000000000..1430a86afa --- /dev/null +++ b/lib/DAV/Auth/Backend/PDOBasicAuth.php @@ -0,0 +1,102 @@ +pdo = $pdo; + if (isset($options['tableName'])) { + $this->tableName = $options['tableName']; + } else { + $this->tableName = 'user'; + } + if (isset($options['digestColumn'])) { + $this->digestColumn = $options['digestColumn']; + } else { + $this->digestColumn = 'digest'; + } + if (isset($options['digestPrefix'])) { + $this->digestPrefix = $options['digestPrefix']; + } + } + + /** + * Validates a username and password. + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * + * @return bool + */ + public function validateUserPass($username, $password) + { + $stmt = $this->pdo->prepare('SELECT '.$this->digestColumn.' FROM '.$this->tableName.' WHERE email = ?'); + $stmt->execute([$username]); + $result = $stmt->fetchAll(); + + if (!count($result)) { + return false; + } + + $digest = $result[0]['password']; + + if (isset($this->digestPrefix)) { + $digest = substr($digest, strlen($this->digestPrefix)); + } + + if (password_verify($password, $digest)) { + return true; + } + + return false; + } +} From bc7a8c06f26bd8b6795f26240e8478704a3227d9 Mon Sep 17 00:00:00 2001 From: tom Date: Sat, 18 Jul 2020 14:14:13 +0200 Subject: [PATCH 2/5] Added Unit Test --- lib/DAV/Auth/Backend/PDOBasicAuth.php | 19 +++- .../Auth/Backend/AbstractPDOBasicAuthTest.php | 107 ++++++++++++++++++ .../Auth/Backend/PDOBasicAuthSqliteTest.php | 10 ++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php create mode 100644 tests/Sabre/DAV/Auth/Backend/PDOBasicAuthSqliteTest.php diff --git a/lib/DAV/Auth/Backend/PDOBasicAuth.php b/lib/DAV/Auth/Backend/PDOBasicAuth.php index 1430a86afa..2d71db08da 100644 --- a/lib/DAV/Auth/Backend/PDOBasicAuth.php +++ b/lib/DAV/Auth/Backend/PDOBasicAuth.php @@ -33,6 +33,14 @@ class PDOBasicAuth extends AbstractBasic */ protected $digestColumn; + /** + * PDO uuid(unique user identifier) column name we'll be using + * (i.e. username, email). + * + * @var string + */ + protected $uuidColumn; + /** * Digest prefix: * if the backend you are using for is prefixing @@ -54,13 +62,18 @@ public function __construct(\PDO $pdo, array $options = []) if (isset($options['tableName'])) { $this->tableName = $options['tableName']; } else { - $this->tableName = 'user'; + $this->tableName = 'users'; } if (isset($options['digestColumn'])) { $this->digestColumn = $options['digestColumn']; } else { $this->digestColumn = 'digest'; } + if (isset($options['uuidcolumn'])) { + $this->uuidColumn = $options['uuidColumn']; + } else { + $this->uuidColumn = 'username'; + } if (isset($options['digestPrefix'])) { $this->digestPrefix = $options['digestPrefix']; } @@ -79,7 +92,7 @@ public function __construct(\PDO $pdo, array $options = []) */ public function validateUserPass($username, $password) { - $stmt = $this->pdo->prepare('SELECT '.$this->digestColumn.' FROM '.$this->tableName.' WHERE email = ?'); + $stmt = $this->pdo->prepare('SELECT '.$this->digestColumn.' FROM '.$this->tableName.' WHERE '.$this->uuidColumn.' = ?'); $stmt->execute([$username]); $result = $stmt->fetchAll(); @@ -87,7 +100,7 @@ public function validateUserPass($username, $password) return false; } - $digest = $result[0]['password']; + $digest = $result[0][$this->digestColumn]; if (isset($this->digestPrefix)) { $digest = substr($digest, strlen($this->digestPrefix)); diff --git a/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php new file mode 100644 index 0000000000..0b387a8b2a --- /dev/null +++ b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php @@ -0,0 +1,107 @@ +dropTables('users'); + $this->createSchema('users'); + + // The supplied hash is a salted bcrypt hash of the plaintext : 'password' + $this->getPDO()->query( + "INSERT INTO users (username,digesta1) VALUES ('user','\$2b\$12\$IwetRH4oj6.AWFGGVy8fpet7Pgp1TafspB6iq1/fiLDxfsGZfi2jS')" + ); + } + + public function testConstruct() + { + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo); + $this->assertTrue($backend instanceof PDOBasicAuth); + } + + public function testCheckNoHeaders() + { + $request = new HTTP\Request('GET', '/'); + $response = new HTTP\Response(); + + $options = [ + 'tableName' => 'users', + 'digestColumn' => 'digesta1', + ]; + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo, $options); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + } + + public function testCheckUnknownUser() + { + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/', + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'wrongpassword', + ]); + $response = new HTTP\Response(); + + $options = [ + 'tableName' => 'users', + 'digestColumn' => 'digesta1', + ]; + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo, $options); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + } + + public function testCheckSuccess() + { + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/', + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'password', + ]); + $response = new HTTP\Response(); + + $options = [ + 'tableName' => 'users', + 'digestColumn' => 'digesta1', + ]; + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo, $options); + $this->assertEquals( + [true, 'principals/user'], + $backend->check($request, $response) + ); + } + + public function testRequireAuth() + { + $request = new HTTP\Request('GET', '/'); + $response = new HTTP\Response(); + + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertEquals( + 'Basic realm="writing unittests on a saturday night", charset="UTF-8"', + $response->getHeader('WWW-Authenticate') + ); + } +} diff --git a/tests/Sabre/DAV/Auth/Backend/PDOBasicAuthSqliteTest.php b/tests/Sabre/DAV/Auth/Backend/PDOBasicAuthSqliteTest.php new file mode 100644 index 0000000000..e961bf0abb --- /dev/null +++ b/tests/Sabre/DAV/Auth/Backend/PDOBasicAuthSqliteTest.php @@ -0,0 +1,10 @@ + Date: Sat, 18 Jul 2020 16:53:39 +0200 Subject: [PATCH 3/5] Extended tests with uuidColumn --- lib/DAV/Auth/Backend/PDOBasicAuth.php | 2 +- tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/DAV/Auth/Backend/PDOBasicAuth.php b/lib/DAV/Auth/Backend/PDOBasicAuth.php index 2d71db08da..8009fe6392 100644 --- a/lib/DAV/Auth/Backend/PDOBasicAuth.php +++ b/lib/DAV/Auth/Backend/PDOBasicAuth.php @@ -69,7 +69,7 @@ public function __construct(\PDO $pdo, array $options = []) } else { $this->digestColumn = 'digest'; } - if (isset($options['uuidcolumn'])) { + if (isset($options['uuidColumn'])) { $this->uuidColumn = $options['uuidColumn']; } else { $this->uuidColumn = 'username'; diff --git a/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php index 0b387a8b2a..5f61e7597b 100644 --- a/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php +++ b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php @@ -36,6 +36,7 @@ public function testCheckNoHeaders() $options = [ 'tableName' => 'users', 'digestColumn' => 'digesta1', + 'uuidColumn' => 'username', ]; $pdo = $this->getPDO(); $backend = new PDOBasicAuth($pdo, $options); @@ -58,6 +59,7 @@ public function testCheckUnknownUser() $options = [ 'tableName' => 'users', 'digestColumn' => 'digesta1', + 'uuidColumn' => 'username', ]; $pdo = $this->getPDO(); $backend = new PDOBasicAuth($pdo, $options); @@ -80,6 +82,7 @@ public function testCheckSuccess() $options = [ 'tableName' => 'users', 'digestColumn' => 'digesta1', + 'uuidColumn' => 'username', ]; $pdo = $this->getPDO(); $backend = new PDOBasicAuth($pdo, $options); From 48d979fa1cd63be3b74bd372b968a01e244f4e0c Mon Sep 17 00:00:00 2001 From: tom Date: Sat, 18 Jul 2020 17:17:39 +0200 Subject: [PATCH 4/5] Improved Coverage --- lib/DAV/Auth/Backend/PDOBasicAuth.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/DAV/Auth/Backend/PDOBasicAuth.php b/lib/DAV/Auth/Backend/PDOBasicAuth.php index 8009fe6392..a2c03e2af0 100644 --- a/lib/DAV/Auth/Backend/PDOBasicAuth.php +++ b/lib/DAV/Auth/Backend/PDOBasicAuth.php @@ -6,7 +6,6 @@ * This is an authentication backend that uses a database to manage passwords. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class PDOBasicAuth extends AbstractBasic @@ -76,6 +75,8 @@ public function __construct(\PDO $pdo, array $options = []) } if (isset($options['digestPrefix'])) { $this->digestPrefix = $options['digestPrefix']; + } else { + $this->digestPrefix = null; } } @@ -98,18 +99,20 @@ public function validateUserPass($username, $password) if (!count($result)) { return false; - } + } else { + $digest = $result[0][$this->digestColumn]; - $digest = $result[0][$this->digestColumn]; + if (null != $this->digestPrefix) { + $digest = substr($digest, strlen($this->digestPrefix)); + } else { + $digest = $digest; + } - if (isset($this->digestPrefix)) { - $digest = substr($digest, strlen($this->digestPrefix)); - } + if (password_verify($password, $digest)) { + return true; + } - if (password_verify($password, $digest)) { - return true; + return false; } - - return false; } } From ef009c52e44ecbe277466fb4cd98dafc6dacefeb Mon Sep 17 00:00:00 2001 From: tom Date: Mon, 20 Jul 2020 12:51:03 +0200 Subject: [PATCH 5/5] Added More Tests --- lib/DAV/Auth/Backend/PDOBasicAuth.php | 6 +-- .../Auth/Backend/AbstractPDOBasicAuthTest.php | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/lib/DAV/Auth/Backend/PDOBasicAuth.php b/lib/DAV/Auth/Backend/PDOBasicAuth.php index a2c03e2af0..39324e4db8 100644 --- a/lib/DAV/Auth/Backend/PDOBasicAuth.php +++ b/lib/DAV/Auth/Backend/PDOBasicAuth.php @@ -75,8 +75,6 @@ public function __construct(\PDO $pdo, array $options = []) } if (isset($options['digestPrefix'])) { $this->digestPrefix = $options['digestPrefix']; - } else { - $this->digestPrefix = null; } } @@ -102,10 +100,8 @@ public function validateUserPass($username, $password) } else { $digest = $result[0][$this->digestColumn]; - if (null != $this->digestPrefix) { + if (isset($this->digestPrefix)) { $digest = substr($digest, strlen($this->digestPrefix)); - } else { - $digest = $digest; } if (password_verify($password, $digest)) { diff --git a/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php index 5f61e7597b..b631bd4424 100644 --- a/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php +++ b/tests/Sabre/DAV/Auth/Backend/AbstractPDOBasicAuthTest.php @@ -19,6 +19,9 @@ public function setup(): void $this->getPDO()->query( "INSERT INTO users (username,digesta1) VALUES ('user','\$2b\$12\$IwetRH4oj6.AWFGGVy8fpet7Pgp1TafspB6iq1/fiLDxfsGZfi2jS')" ); + $this->getPDO()->query( + "INSERT INTO users (username,digesta1) VALUES ('prefix_user','bcrypt\$\$2b\$12\$IwetRH4oj6.AWFGGVy8fpet7Pgp1TafspB6iq1/fiLDxfsGZfi2jS')" + ); } public function testConstruct() @@ -47,6 +50,29 @@ public function testCheckNoHeaders() } public function testCheckUnknownUser() + { + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/', + 'PHP_AUTH_USER' => 'unkown_user', + 'PHP_AUTH_PW' => 'wrongpassword', + ]); + $response = new HTTP\Response(); + + $options = [ + 'tableName' => 'users', + 'digestColumn' => 'digesta1', + 'uuidColumn' => 'username', + ]; + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo, $options); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + } + + public function testCheckAuthenticationFailure() { $request = HTTP\Sapi::createFromServerArray([ 'REQUEST_METHOD' => 'GET', @@ -92,6 +118,30 @@ public function testCheckSuccess() ); } + public function testPrefixSuccess() + { + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/', + 'PHP_AUTH_USER' => 'prefix_user', + 'PHP_AUTH_PW' => 'password', + ]); + $response = new HTTP\Response(); + + $options = [ + 'tableName' => 'users', + 'digestColumn' => 'digesta1', + 'uuidColumn' => 'username', + 'digestPrefix' => 'bcrypt$', + ]; + $pdo = $this->getPDO(); + $backend = new PDOBasicAuth($pdo, $options); + $this->assertEquals( + [true, 'principals/prefix_user'], + $backend->check($request, $response) + ); + } + public function testRequireAuth() { $request = new HTTP\Request('GET', '/');