From 87c69a8d0c722f59b3497bda8bf143face709e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 11:57:08 +0100 Subject: [PATCH 1/6] add Config class to source tree instead of as a dependency --- .phan/config.php | 14 ++ bin/BackendTest.php | 2 +- bin/addUsers.php | 2 +- composer.json | 1 - composer.lock | 133 +++--------------- src/fkooman/VootProvider/Config/Config.php | 101 +++++++++++++ .../VootProvider/Config/ConfigException.php | 23 +++ src/fkooman/VootProvider/LdapVootStorage.php | 2 +- src/fkooman/VootProvider/PdoVootStorage.php | 2 +- .../VootProvider/VootStorageException.php | 4 +- web/voot.php | 2 +- 11 files changed, 168 insertions(+), 118 deletions(-) create mode 100644 .phan/config.php create mode 100644 src/fkooman/VootProvider/Config/Config.php create mode 100644 src/fkooman/VootProvider/Config/ConfigException.php diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000..e45e0e6 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,14 @@ + [ + 'src', + 'web', + 'bin', + 'vendor/fkooman/php-lib-json', + 'vendor/fkooman/php-lib-rest', + ], + 'exclude_analysis_directory_list' => [ + 'vendor/', + ], +]; diff --git a/bin/BackendTest.php b/bin/BackendTest.php index 1ca334c..a76f642 100644 --- a/bin/BackendTest.php +++ b/bin/BackendTest.php @@ -23,7 +23,7 @@ exit(1); } -use fkooman\Config\Config; +use fkooman\VootProvider\Config\Config; $config = Config::fromIniFile(dirname(__DIR__) . '/config/voot.ini'); diff --git a/bin/addUsers.php b/bin/addUsers.php index 16d0f13..2141522 100644 --- a/bin/addUsers.php +++ b/bin/addUsers.php @@ -18,7 +18,7 @@ require_once dirname(__DIR__) . '/vendor/autoload.php'; -use fkooman\Config\Config; +use fkooman\VootProvider\Config\Config; use fkooman\VootProvider\PdoVootStorage; $config = Config::fromIniFile(dirname(__DIR__) . '/config/voot.ini'); diff --git a/composer.json b/composer.json index 79eefa6..01bd742 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,6 @@ "name": "fkooman/php-voot-provider", "require": { "ext-ldap": "*", - "fkooman/php-lib-config": "0.2.*", "fkooman/php-lib-rest": "0.3.*", "php": ">=5.3.3" } diff --git a/composer.lock b/composer.lock index 97b235f..93463d7 100644 --- a/composer.lock +++ b/composer.lock @@ -1,61 +1,23 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" ], - "hash": "1785566704b4c55332fe7d1ac5efa2b5", + "content-hash": "0c9d1f726ee35e607f1b54b87d7f93fe", "packages": [ - { - "name": "fkooman/php-lib-config", - "version": "0.2.0", - "source": { - "type": "git", - "url": "https://github.com/fkooman/php-lib-config.git", - "reference": "6ca3ad575728302621c9bd67ba75eb2048a3c5ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fkooman/php-lib-config/zipball/6ca3ad575728302621c9bd67ba75eb2048a3c5ca", - "reference": "6ca3ad575728302621c9bd67ba75eb2048a3c5ca", - "shasum": "" - }, - "require": { - "fkooman/php-lib-json": "0.1.0", - "php": ">=5.3.3", - "symfony/yaml": "2.3.*" - }, - "type": "library", - "autoload": { - "psr-0": { - "fkooman\\Config\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "François Kooman", - "email": "fkooman@tuxed.net", - "role": "Developer" - } - ], - "description": "PHP library to handle YAML, INI and JSON configuration files", - "time": "2013-08-09 14:13:52" - }, { "name": "fkooman/php-lib-json", - "version": "0.1.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/fkooman/php-lib-json.git", - "reference": "6aa1c9e6bc411eac8dfc38912e316114d12e8ff6" + "reference": "eb64350e01d8a3c11d70d82374ac1bb35a44b7c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fkooman/php-lib-json/zipball/6aa1c9e6bc411eac8dfc38912e316114d12e8ff6", - "reference": "6aa1c9e6bc411eac8dfc38912e316114d12e8ff6", + "url": "https://api.github.com/repos/fkooman/php-lib-json/zipball/eb64350e01d8a3c11d70d82374ac1bb35a44b7c9", + "reference": "eb64350e01d8a3c11d70d82374ac1bb35a44b7c9", "shasum": "" }, "require": { @@ -79,24 +41,25 @@ } ], "description": "PHP library to encode and decode JSON", - "time": "2013-05-30 12:04:47" + "abandoned": "fkooman/json", + "time": "2013-11-07T15:48:53+00:00" }, { "name": "fkooman/php-lib-rest", - "version": "0.3.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/fkooman/php-lib-rest.git", - "reference": "21e54e002811cec66be17dbbb2b14d27baa6759f" + "reference": "c3cf128e4878173b392ca577a65a2c36476f6565" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fkooman/php-lib-rest/zipball/21e54e002811cec66be17dbbb2b14d27baa6759f", - "reference": "21e54e002811cec66be17dbbb2b14d27baa6759f", + "url": "https://api.github.com/repos/fkooman/php-lib-rest/zipball/c3cf128e4878173b392ca577a65a2c36476f6565", + "reference": "c3cf128e4878173b392ca577a65a2c36476f6565", "shasum": "" }, "require": { - "fkooman/php-lib-json": "0.1.*", + "fkooman/php-lib-json": "0.3.*", "php": ">=5.3.3" }, "require-dev": { @@ -121,71 +84,19 @@ } ], "description": "Simple PHP library for writing REST services.", - "time": "2013-09-30 10:32:42" - }, - { - "name": "symfony/yaml", - "version": "v2.3.5", - "target-dir": "Symfony/Component/Yaml", - "source": { - "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/6bb881b948368482e1abf1a75c08bcf88a1c5fc3", - "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "http://symfony.com", - "time": "2013-09-22 18:04:39" + "abandoned": "fkooman/rest", + "time": "2013-11-12T13:32:42+00:00" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "packages-dev": [], + "aliases": [], "minimum-stability": "stable", - "stability-flags": [ - - ], + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, "platform": { "ext-ldap": "*", "php": ">=5.3.3" }, - "platform-dev": [ - - ] + "platform-dev": [] } diff --git a/src/fkooman/VootProvider/Config/Config.php b/src/fkooman/VootProvider/Config/Config.php new file mode 100644 index 0000000..0429022 --- /dev/null +++ b/src/fkooman/VootProvider/Config/Config.php @@ -0,0 +1,101 @@ + +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +namespace fkooman\VootProvider\Config; + +class Config +{ + private $config; + + public function __construct(array $config) + { + $this->config = $config; + } + + public static function fromIniFile($configFile) + { + $fileContent = @file_get_contents($configFile); + if (false === $fileContent) { + throw new ConfigException(sprintf("unable to read configuration file '%s'", $configFile)); + } + $configData = @parse_ini_string($fileContent, true); + if (false === $configData) { + throw new ConfigException(sprintf("unable to parse configuration file '%s'", $configFile)); + } + + return new static($configData); + } + + public function getSubtree($section, $required = false, array $default = array()) + { + if (!array_key_exists($section, $this->config)) { + if ($required) { + throw new ConfigException(sprintf("subtree '%s' does not exist", $section)); + } + + return new static($default); + } + if (!is_array($this->config[$section])) { + throw new ConfigException(sprintf("'%s' is not a subtree", $section)); + } + + return new static($this->config[$section]); + } + + public function s($section, $required = false, array $default = array()) + { + return $this->getSubtree($section, $required, $default); + } + + public function getSection($section, $required = false, array $default = array()) + { + return $this->getSubtree($section, $required, $default); + } + + public function getLeaf($key, $required = false, $default = null) + { + if (!array_key_exists($key, $this->config)) { + if ($required) { + throw new ConfigException(sprintf("required leaf '%s' does not exist", $key)); + } + + return $default; + } + if (is_array($this->config[$key])) { + throw new ConfigException(sprintf("'%s' is a subtree", $key)); + } + + return $this->config[$key]; + } + + public function getValue($key, $required = false, $default = null) + { + return $this->getLeaf($key, $required, $default); + } + + public function l($key, $required = false, $default = null) + { + return $this->getLeaf($key, $required, $default); + } + + public function toArray() + { + return $this->config; + } + +} diff --git a/src/fkooman/VootProvider/Config/ConfigException.php b/src/fkooman/VootProvider/Config/ConfigException.php new file mode 100644 index 0000000..39f447a --- /dev/null +++ b/src/fkooman/VootProvider/Config/ConfigException.php @@ -0,0 +1,23 @@ + +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +namespace fkooman\VootProvider\Config; + +class ConfigException extends \Exception +{ +} diff --git a/src/fkooman/VootProvider/LdapVootStorage.php b/src/fkooman/VootProvider/LdapVootStorage.php index 3f2f684..5d25839 100644 --- a/src/fkooman/VootProvider/LdapVootStorage.php +++ b/src/fkooman/VootProvider/LdapVootStorage.php @@ -18,7 +18,7 @@ namespace fkooman\VootProvider; -use fkooman\Config\Config; +use fkooman\VootProvider\Config\Config; class LdapVootStorage implements VootStorageInterface { diff --git a/src/fkooman/VootProvider/PdoVootStorage.php b/src/fkooman/VootProvider/PdoVootStorage.php index 6b55d7c..de95da8 100644 --- a/src/fkooman/VootProvider/PdoVootStorage.php +++ b/src/fkooman/VootProvider/PdoVootStorage.php @@ -18,7 +18,7 @@ namespace fkooman\VootProvider; -use fkooman\Config\Config; +use fkooman\VootProvider\Config\Config; use PDO; class PdoVootStorage implements VootStorageInterface diff --git a/src/fkooman/VootProvider/VootStorageException.php b/src/fkooman/VootProvider/VootStorageException.php index b1f11e3..d622595 100644 --- a/src/fkooman/VootProvider/VootStorageException.php +++ b/src/fkooman/VootProvider/VootStorageException.php @@ -18,7 +18,9 @@ namespace fkooman\VootProvider; -class VootStorageException extends \Exception +use Exception; + +class VootStorageException extends Exception { private $description; diff --git a/web/voot.php b/web/voot.php index 58d7892..665e6d5 100644 --- a/web/voot.php +++ b/web/voot.php @@ -18,7 +18,7 @@ require_once dirname(__DIR__) . "/vendor/autoload.php"; -use fkooman\Config\Config; +use fkooman\VootProvider\Config\Config; use fkooman\Http\JsonResponse; use fkooman\Http\Request; From fe2b48f3fb09b6ba02a5e6d2116016880d817c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 11:58:44 +0100 Subject: [PATCH 2/6] source formatting --- .php_cs.dist | 16 ++ bin/BackendTest.php | 39 ++- bin/addUsers.php | 45 ++-- composer.json | 2 +- composer.lock | 4 +- docs/migrate_grouper.php | 46 ++-- src/fkooman/VootProvider/Config/Config.php | 35 ++- .../VootProvider/Config/ConfigException.php | 28 +-- src/fkooman/VootProvider/LdapVootStorage.php | 224 +++++++++--------- src/fkooman/VootProvider/PdoVootStorage.php | 118 ++++----- .../VootProvider/VootStorageException.php | 34 +-- .../VootProvider/VootStorageInterface.php | 29 +-- web/voot.php | 75 +++--- 13 files changed, 352 insertions(+), 343 deletions(-) create mode 100644 .php_cs.dist diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..7caac44 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,16 @@ +in(__DIR__) +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + '@Symfony' => true, + 'ordered_imports' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_class_elements' => true, + ]) + ->setFinder($finder) +; diff --git a/bin/BackendTest.php b/bin/BackendTest.php index a76f642..4897abf 100644 --- a/bin/BackendTest.php +++ b/bin/BackendTest.php @@ -1,33 +1,32 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -require_once dirname(__DIR__) . '/vendor/autoload.php'; + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require_once dirname(__DIR__).'/vendor/autoload.php'; if ($argc < 2) { - echo "specify userId as a parameter" . PHP_EOL; + echo 'specify userId as a parameter'.PHP_EOL; exit(1); } use fkooman\VootProvider\Config\Config; -$config = Config::fromIniFile(dirname(__DIR__) . '/config/voot.ini'); +$config = Config::fromIniFile(dirname(__DIR__).'/config/voot.ini'); -$vootStorageBackend = "fkooman\\VootProvider\\" . $config->getValue('storageBackend'); +$vootStorageBackend = 'fkooman\\VootProvider\\'.$config->getValue('storageBackend'); try { $vootStorage = new $vootStorageBackend($config); @@ -39,5 +38,5 @@ var_dump($groupMembers); } } catch (Exception $e) { - echo $e->getMessage() . PHP_EOL; + echo $e->getMessage().PHP_EOL; } diff --git a/bin/addUsers.php b/bin/addUsers.php index 2141522..da91056 100644 --- a/bin/addUsers.php +++ b/bin/addUsers.php @@ -1,38 +1,37 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -require_once dirname(__DIR__) . '/vendor/autoload.php'; + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require_once dirname(__DIR__).'/vendor/autoload.php'; use fkooman\VootProvider\Config\Config; use fkooman\VootProvider\PdoVootStorage; -$config = Config::fromIniFile(dirname(__DIR__) . '/config/voot.ini'); +$config = Config::fromIniFile(dirname(__DIR__).'/config/voot.ini'); $storage = new PdoVootStorage($config); -$data = file_get_contents(dirname(__DIR__) . '/schema/user_attributes.json'); +$data = file_get_contents(dirname(__DIR__).'/schema/user_attributes.json'); $d = json_decode($data, true); foreach ($d as $v) { $storage->addUser($v['id'], $v['displayName'], $v['mail']); } -$data = file_get_contents(dirname(__DIR__) . '/schema/group_membership.json'); +$data = file_get_contents(dirname(__DIR__).'/schema/group_membership.json'); $d = json_decode($data, true); foreach ($d as $v) { @@ -45,13 +44,13 @@ function roleToInt($role) { switch ($role) { - case "member": + case 'member': return 10; - case "admin": + case 'admin': return 50; - case "manager": + case 'manager': return 20; default: - die("invalid role"); + die('invalid role'); } } diff --git a/composer.json b/composer.json index 01bd742..e7a673a 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,6 @@ "require": { "ext-ldap": "*", "fkooman/php-lib-rest": "0.3.*", - "php": ">=5.3.3" + "php": ">=5.4" } } diff --git a/composer.lock b/composer.lock index 93463d7..6d569c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "0c9d1f726ee35e607f1b54b87d7f93fe", + "content-hash": "282d5685cc7eb3d22a8e37b071230e4e", "packages": [ { "name": "fkooman/php-lib-json", @@ -96,7 +96,7 @@ "prefer-lowest": false, "platform": { "ext-ldap": "*", - "php": ">=5.3.3" + "php": ">=5.4" }, "platform-dev": [] } diff --git a/docs/migrate_grouper.php b/docs/migrate_grouper.php index d8be93b..297cd27 100644 --- a/docs/migrate_grouper.php +++ b/docs/migrate_grouper.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /*** Grouper MySQL database ***/ $dsn = 'mysql:dbname=teams;host=127.0.0.1'; @@ -24,7 +24,7 @@ $p = new PDO($dsn, $user, $password); /*** ENABLE FOREIGN KEY CONSTRAINT ***/ -echo "PRAGMA foreign_keys = ON;" . PHP_EOL; +echo 'PRAGMA foreign_keys = ON;'.PHP_EOL; /*** GROUPS ***/ $query = " @@ -44,12 +44,12 @@ $stmt = $p->prepare($query); $result = $stmt->execute(); -if (FALSE === $result) { +if (false === $result) { var_dump($p->errorInfo()); } $results = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($results as $r) { - echo 'INSERT INTO groups VALUES("' . $r['name'] . '","' . $r['display_extension'] . '","' . $r['description'] .'");' . PHP_EOL; + echo 'INSERT INTO groups VALUES("'.$r['name'].'","'.$r['display_extension'].'","'.$r['description'].'");'.PHP_EOL; } /*** MEMBERSHIPS ***/ @@ -59,13 +59,13 @@ $stmt = $p->prepare($query); $result = $stmt->execute(); -if (FALSE === $result) { +if (false === $result) { var_dump($p->errorInfo()); } $results = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($results as $r) { $level = 10; - echo 'INSERT INTO membership VALUES("' . $r['subject_id'] . '","' . $r['groupname'] . '",' . $level .');' . PHP_EOL; + echo 'INSERT INTO membership VALUES("'.$r['subject_id'].'","'.$r['groupname'].'",'.$level.');'.PHP_EOL; } $query = " @@ -73,13 +73,13 @@ $stmt = $p->prepare($query); $result = $stmt->execute(); -if (FALSE === $result) { +if (false === $result) { var_dump($p->errorInfo()); } $results = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($results as $r) { $level = 20; - echo 'UPDATE membership SET role=' . $level . ' WHERE id="' . $r['subject_id'] . '" AND groupid="' . $r['groupname'] . '";' . PHP_EOL; + echo 'UPDATE membership SET role='.$level.' WHERE id="'.$r['subject_id'].'" AND groupid="'.$r['groupname'].'";'.PHP_EOL; } $query = " @@ -87,11 +87,11 @@ $stmt = $p->prepare($query); $result = $stmt->execute(); -if (FALSE === $result) { +if (false === $result) { var_dump($p->errorInfo()); } $results = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($results as $r) { $level = 50; - echo 'UPDATE membership SET role=' . $level . ' WHERE id="' . $r['subject_id'] . '" AND groupid="' . $r['groupname'] . '";' . PHP_EOL; + echo 'UPDATE membership SET role='.$level.' WHERE id="'.$r['subject_id'].'" AND groupid="'.$r['groupname'].'";'.PHP_EOL; } diff --git a/src/fkooman/VootProvider/Config/Config.php b/src/fkooman/VootProvider/Config/Config.php index 0429022..56aa69b 100644 --- a/src/fkooman/VootProvider/Config/Config.php +++ b/src/fkooman/VootProvider/Config/Config.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider\Config; @@ -41,7 +41,7 @@ public static function fromIniFile($configFile) return new static($configData); } - public function getSubtree($section, $required = false, array $default = array()) + public function getSubtree($section, $required = false, array $default = []) { if (!array_key_exists($section, $this->config)) { if ($required) { @@ -57,12 +57,12 @@ public function getSubtree($section, $required = false, array $default = array() return new static($this->config[$section]); } - public function s($section, $required = false, array $default = array()) + public function s($section, $required = false, array $default = []) { return $this->getSubtree($section, $required, $default); } - public function getSection($section, $required = false, array $default = array()) + public function getSection($section, $required = false, array $default = []) { return $this->getSubtree($section, $required, $default); } @@ -97,5 +97,4 @@ public function toArray() { return $this->config; } - } diff --git a/src/fkooman/VootProvider/Config/ConfigException.php b/src/fkooman/VootProvider/Config/ConfigException.php index 39f447a..0771695 100644 --- a/src/fkooman/VootProvider/Config/ConfigException.php +++ b/src/fkooman/VootProvider/Config/ConfigException.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider\Config; diff --git a/src/fkooman/VootProvider/LdapVootStorage.php b/src/fkooman/VootProvider/LdapVootStorage.php index 5d25839..bf0354a 100644 --- a/src/fkooman/VootProvider/LdapVootStorage.php +++ b/src/fkooman/VootProvider/LdapVootStorage.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider; @@ -30,7 +30,7 @@ public function __construct(Config $c) $this->config = $c; $this->ldapConnection = @ldap_connect($this->config->s('LdapVootStorage')->l('uri')); if (false === $this->ldapConnection) { - throw new VootStorageException("ldap_error", "unable to connect to ldap server"); + throw new VootStorageException('ldap_error', 'unable to connect to ldap server'); } $bindDn = $this->config->s('LdapVootStorage')->l('bindDn', false); @@ -38,61 +38,13 @@ public function __construct(Config $c) if (null !== $bindDn) { if (false === @ldap_bind($this->ldapConnection, $bindDn, $bindPass)) { throw new VootStorageException( - "ldap_error", - "unable to bind to ldap server, possibly invalid credentials" + 'ldap_error', + 'unable to bind to ldap server, possibly invalid credentials' ); } } } - private function getUserDn($resourceOwnerId) - { - /* get the user distinguishedName */ - $filter = '(' . $this->config->s('LdapVootStorage')->l('userIdAttribute') . '=' . $resourceOwnerId . ')'; - $query = @ldap_search($this->ldapConnection, $this->config->s('LdapVootStorage')->l('peopleDn'), $filter); - if (false === $query) { - throw new VootStorageException("ldap_error", "directory query for user failed"); - } - /* we assume there is only one entry for the specified user, if not - we only look at the first result */ - $entry = @ldap_first_entry($this->ldapConnection, $query); - if (false === $entry) { - throw new VootStorageException("not_found", "user not found"); - } - $userDn = @ldap_get_dn($this->ldapConnection, $entry); - if (false === $userDn) { - throw new VootStorageException("ldap_error", "unable to get user distinguishedName"); - } - - return $userDn; - } - - private function getUserAttributesByDn($userDn) - { - $query = @ldap_read( - $this->ldapConnection, - $userDn, - "(objectClass=*)", - array_values( - $this->config->s('LdapVootStorage')->s('attributeMapping')->toArray() - ) - ); - if (false === $query) { - throw new VootStorageException("ldap_error", "directory query for user failed"); - } - $entry = @ldap_first_entry($this->ldapConnection, $query); - if (false === $entry) { - throw new VootStorageException("not_found", "user not found"); - } - $attributes = @ldap_get_attributes($this->ldapConnection, $entry); - if (false === $attributes) { - throw new VootStorageException("ldap_error", "unable to get user attributes"); - } - $filteredAttributes = $this->filterAttributes($attributes); - - return $filteredAttributes; - } - public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $count = null) { // get the members of a group by its cn @@ -112,118 +64,118 @@ public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $co $memberAttribute = $this->config->s('LdapVootStorage')->l('memberAttribute'); $userDn = $this->getUserDn($resourceOwnerId); - + $groupsProvider = $this->config->s('LdapVootStorage')->l('groupsProvider'); - + // FIXME: make sure the user is member of the group being requested - $filter = '(cn=' . $groupId . ')'; + $filter = '(cn='.$groupId.')'; $query = @ldap_search( $this->ldapConnection, $this->config->s('LdapVootStorage')->l('groupDn'), $filter, - array( - $memberAttribute - ) + [ + $memberAttribute, + ] ); if (false === $query) { - throw new VootStorageException("ldap_error", "directory query for group failed"); + throw new VootStorageException('ldap_error', 'directory query for group failed'); } - - $all = ldap_get_entries($this->ldapConnection, $query); - + + $all = ldap_get_entries($this->ldapConnection, $query); + switch ($groupsProvider) { - case "posixgroup": + case 'posixgroup': // we are only interested in group memberuid array $attributes = $all[0]; break; default: break; - } + } $entry = @ldap_first_entry($this->ldapConnection, $query); if (false === $entry) { - throw new VootStorageException("not_found", "group not found"); + throw new VootStorageException('not_found', 'group not found'); } $attributes = @ldap_get_attributes($this->ldapConnection, $entry); if (false === $attributes) { - throw new VootStorageException("ldap_error", "unable to get group attributes"); + throw new VootStorageException('ldap_error', 'unable to get group attributes'); } - $data = array(); + $data = []; if (array_key_exists($memberAttribute, $attributes)) { // we have some members - for ($i = 0; $i < $attributes[$memberAttribute]["count"]; $i++) { + for ($i = 0; $i < $attributes[$memberAttribute]['count']; ++$i) { // member DN // fetch attributes for this particular user switch ($groupsProvider) { - case "posixgroup": - $user_dn = 'uid=' . $attributes[$memberAttribute][$i] . ',' . $this->config->s('LdapVootStorage')->l('peopleDn'); + case 'posixgroup': + $user_dn = 'uid='.$attributes[$memberAttribute][$i].','.$this->config->s('LdapVootStorage')->l('peopleDn'); $userAttributes = $this->getUserAttributesByDn($user_dn); break; - default: + default: $userAttributes = $this->getUserAttributesByDn($attributes[$memberAttribute][$i]); break; - } + } - $userAttributes['voot_membership_role'] = "member"; + $userAttributes['voot_membership_role'] = 'member'; array_push($data, $userAttributes); } } // backwards compatible "emails" element with array - for ($i = 0; $i < count($data); $i++) { - $data[$i]["emails"] = array($data[$i]['mail']); + for ($i = 0; $i < count($data); ++$i) { + $data[$i]['emails'] = [$data[$i]['mail']]; } - return array( + return [ 'startIndex' => 0, 'totalResults' => count($data), 'itemsPerPage' => count($data), - 'entry' => $data - ); + 'entry' => $data, + ]; } public function isMemberOf($resourceOwnerId, $startIndex = null, $count = null) { $userDn = $this->getUserDn($resourceOwnerId); - $userGroups = array(); - + $userGroups = []; + $groupsProvider = $this->config->s('LdapVootStorage')->l('groupsProvider'); - + /* get the groups the user is a member of */ switch ($groupsProvider) { - case "posixgroup": - $filter = '(' . $this->config->s('LdapVootStorage')->l('memberAttribute') . '=' . $resourceOwnerId . ')'; + case 'posixgroup': + $filter = '('.$this->config->s('LdapVootStorage')->l('memberAttribute').'='.$resourceOwnerId.')'; break; default: - $filter = '(' . $this->config->s('LdapVootStorage')->l('memberAttribute') . '=' . $userDn . ')'; + $filter = '('.$this->config->s('LdapVootStorage')->l('memberAttribute').'='.$userDn.')'; break; - } + } $query = @ldap_search($this->ldapConnection, $this->config->s('LdapVootStorage')->l('groupDn'), $filter); if (false === $query) { - throw new VootStorageException("ldap_error", "directory query for groups failed"); + throw new VootStorageException('ldap_error', 'directory query for groups failed'); } $entry = @ldap_first_entry($this->ldapConnection, $query); while (false !== $entry) { $attributes = @ldap_get_attributes($this->ldapConnection, $entry); if (false === $attributes) { - throw new VootStorageException("ldap_error", "unable to get group attributes"); + throw new VootStorageException('ldap_error', 'unable to get group attributes'); } - $commonName = array_key_exists("cn", $attributes) ? $attributes["cn"][0] : null; - $displayName = array_key_exists("displayName", $attributes) ? $attributes["displayName"][0] : null; - $description = array_key_exists("description", $attributes) ? $attributes["description"][0] : null; + $commonName = array_key_exists('cn', $attributes) ? $attributes['cn'][0] : null; + $displayName = array_key_exists('displayName', $attributes) ? $attributes['displayName'][0] : null; + $description = array_key_exists('description', $attributes) ? $attributes['description'][0] : null; $distinguishedName = @ldap_get_dn($this->ldapConnection, $entry); if (false === $distinguishedName) { - throw new VootStorageException("ldap_error", "unable to get distinguishedName"); + throw new VootStorageException('ldap_error', 'unable to get distinguishedName'); } if (null === $commonName) { - throw new VootStorageException("ldap_error", "no cn for group"); + throw new VootStorageException('ldap_error', 'no cn for group'); } - $a = array(); + $a = []; $a['id'] = $commonName; $a['title'] = null !== $displayName ? $displayName : $commonName; if (null !== $description) { @@ -236,28 +188,76 @@ public function isMemberOf($resourceOwnerId, $startIndex = null, $count = null) // FIXME: we need to implement paging for LDAP as well... $startIndex = 0; - $totalResults = sizeof($userGroups); + $totalResults = count($userGroups); - return array( + return [ 'startIndex' => $startIndex, 'totalResults' => $totalResults, 'itemsPerPage' => $totalResults, - 'entry' => $userGroups + 'entry' => $userGroups, + ]; + } + + private function getUserDn($resourceOwnerId) + { + /* get the user distinguishedName */ + $filter = '('.$this->config->s('LdapVootStorage')->l('userIdAttribute').'='.$resourceOwnerId.')'; + $query = @ldap_search($this->ldapConnection, $this->config->s('LdapVootStorage')->l('peopleDn'), $filter); + if (false === $query) { + throw new VootStorageException('ldap_error', 'directory query for user failed'); + } + /* we assume there is only one entry for the specified user, if not + we only look at the first result */ + $entry = @ldap_first_entry($this->ldapConnection, $query); + if (false === $entry) { + throw new VootStorageException('not_found', 'user not found'); + } + $userDn = @ldap_get_dn($this->ldapConnection, $entry); + if (false === $userDn) { + throw new VootStorageException('ldap_error', 'unable to get user distinguishedName'); + } + + return $userDn; + } + + private function getUserAttributesByDn($userDn) + { + $query = @ldap_read( + $this->ldapConnection, + $userDn, + '(objectClass=*)', + array_values( + $this->config->s('LdapVootStorage')->s('attributeMapping')->toArray() + ) ); + if (false === $query) { + throw new VootStorageException('ldap_error', 'directory query for user failed'); + } + $entry = @ldap_first_entry($this->ldapConnection, $query); + if (false === $entry) { + throw new VootStorageException('not_found', 'user not found'); + } + $attributes = @ldap_get_attributes($this->ldapConnection, $entry); + if (false === $attributes) { + throw new VootStorageException('ldap_error', 'unable to get user attributes'); + } + $filteredAttributes = $this->filterAttributes($attributes); + + return $filteredAttributes; } private function filterAttributes($attributes) { $attributeMapping = $this->config->s('LdapVootStorage')->s('attributeMapping')->toArray(); - $filteredAttributes = array(); + $filteredAttributes = []; foreach ($attributeMapping as $k => $v) { if (array_key_exists($v, $attributes)) { $filteredAttributes[$k] = $attributes[$v][0]; } } - if (!array_key_exists("id", $filteredAttributes)) { + if (!array_key_exists('id', $filteredAttributes)) { throw new VootStorageException( - "ldap_error", + 'ldap_error', "mapping for 'id' attribute not set in LDAP response" ); } diff --git a/src/fkooman/VootProvider/PdoVootStorage.php b/src/fkooman/VootProvider/PdoVootStorage.php index de95da8..6593219 100644 --- a/src/fkooman/VootProvider/PdoVootStorage.php +++ b/src/fkooman/VootProvider/PdoVootStorage.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider; @@ -30,9 +30,9 @@ public function __construct(Config $c) { $this->config = $c; - $driverOptions = array(); + $driverOptions = []; if ($this->config->s('PdoVootStorage')->l('persistentConnection')) { - $driverOptions = array(PDO::ATTR_PERSISTENT => true); + $driverOptions = [PDO::ATTR_PERSISTENT => true]; } $this->storage = new PDO( @@ -42,15 +42,15 @@ public function __construct(Config $c) $driverOptions ); - if (0 === strpos($this->config->s('PdoVootStorage')->l('dsn'), "sqlite:")) { + if (0 === strpos($this->config->s('PdoVootStorage')->l('dsn'), 'sqlite:')) { // only for SQlite - $this->storage->exec("PRAGMA foreign_keys = ON"); + $this->storage->exec('PRAGMA foreign_keys = ON'); } } public function isMemberOf($resourceOwnerId, $startIndex = 0, $count = null) { - $query = <<< EOQ + $query = <<< 'EOQ' SELECT COUNT(*) AS count FROM @@ -62,12 +62,12 @@ public function isMemberOf($resourceOwnerId, $startIndex = 0, $count = null) EOQ; $stmt = $this->storage->prepare($query); - $stmt->bindValue(":user_id", $resourceOwnerId, PDO::PARAM_STR); + $stmt->bindValue(':user_id', $resourceOwnerId, PDO::PARAM_STR); $result = $stmt->execute(); if (false === $result) { throw new VootStorageException( - "internal_server_error", - "unable to retrieve membership" + 'internal_server_error', + 'unable to retrieve membership' ); } $data = $stmt->fetch(PDO::FETCH_ASSOC); @@ -80,7 +80,7 @@ public function isMemberOf($resourceOwnerId, $startIndex = 0, $count = null) $count = $totalResults; } - $query = <<< EOQ + $query = <<< 'EOQ' SELECT g.id, g.title, g.description, r.voot_membership_role FROM @@ -93,28 +93,28 @@ public function isMemberOf($resourceOwnerId, $startIndex = 0, $count = null) EOQ; $stmt = $this->storage->prepare($query); - $stmt->bindValue(":user_id", $resourceOwnerId, PDO::PARAM_STR); - $stmt->bindValue(":start_index", $startIndex, PDO::PARAM_INT); - $stmt->bindValue(":count", $count, PDO::PARAM_INT); + $stmt->bindValue(':user_id', $resourceOwnerId, PDO::PARAM_STR); + $stmt->bindValue(':start_index', $startIndex, PDO::PARAM_INT); + $stmt->bindValue(':count', $count, PDO::PARAM_INT); $result = $stmt->execute(); if (false === $result) { - throw new VootStorageException("internal_server_error", "unable to retrieve membership"); + throw new VootStorageException('internal_server_error', 'unable to retrieve membership'); } $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - return array( + return [ 'startIndex' => intval($startIndex), 'totalResults' => intval($totalResults), 'itemsPerPage' => count($data), - 'entry' => $data - ); + 'entry' => $data, + ]; } public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $count = null) { // FIXME: check whether or not $resourceOwnerId is a member of the group, if not don't // return anything (or error). - $query = <<< EOQ + $query = <<< 'EOQ' SELECT COUNT(*) AS count FROM @@ -127,10 +127,10 @@ public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $co EOQ; $stmt = $this->storage->prepare($query); - $stmt->bindValue(":group_id", $groupId, PDO::PARAM_STR); + $stmt->bindValue(':group_id', $groupId, PDO::PARAM_STR); $result = $stmt->execute(); if (false === $result) { - throw new VootStorageException("internal_server_error", "unable to retrieve members"); + throw new VootStorageException('internal_server_error', 'unable to retrieve members'); } $data = $stmt->fetch(PDO::FETCH_ASSOC); $totalResults = $data['count']; @@ -142,7 +142,7 @@ public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $co $count = $totalResults; } - $query = <<< EOQ + $query = <<< 'EOQ' SELECT u.id, u.display_name as displayName, @@ -160,37 +160,37 @@ public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $co EOQ; $stmt = $this->storage->prepare($query); - $stmt->bindValue(":group_id", $groupId, PDO::PARAM_STR); - $stmt->bindValue(":start_index", $startIndex, PDO::PARAM_INT); - $stmt->bindValue(":count", $count, PDO::PARAM_INT); + $stmt->bindValue(':group_id', $groupId, PDO::PARAM_STR); + $stmt->bindValue(':start_index', $startIndex, PDO::PARAM_INT); + $stmt->bindValue(':count', $count, PDO::PARAM_INT); $result = $stmt->execute(); if (false === $result) { - throw new VootStorageException("internal_server_error", "unable to retrieve members"); + throw new VootStorageException('internal_server_error', 'unable to retrieve members'); } $data = $stmt->fetchAll(PDO::FETCH_ASSOC); // fill emails element according to OpenSocial "Plural-Field" - for ($i = 0; $i < count($data); $i++) { - $data[$i]["emails"] = array( array("type" => "work", "value" => $data[$i]['mail'])); - unset($data[$i]["mail"]); + for ($i = 0; $i < count($data); ++$i) { + $data[$i]['emails'] = [['type' => 'work', 'value' => $data[$i]['mail']]]; + unset($data[$i]['mail']); } - return array( + return [ 'startIndex' => intval($startIndex), 'totalResults' => intval($totalResults), 'itemsPerPage' => count($data), - 'entry' => $data - ); + 'entry' => $data, + ]; } public function addUser($id, $displayName, $mail) { - $stmt = $this->storage->prepare("INSERT INTO users (id, display_name, mail) VALUES(:id, :display_name, :mail)"); - $stmt->bindValue(":id", $id, PDO::PARAM_STR); - $stmt->bindValue(":display_name", $displayName, PDO::PARAM_STR); - $stmt->bindValue(":mail", $mail, PDO::PARAM_STR); + $stmt = $this->storage->prepare('INSERT INTO users (id, display_name, mail) VALUES(:id, :display_name, :mail)'); + $stmt->bindValue(':id', $id, PDO::PARAM_STR); + $stmt->bindValue(':display_name', $displayName, PDO::PARAM_STR); + $stmt->bindValue(':mail', $mail, PDO::PARAM_STR); if (false === $stmt->execute()) { - throw new VootStorageException("internal_server_error", "unable to add user"); + throw new VootStorageException('internal_server_error', 'unable to add user'); } return 1 === $stmt->rowCount(); @@ -199,13 +199,13 @@ public function addUser($id, $displayName, $mail) public function addGroup($id, $title, $description) { $stmt = $this->storage->prepare( - "INSERT INTO groups (id, title, description) VALUES(:id, :title, :description)" + 'INSERT INTO groups (id, title, description) VALUES(:id, :title, :description)' ); - $stmt->bindValue(":id", $id, PDO::PARAM_STR); - $stmt->bindValue(":title", $title, PDO::PARAM_STR); - $stmt->bindValue(":description", $description, PDO::PARAM_STR); + $stmt->bindValue(':id', $id, PDO::PARAM_STR); + $stmt->bindValue(':title', $title, PDO::PARAM_STR); + $stmt->bindValue(':description', $description, PDO::PARAM_STR); if (false === $stmt->execute()) { - throw new VootStorageException("internal_server_error", "unable to add group"); + throw new VootStorageException('internal_server_error', 'unable to add group'); } return 1 === $stmt->rowCount(); @@ -214,13 +214,13 @@ public function addGroup($id, $title, $description) public function addMembership($userId, $groupId, $roleId) { $stmt = $this->storage->prepare( - "INSERT INTO users_groups_roles (user_id, group_id, role_id) VALUES(:user_id, :group_id, :role_id)" + 'INSERT INTO users_groups_roles (user_id, group_id, role_id) VALUES(:user_id, :group_id, :role_id)' ); - $stmt->bindValue(":user_id", $userId, PDO::PARAM_STR); - $stmt->bindValue(":group_id", $groupId, PDO::PARAM_STR); - $stmt->bindValue(":role_id", $roleId, PDO::PARAM_INT); + $stmt->bindValue(':user_id', $userId, PDO::PARAM_STR); + $stmt->bindValue(':group_id', $groupId, PDO::PARAM_STR); + $stmt->bindValue(':role_id', $roleId, PDO::PARAM_INT); if (false === $stmt->execute()) { - throw new VootStorageException("internal_server_error", "unable to add membership"); + throw new VootStorageException('internal_server_error', 'unable to add membership'); } return 1 === $stmt->rowCount(); diff --git a/src/fkooman/VootProvider/VootStorageException.php b/src/fkooman/VootProvider/VootStorageException.php index d622595..b4c96c0 100644 --- a/src/fkooman/VootProvider/VootStorageException.php +++ b/src/fkooman/VootProvider/VootStorageException.php @@ -1,20 +1,20 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider; @@ -38,10 +38,10 @@ public function getDescription() public function getResponseCode() { switch ($this->message) { - case "not_found": + case 'not_found': return 404; - case "ldap_error": - case "internal_server_error": + case 'ldap_error': + case 'internal_server_error': return 500; default: return 400; diff --git a/src/fkooman/VootProvider/VootStorageInterface.php b/src/fkooman/VootProvider/VootStorageInterface.php index b9e19e8..66c9c3e 100644 --- a/src/fkooman/VootProvider/VootStorageInterface.php +++ b/src/fkooman/VootProvider/VootStorageInterface.php @@ -1,25 +1,26 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ namespace fkooman\VootProvider; interface VootStorageInterface { public function getGroupMembers($resourceOwnerId, $groupId, $startIndex = 0, $count = null); + public function isMemberOf($resourceOwnerId, $startIndex = null, $count = null); } diff --git a/web/voot.php b/web/voot.php index 665e6d5..633224e 100644 --- a/web/voot.php +++ b/web/voot.php @@ -1,37 +1,33 @@ -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -require_once dirname(__DIR__) . "/vendor/autoload.php"; - -use fkooman\VootProvider\Config\Config; + * Copyright 2013 François Kooman . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require_once dirname(__DIR__).'/vendor/autoload.php'; +use fkooman\Http\IncomingRequest; use fkooman\Http\JsonResponse; use fkooman\Http\Request; -use fkooman\Http\IncomingRequest; - -use fkooman\Rest\Service; use fkooman\Rest\Plugin\BasicAuthentication; - +use fkooman\Rest\Service; +use fkooman\VootProvider\Config\Config; use fkooman\VootProvider\VootStorageException; try { $config = Config::fromIniFile( - dirname(__DIR__) . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "voot.ini" + dirname(__DIR__).DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'voot.ini' ); $vootStorageBackend = sprintf('fkooman\VootProvider\%s', $config->getValue('storageBackend')); @@ -55,13 +51,13 @@ // GROUPS $service->match( - "GET", - "/groups/:uid", + 'GET', + '/groups/:uid', function ($uid) use ($request, $vootStorage) { $groups = $vootStorage->isMemberOf( $uid, - $request->getQueryParameter("startIndex"), - $request->getQueryParameter("count") + $request->getQueryParameter('startIndex'), + $request->getQueryParameter('count') ); $response = new JsonResponse(200); $response->setContent($groups); @@ -72,14 +68,14 @@ function ($uid) use ($request, $vootStorage) { // PEOPLE IN GROUP $service->match( - "GET", - "/people/:uid/:gid", + 'GET', + '/people/:uid/:gid', function ($uid, $gid) use ($request, $vootStorage) { $users = $vootStorage->getGroupMembers( $uid, $gid, - $request->getQueryParameter("startIndex"), - $request->getQueryParameter("count") + $request->getQueryParameter('startIndex'), + $request->getQueryParameter('count') ); $response = new JsonResponse(200); $response->setContent($users); @@ -89,24 +85,23 @@ function ($uid, $gid) use ($request, $vootStorage) { ); $service->run()->sendResponse(); - } catch (VootStorageException $e) { $response = new JsonResponse($e->getResponseCode()); $response->setContent( - array( - "error" => $e->getMessage(), - "error_description" => $e->getDescription() - ) + [ + 'error' => $e->getMessage(), + 'error_description' => $e->getDescription(), + ] ); $response->sendResponse(); } catch (Exception $e) { // any other error thrown by any of the modules, assume internal server error $response = new JsonResponse(500); $response->setContent( - array( - "error" => "internal_server_error", - "error_description" => $e->getMessage() - ) + [ + 'error' => 'internal_server_error', + 'error_description' => $e->getMessage(), + ] ); $response->sendResponse(); } From 8dbe78372c7569c0ac8707663d8b947c4733dfbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 11:59:50 +0100 Subject: [PATCH 3/6] switch to psr-4 --- composer.json | 2 +- src/{fkooman/VootProvider => }/Config/Config.php | 0 src/{fkooman/VootProvider => }/Config/ConfigException.php | 0 src/{fkooman/VootProvider => }/LdapVootStorage.php | 0 src/{fkooman/VootProvider => }/PdoVootStorage.php | 0 src/{fkooman/VootProvider => }/VootStorageException.php | 0 src/{fkooman/VootProvider => }/VootStorageInterface.php | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename src/{fkooman/VootProvider => }/Config/Config.php (100%) rename src/{fkooman/VootProvider => }/Config/ConfigException.php (100%) rename src/{fkooman/VootProvider => }/LdapVootStorage.php (100%) rename src/{fkooman/VootProvider => }/PdoVootStorage.php (100%) rename src/{fkooman/VootProvider => }/VootStorageException.php (100%) rename src/{fkooman/VootProvider => }/VootStorageInterface.php (100%) diff --git a/composer.json b/composer.json index e7a673a..95e0f93 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ } ], "autoload": { - "psr-0": { + "psr-4": { "fkooman\\VootProvider\\": "src/" } }, diff --git a/src/fkooman/VootProvider/Config/Config.php b/src/Config/Config.php similarity index 100% rename from src/fkooman/VootProvider/Config/Config.php rename to src/Config/Config.php diff --git a/src/fkooman/VootProvider/Config/ConfigException.php b/src/Config/ConfigException.php similarity index 100% rename from src/fkooman/VootProvider/Config/ConfigException.php rename to src/Config/ConfigException.php diff --git a/src/fkooman/VootProvider/LdapVootStorage.php b/src/LdapVootStorage.php similarity index 100% rename from src/fkooman/VootProvider/LdapVootStorage.php rename to src/LdapVootStorage.php diff --git a/src/fkooman/VootProvider/PdoVootStorage.php b/src/PdoVootStorage.php similarity index 100% rename from src/fkooman/VootProvider/PdoVootStorage.php rename to src/PdoVootStorage.php diff --git a/src/fkooman/VootProvider/VootStorageException.php b/src/VootStorageException.php similarity index 100% rename from src/fkooman/VootProvider/VootStorageException.php rename to src/VootStorageException.php diff --git a/src/fkooman/VootProvider/VootStorageInterface.php b/src/VootStorageInterface.php similarity index 100% rename from src/fkooman/VootProvider/VootStorageInterface.php rename to src/VootStorageInterface.php From b7de5f75ab24da3f6012fca20bb6b4037b373bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 13:27:51 +0100 Subject: [PATCH 4/6] include http/rest classes as well in the project --- .phan/config.php | 2 - composer.json | 1 - composer.lock | 86 +-------- src/Http/IncomingRequest.php | 133 +++++++++++++ src/Http/IncomingRequestException.php | 23 +++ src/Http/JsonResponse.php | 40 ++++ src/Http/Request.php | 239 ++++++++++++++++++++++++ src/Http/RequestException.php | 23 +++ src/Http/Response.php | 236 +++++++++++++++++++++++ src/Http/ResponseException.php | 23 +++ src/Http/Uri.php | 142 ++++++++++++++ src/Http/UriException.php | 23 +++ src/Rest/Plugin/BasicAuthentication.php | 69 +++++++ src/Rest/Service.php | 235 +++++++++++++++++++++++ src/Rest/ServicePluginInterface.php | 29 +++ web/voot.php | 10 +- 16 files changed, 1222 insertions(+), 92 deletions(-) create mode 100644 src/Http/IncomingRequest.php create mode 100644 src/Http/IncomingRequestException.php create mode 100644 src/Http/JsonResponse.php create mode 100644 src/Http/Request.php create mode 100644 src/Http/RequestException.php create mode 100644 src/Http/Response.php create mode 100644 src/Http/ResponseException.php create mode 100644 src/Http/Uri.php create mode 100644 src/Http/UriException.php create mode 100644 src/Rest/Plugin/BasicAuthentication.php create mode 100644 src/Rest/Service.php create mode 100644 src/Rest/ServicePluginInterface.php diff --git a/.phan/config.php b/.phan/config.php index e45e0e6..cc2532a 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -5,8 +5,6 @@ 'src', 'web', 'bin', - 'vendor/fkooman/php-lib-json', - 'vendor/fkooman/php-lib-rest', ], 'exclude_analysis_directory_list' => [ 'vendor/', diff --git a/composer.json b/composer.json index 95e0f93..8dcf827 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,6 @@ "name": "fkooman/php-voot-provider", "require": { "ext-ldap": "*", - "fkooman/php-lib-rest": "0.3.*", "php": ">=5.4" } } diff --git a/composer.lock b/composer.lock index 6d569c0..bc238b5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,90 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "282d5685cc7eb3d22a8e37b071230e4e", - "packages": [ - { - "name": "fkooman/php-lib-json", - "version": "0.3.0", - "source": { - "type": "git", - "url": "https://github.com/fkooman/php-lib-json.git", - "reference": "eb64350e01d8a3c11d70d82374ac1bb35a44b7c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fkooman/php-lib-json/zipball/eb64350e01d8a3c11d70d82374ac1bb35a44b7c9", - "reference": "eb64350e01d8a3c11d70d82374ac1bb35a44b7c9", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "fkooman\\Json\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "François Kooman", - "email": "fkooman@tuxed.net", - "role": "Developer" - } - ], - "description": "PHP library to encode and decode JSON", - "abandoned": "fkooman/json", - "time": "2013-11-07T15:48:53+00:00" - }, - { - "name": "fkooman/php-lib-rest", - "version": "0.3.1", - "source": { - "type": "git", - "url": "https://github.com/fkooman/php-lib-rest.git", - "reference": "c3cf128e4878173b392ca577a65a2c36476f6565" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fkooman/php-lib-rest/zipball/c3cf128e4878173b392ca577a65a2c36476f6565", - "reference": "c3cf128e4878173b392ca577a65a2c36476f6565", - "shasum": "" - }, - "require": { - "fkooman/php-lib-json": "0.3.*", - "php": ">=5.3.3" - }, - "require-dev": { - "sami/sami": "v1.1" - }, - "type": "library", - "autoload": { - "psr-0": { - "fkooman\\Http\\": "src/", - "fkooman\\Rest\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "François Kooman", - "email": "fkooman@tuxed.net", - "role": "Developer" - } - ], - "description": "Simple PHP library for writing REST services.", - "abandoned": "fkooman/rest", - "time": "2013-11-12T13:32:42+00:00" - } - ], + "content-hash": "bf1a49de36dd4eab90ec453a2415cd9c", + "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable", diff --git a/src/Http/IncomingRequest.php b/src/Http/IncomingRequest.php new file mode 100644 index 0000000..3bca508 --- /dev/null +++ b/src/Http/IncomingRequest.php @@ -0,0 +1,133 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class IncomingRequest +{ + public function __construct() + { + $required_keys = ['SERVER_NAME', 'SERVER_PORT', 'REQUEST_URI', 'REQUEST_METHOD']; + foreach ($required_keys as $r) { + if (!array_key_exists($r, $_SERVER) || empty($_SERVER[$r])) { + throw new IncomingRequestException('missing (one or more) required environment variables'); + } + } + } + + public function getRequestMethod() + { + return $_SERVER['REQUEST_METHOD']; + } + + public function getPathInfo() + { + return array_key_exists('PATH_INFO', $_SERVER) ? $_SERVER['PATH_INFO'] : null; + } + + public function getRequestUri() + { + // scheme + $proxy = false; + if (array_key_exists('HTTPS', $_SERVER) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + $scheme = 'https'; + } elseif (array_key_exists('HTTP_X_FORWARDED_PROTO', $_SERVER) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) { + // HTTPS to HTTP proxy is present + $scheme = 'https'; + $proxy = true; + } else { + $scheme = 'http'; + } + + // server name + if (filter_var($_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { + $name = $_SERVER['SERVER_NAME']; + } else { + $name = '['.$_SERVER['SERVER_NAME'].']'; + } + + // server port + if (($_SERVER['SERVER_PORT'] === '80' && ($scheme === 'http' || $proxy)) || ($_SERVER['SERVER_PORT'] === '443' && $scheme === 'https')) { + $port = ''; + } else { + $port = ':'.$_SERVER['SERVER_PORT']; + } + + return $scheme.'://'.$name.$port.$_SERVER['REQUEST_URI']; + } + + public function getContent() + { + if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'PUT') { + return null; + } + if (array_key_exists('CONTENT_LENGTH', $_SERVER) && $_SERVER['CONTENT_LENGTH'] > 0) { + return $this->getRawContent(); + } + + return null; + } + + public function getRawContent() + { + return file_get_contents('php://input'); + } + + public function getBasicAuthUser() + { + if (array_key_exists('PHP_AUTH_USER', $_SERVER)) { + return $_SERVER['PHP_AUTH_USER']; + } + + return null; + } + + public function getBasicAuthPass() + { + if (array_key_exists('PHP_AUTH_PW', $_SERVER)) { + return $_SERVER['PHP_AUTH_PW']; + } + + return null; + } + + public function getRequestHeaders() + { + $requestHeaders = []; + + // normalize headers from $_SERVER + foreach ($_SERVER as $k => $v) { + $key = Request::normalizeHeaderKey($k); + $requestHeaders[$key] = $v; + } + + // also normalize Apache headers (if available), but do not override + // headers from $_SERVER + if (function_exists('apache_request_headers')) { + $apacheHeaders = apache_request_headers(); + foreach ($apacheHeaders as $k => $v) { + $key = Request::normalizeHeaderKey($k); + if (!array_key_exists($key, $requestHeaders)) { + $requestHeaders[$key] = $v; + } + } + } + + return $requestHeaders; + } +} diff --git a/src/Http/IncomingRequestException.php b/src/Http/IncomingRequestException.php new file mode 100644 index 0000000..a67a390 --- /dev/null +++ b/src/Http/IncomingRequestException.php @@ -0,0 +1,23 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class IncomingRequestException extends \Exception +{ +} diff --git a/src/Http/JsonResponse.php b/src/Http/JsonResponse.php new file mode 100644 index 0000000..d134cd3 --- /dev/null +++ b/src/Http/JsonResponse.php @@ -0,0 +1,40 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class JsonResponse extends Response +{ + public function __construct($statusCode = 200) + { + parent::__construct($statusCode, 'application/json'); + } + + /** + * @param mixed $content + */ + public function setContent($content) + { + parent::setContent(json_encode($content)); + } + + public function getContent() + { + return json_decode(parent::getContent(), true); + } +} diff --git a/src/Http/Request.php b/src/Http/Request.php new file mode 100644 index 0000000..3b22a4b --- /dev/null +++ b/src/Http/Request.php @@ -0,0 +1,239 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class Request +{ + protected $uri; + protected $method; + protected $headers; + + /** @var null|string */ + protected $content; + protected $pathInfo; + protected $basicAuthUser; + protected $basicAuthPass; + + public function __construct($requestUri, $requestMethod = 'GET') + { + $this->setRequestUri(new Uri($requestUri)); + $this->setRequestMethod($requestMethod); + $this->headers = []; + $this->content = null; + $this->pathInfo = null; + $this->basicAuthUser = null; + $this->basicAuthPass = null; + } + + public function __toString() + { + $s = PHP_EOL; + $s .= '*Request*'.PHP_EOL; + $s .= 'Request Method: '.$this->getRequestMethod().PHP_EOL; + $s .= 'Request URI: '.$this->getRequestUri()->getUri().PHP_EOL; + if (null !== $this->getBasicAuthUser()) { + $s .= 'Basic Authentication: '.$this->getBasicAuthUser().':'.$this->getBasicAuthPass(); + } + $s .= 'Headers:'.PHP_EOL; + foreach ($this->getHeaders(true) as $v) { + $s .= "\t".$v.PHP_EOL; + } + $s .= 'Content:'.PHP_EOL; + $s .= $this->getContent(); + + return $s; + } + + public static function fromIncomingRequest(IncomingRequest $i) + { + $request = new static($i->getRequestUri(), $i->getRequestMethod()); + $request->setHeaders($i->getRequestHeaders()); + $request->setContent($i->getContent()); + $request->setPathInfo($i->getPathInfo()); + $request->setBasicAuthUser($i->getBasicAuthUser()); + $request->setBasicAuthPass($i->getBasicAuthPass()); + + return $request; + } + + public function setRequestUri(Uri $u) + { + $this->uri = $u; + } + + public function getRequestUri() + { + return $this->uri; + } + + public function setRequestMethod($method) + { + if (!in_array($method, ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'])) { + throw new RequestException('invalid or unsupported request method'); + } + $this->method = $method; + } + + public function getRequestMethod() + { + return $this->method; + } + + public function setPostParameters(array $parameters) + { + if ($this->getRequestMethod() !== 'POST') { + throw new RequestException('request method should be POST'); + } + $this->setHeader('Content-Type', 'application/x-www-form-urlencoded'); + $this->setContent(http_build_query($parameters, '', '&')); + } + + public function getQueryParameters() + { + if ($this->uri->getQuery() === null) { + return []; + } + $parameters = []; + parse_str($this->uri->getQuery(), $parameters); + + return $parameters; + } + + public function getQueryParameter($key) + { + $parameters = $this->getQueryParameters(); + + return (array_key_exists($key, $parameters) && 0 !== strlen($parameters[$key])) ? $parameters[$key] : null; + } + + public function getPostParameter($key) + { + $parameters = $this->getPostParameters(); + + return (array_key_exists($key, $parameters) && 0 !== strlen($parameters[$key])) ? $parameters[$key] : null; + } + + public function getPostParameters() + { + if ($this->getRequestMethod() !== 'POST') { + throw new RequestException('request method should be POST'); + } + // FIXME: we should check to see if it was a proper FORM post! + $parameters = []; + parse_str($this->getContent(), $parameters); + + return $parameters; + } + + public function setHeaders(array $headers) + { + foreach ($headers as $k => $v) { + $this->setHeader($k, $v); + } + } + + public function setHeader($key, $value) + { + $k = self::normalizeHeaderKey($key); + $this->headers[$k] = $value; + } + + public function getHeader($key) + { + $k = self::normalizeHeaderKey($key); + + return array_key_exists($k, $this->headers) ? $this->headers[$k] : null; + } + + public function getHeaders($formatted = false) + { + if (!$formatted) { + return $this->headers; + } + $hdrs = []; + foreach ($this->headers as $k => $v) { + array_push($hdrs, $k.': '.$v); + } + + return $hdrs; + } + + /** + * @param string $content + */ + public function setContent($content) + { + $this->content = $content; + } + + public function getContent() + { + return $this->content; + } + + public function setContentType($contentType) + { + $this->setHeader('Content-Type', $contentType); + } + + public function getContentType() + { + return $this->getHeader('Content-Type'); + } + + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + } + + public function getPathInfo() + { + return $this->pathInfo; + } + + public function setBasicAuthUser($u) + { + $this->basicAuthUser = $u; + } + + public function setBasicAuthPass($p) + { + $this->basicAuthPass = $p; + } + + public function getBasicAuthUser() + { + return $this->basicAuthUser; + } + + public function getBasicAuthPass() + { + return $this->basicAuthPass; + } + + public static function normalizeHeaderKey($key) + { + // strip HTTP_ if needed + if (0 === strpos($key, 'HTTP_') || 0 === strpos($key, 'HTTP-')) { + $key = substr($key, 5); + } + // convert to capitals and replace '-' with '_' + return strtoupper(str_replace('-', '_', $key)); + } +} diff --git a/src/Http/RequestException.php b/src/Http/RequestException.php new file mode 100644 index 0000000..10228b6 --- /dev/null +++ b/src/Http/RequestException.php @@ -0,0 +1,23 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class RequestException extends \Exception +{ +} diff --git a/src/Http/Response.php b/src/Http/Response.php new file mode 100644 index 0000000..e78633b --- /dev/null +++ b/src/Http/Response.php @@ -0,0 +1,236 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class Response +{ + private $headers; + + /** @var null|string */ + private $content; + + private $statusCode; + + private $statusCodes = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ]; + + public function __construct($statusCode = 200, $contentType = 'text/html') + { + $this->headers = []; + $this->setStatusCode($statusCode); + $this->setContentType($contentType); + $this->setContent(null); + } + + public function __toString() + { + $s = PHP_EOL; + $s .= '*Response*'.PHP_EOL; + $s .= 'Status:'.PHP_EOL; + $s .= "\t".$this->getStatusLine().PHP_EOL; + $s .= 'Headers:'.PHP_EOL; + foreach ($this->getHeaders() as $k => $v) { + $s .= "\t".($k.': '.$v).PHP_EOL; + } + $s .= 'Content:'.PHP_EOL; + $s .= $this->content; + + return $s; + } + + public function getContent() + { + return $this->content; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getStatusReason() + { + return $this->statusCodes[$this->statusCode]; + } + + public function setContentType($contentType) + { + $this->setHeader('Content-Type', $contentType); + } + + public function getContentType() + { + return $this->getHeader('Content-Type'); + } + + /** + * @param string|null $content + */ + public function setContent($content) + { + $this->content = $content; + } + + public function setStatusCode($code) + { + if (!is_numeric($code) || !array_key_exists($code, $this->statusCodes)) { + throw new ResponseException('invalid status code'); + } + $this->statusCode = (int) $code; + } + + public function setHeaders(array $headers) + { + foreach ($headers as $k => $v) { + $this->setHeader($k, $v); + } + } + + public function setHeader($headerKey, $headerValue) + { + $foundHeaderKey = $this->getHeaderKey($headerKey); + if ($foundHeaderKey === null) { + $this->headers[$headerKey] = $headerValue; + } else { + $this->headers[$foundHeaderKey] = $headerValue; + } + } + + public function getHeader($headerKey) + { + $headerKey = $this->getHeaderKey($headerKey); + + return $headerKey !== null ? $this->headers[$headerKey] : null; + } + + public function getHeaders($formatted = false) + { + if (!$formatted) { + return $this->headers; + } + $hdrs = []; + foreach ($this->headers as $k => $v) { + array_push($hdrs, $k.': '.$v); + } + + return $hdrs; + } + + public function getStatusLine() + { + return 'HTTP/1.1 '.$this->getStatusCode().' '.$this->getStatusReason(); + } + + public function sendResponse() + { + header($this->getStatusLine()); + foreach ($this->getHeaders() as $k => $v) { + header($k.': '.$v); + } + echo $this->content; + } + + /** + * Construct the Response from a file, you can create the + * dumps from actual traffic using "curl -i http://www.example.org > dump.txt". + */ + public static function fromFile($file) + { + $data = @file_get_contents($file); + if (false === $data) { + throw new ResponseException('unable to read file'); + } + $response = new self(); + + // separate the headers from the content + list($headerLines, $contentData) = explode("\r\n\r\n", $data); + + $headerLinesArray = explode("\r\n", $headerLines); + + // First header is HTTP response code, e.g.: HTTP/1.1 200 OK + $responseCode = substr($headerLinesArray[0], 9, 3); + $response->setStatusCode($responseCode); + + unset($headerLinesArray[0]); + foreach ($headerLinesArray as $headerLine) { + list($k, $v) = explode(':', $headerLine); + $response->setHeader(trim($k), trim($v)); + } + $response->setContent($contentData); + + return $response; + } + + /** + * Look for a header in a case insensitive way. It is possible to have a + * header key "Content-type" or a header key "Content-Type", these should + * be treated as the same. + * + * @param string $headerKey the name of the header to search for + * @returns null|string The name of the header as it was set (original case) + */ + protected function getHeaderKey($headerKey) + { + $headerKeys = array_keys($this->headers); + $keyPositionInArray = array_search(strtolower($headerKey), array_map('strtolower', $headerKeys)); + + return ($keyPositionInArray === false) ? null : $headerKeys[$keyPositionInArray]; + } +} diff --git a/src/Http/ResponseException.php b/src/Http/ResponseException.php new file mode 100644 index 0000000..b838fe3 --- /dev/null +++ b/src/Http/ResponseException.php @@ -0,0 +1,23 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class ResponseException extends \Exception +{ +} diff --git a/src/Http/Uri.php b/src/Http/Uri.php new file mode 100644 index 0000000..f2a2f3b --- /dev/null +++ b/src/Http/Uri.php @@ -0,0 +1,142 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class Uri +{ + private $uriParts; + + public function __construct($inputUri) + { + $this->validateUri($inputUri); + $this->setUriParts($inputUri); + } + + public function getScheme() + { + return array_key_exists('scheme', $this->uriParts) ? $this->uriParts['scheme'] : null; + } + + public function getUser() + { + return array_key_exists('user', $this->uriParts) ? $this->uriParts['user'] : null; + } + + public function getPass() + { + return array_key_exists('pass', $this->uriParts) ? $this->uriParts['pass'] : null; + } + + public function getHost() + { + return array_key_exists('host', $this->uriParts) ? $this->uriParts['host'] : null; + } + + public function getPort() + { + return array_key_exists('port', $this->uriParts) ? $this->uriParts['port'] : null; + } + + public function getPath() + { + return array_key_exists('path', $this->uriParts) ? $this->uriParts['path'] : null; + } + + public function getQuery() + { + return array_key_exists('query', $this->uriParts) ? $this->uriParts['query'] : null; + } + + public function setQuery($query) + { + $this->uriParts['query'] = $query; + } + + public function appendQuery($query) + { + if ($this->getQuery() === null) { + $this->setQuery($query); + } else { + $this->setQuery($this->getQuery().'&'.$query); + } + } + + public function getFragment() + { + return array_key_exists('fragment', $this->uriParts) ? $this->uriParts['fragment'] : null; + } + + public function setFragment($fragment) + { + $this->uriParts['fragment'] = $fragment; + } + + public function getUri() + { + $uri = $this->constructUriFromParts(); + $this->validateUri($uri); + + return $uri; + } + + private function validateUri($uri) + { + $u = filter_var($uri, FILTER_VALIDATE_URL); + if ($u === false) { + throw new UriException('the uri is malformed'); + } + } + + private function setUriParts($uri) + { + $this->uriParts = parse_url($uri); + } + + private function constructUriFromParts() + { + $uri = ''; + if (null !== $this->getScheme()) { + $uri .= $this->getScheme().'://'; + } + if (null !== $this->getUser()) { + $uri .= $this->getUser(); + if (null !== $this->getPass()) { + $uri .= ':'.$this->getPass(); + } + $uri .= '@'; + } + if (null !== $this->getHost()) { + $uri .= $this->getHost(); + } + if (null !== $this->getPort()) { + $uri .= ':'.$this->getPort(); + } + if (null !== $this->getPath()) { + $uri .= $this->getPath(); + } + if (null !== $this->getQuery()) { + $uri .= '?'.$this->getQuery(); + } + if (null !== $this->getFragment()) { + $uri .= '#'.$this->getFragment(); + } + + return $uri; + } +} diff --git a/src/Http/UriException.php b/src/Http/UriException.php new file mode 100644 index 0000000..8b11035 --- /dev/null +++ b/src/Http/UriException.php @@ -0,0 +1,23 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Http; + +class UriException extends \Exception +{ +} diff --git a/src/Rest/Plugin/BasicAuthentication.php b/src/Rest/Plugin/BasicAuthentication.php new file mode 100644 index 0000000..ec3e0de --- /dev/null +++ b/src/Rest/Plugin/BasicAuthentication.php @@ -0,0 +1,69 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Rest\Plugin; + +use fkooman\VootProvider\Http\JsonResponse; +use fkooman\VootProvider\Http\Request; +use fkooman\VootProvider\Rest\ServicePluginInterface; + +class BasicAuthentication implements ServicePluginInterface +{ + /** @var string */ + private $basicAuthUser; + + /** @var string */ + private $basicAuthPass; + + /** @var string */ + private $basicAuthRealm; + + public function __construct($basicAuthUser, $basicAuthPass, $basicAuthRealm = 'Protected Resource') + { + $this->basicAuthUser = $basicAuthUser; + $this->basicAuthPass = $basicAuthPass; + $this->basicAuthRealm = $basicAuthRealm; + } + + /** + * @return bool|\fkooman\VootProvider\Http\Response + */ + public function execute(Request $request) + { + $requestBasicAuthUser = $request->getBasicAuthUser(); + $requestBasicAuthPass = $request->getBasicAuthPass(); + + if ($this->basicAuthUser !== $requestBasicAuthUser || $this->basicAuthPass !== $requestBasicAuthPass) { + $response = new JsonResponse(401); + $response->setHeader( + 'WWW-Authenticate', + sprintf('Basic realm="%s"', $this->basicAuthRealm) + ); + $response->setContent( + [ + 'code' => 401, + 'error' => 'Unauthorized', + ] + ); + + return $response; + } + + return true; + } +} diff --git a/src/Rest/Service.php b/src/Rest/Service.php new file mode 100644 index 0000000..b8d5fba --- /dev/null +++ b/src/Rest/Service.php @@ -0,0 +1,235 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Rest; + +use fkooman\VootProvider\Http\JsonResponse; +use fkooman\VootProvider\Http\Request; +use fkooman\VootProvider\Http\RequestException; +use fkooman\VootProvider\Http\Response; +use UnexpectedValueException; + +class Service +{ + /** @var \fkooman\VootProvider\Http\Request */ + private $request; + + /** @var array */ + private $match; + + /** @var array */ + private $supportedMethods; + + /** @var array */ + private $beforeMatchingPlugins; + + /** @var array */ + private $beforeEachMatchPlugins; + + /** + * Create a new Service object. + * + * @param \fkooman\VootProvider\Http\Request $request the HTTP request + */ + public function __construct(Request $request) + { + $this->request = $request; + $this->match = []; + $this->supportedMethods = []; + + $this->beforeMatchingPlugins = []; + $this->beforeEachMatchPlugins = []; + } + + /** + * Register a plugin that is always run before the matching starts. + * + * @param \fkooman\VootProvider\Http\ServicePluginInterface $servicePlugin the plugin to + * register + */ + public function registerBeforeMatchingPlugin(ServicePluginInterface $servicePlugin) + { + $this->beforeMatchingPlugins[] = $servicePlugin; + } + + /** + * Register a plugin that is run for every match, allowing you to skip it + * for particular matches. + * + * @param \fkooman\VootProvider\Http\ServicePluginInterface the plugin to register + */ + public function registerBeforeEachMatchPlugin(ServicePluginInterface $servicePlugin) + { + $this->beforeEachMatchPlugins[] = $servicePlugin; + } + + /** + * Register a method/pattern match. + * + * @param string $requestMethod the request method, e.g. 'GET', 'POST' + * @param string $requestPattern the pattern to match + * @param callback $callback the callback to execute when this pattern + * matches + * @param array $skipPlugin the full namespaced names of the plugin classes + * to skip + */ + public function match($requestMethod, $requestPattern, $callback, array $skipPlugin = []) + { + $this->match[] = [ + 'requestMethod' => $requestMethod, + 'requestPattern' => $requestPattern, + 'callback' => $callback, + 'skipPlugin' => $skipPlugin, + ]; + if (!in_array($requestMethod, $this->supportedMethods)) { + $this->supportedMethods[] = $requestMethod; + } + } + + /** + * Run the Service. + * + * @return \fkooman\VootProvider\Http\Response the HTTP response object after mathing + * is done and the appropriate callback was + * executed. If nothing matches either 404 + * or 405 response is returned. + */ + public function run() + { + // run the beforeMatchingPlugins + foreach ($this->beforeMatchingPlugins as $plugin) { + $response = $plugin->execute($this->request); + if ($response instanceof Response) { + return $response; + } + } + + foreach ($this->match as $m) { + // run the beforeEachMatchPlugins + foreach ($this->beforeEachMatchPlugins as $plugin) { + // only run when plugin should not be skipped + if (in_array(get_class($plugin), $m['skipPlugin'])) { + continue; + } + $response = $plugin->execute($this->request); + if ($response instanceof Response) { + return $response; + } + } + + $response = $this->matchRest( + $m['requestMethod'], + $m['requestPattern'], + $m['callback'] + ); + + // false indicates not a match + if (false !== $response) { + if ($response instanceof Response) { + return $response; + } + if (!is_string($response)) { + throw new UnexpectedValueException('callback MUST return Response object or string'); + } + $responseObj = new Response(200, 'text/html'); + $responseObj->setContent($response); + + return $responseObj; + } + } + + // handle non matching patterns + if (in_array($this->request->getRequestMethod(), $this->supportedMethods)) { + $response = new JsonResponse(404); + $response->setContent( + [ + 'code' => 404, + 'error' => 'Not Found', + ] + ); + + return $response; + } + + $response = new JsonResponse(405); + $response->setHeader('Allow', implode(',', $this->supportedMethods)); + $response->setContent( + [ + 'code' => 405, + 'error' => 'Method Not Allowed', + ] + ); + + return $response; + } + + private function matchRest($requestMethod, $requestPattern, $callback) + { + if ($requestMethod !== $this->request->getRequestMethod()) { + return false; + } + // if no pattern is defined, all paths are valid + if (null === $requestPattern) { + return call_user_func_array($callback, []); + } + // both the pattern and request path should start with a "/" + if (0 !== strpos($this->request->getPathInfo(), '/') || 0 !== strpos($requestPattern, '/')) { + return false; + } + + // handle optional parameters + $requestPattern = str_replace(')', ')?', $requestPattern); + + // check for variables in the requestPattern + $pma = preg_match_all('#:([\w]+)\+?#', $requestPattern, $matches); + if (false === $pma) { + throw new RequestException('regex for variable search failed'); + } + if (0 === $pma) { + // no variables in the pattern, pattern and request must be identical + if ($this->request->getPathInfo() === $requestPattern) { + return call_user_func_array($callback, []); + } + // FIXME?! + //return false; + } + // replace all the variables with a regex so the actual value in the request + // can be captured + foreach ($matches[0] as $m) { + // determine pattern based on whether variable is wildcard or not + $mm = str_replace([':', '+'], '', $m); + $pattern = (strpos($m, '+') === strlen($m) - 1) ? '(?P<'.$mm.'>(.+?[^/]))' : '(?P<'.$mm.'>([^/]+))'; + $requestPattern = str_replace($m, $pattern, $requestPattern); + } + $pm = preg_match('#^'.$requestPattern.'$#', $this->request->getPathInfo(), $parameters); + if (false === $pm) { + throw new RequestException('regex for path matching failed'); + } + if (0 === $pm) { + // request path does not match pattern + return false; + } + foreach ($parameters as $k => $v) { + if (!is_string($k)) { + unset($parameters[$k]); + } + } + // request path matches pattern! + return call_user_func_array($callback, array_values($parameters)); + } +} diff --git a/src/Rest/ServicePluginInterface.php b/src/Rest/ServicePluginInterface.php new file mode 100644 index 0000000..b2a5575 --- /dev/null +++ b/src/Rest/ServicePluginInterface.php @@ -0,0 +1,29 @@ +. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace fkooman\VootProvider\Rest; + +use fkooman\VootProvider\Http\Request; + +interface ServicePluginInterface +{ + /** + * @return bool|\fkooman\VootProvider\Http\Response + */ + public function execute(Request $request); +} diff --git a/web/voot.php b/web/voot.php index 633224e..6d38403 100644 --- a/web/voot.php +++ b/web/voot.php @@ -17,12 +17,12 @@ */ require_once dirname(__DIR__).'/vendor/autoload.php'; -use fkooman\Http\IncomingRequest; -use fkooman\Http\JsonResponse; -use fkooman\Http\Request; -use fkooman\Rest\Plugin\BasicAuthentication; -use fkooman\Rest\Service; use fkooman\VootProvider\Config\Config; +use fkooman\VootProvider\Http\IncomingRequest; +use fkooman\VootProvider\Http\JsonResponse; +use fkooman\VootProvider\Http\Request; +use fkooman\VootProvider\Rest\Plugin\BasicAuthentication; +use fkooman\VootProvider\Rest\Service; use fkooman\VootProvider\VootStorageException; try { From 1650aabb4f3894ad5a56ff993cbeb28ceefd4382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 13:29:57 +0100 Subject: [PATCH 5/6] update dependencies --- composer.json | 5 +++++ composer.lock | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8dcf827..8f2d46c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,12 @@ "license": "Apache-2.0", "name": "fkooman/php-voot-provider", "require": { + "ext-filter": "*", + "ext-json": "*", "ext-ldap": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-spl": "*", "php": ">=5.4" } } diff --git a/composer.lock b/composer.lock index bc238b5..6b6c6b6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "bf1a49de36dd4eab90ec453a2415cd9c", + "content-hash": "0d84b56a13144c05011409b106fd1812", "packages": [], "packages-dev": [], "aliases": [], @@ -13,7 +13,12 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { + "ext-filter": "*", + "ext-json": "*", "ext-ldap": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-spl": "*", "php": ">=5.4" }, "platform-dev": [] From 4be03edbc80b7e352886fd5b8f8b08d66e4f7163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Fri, 17 Feb 2017 14:01:02 +0100 Subject: [PATCH 6/6] implement constant time password compare for Basic auth --- composer.json | 3 +- composer.lock | 113 +++++++++++++++++++++++- src/Rest/Plugin/BasicAuthentication.php | 14 ++- 3 files changed, 126 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 8f2d46c..94ad044 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "ext-pcre": "*", "ext-pdo": "*", "ext-spl": "*", - "php": ">=5.4" + "php": ">=5.4", + "symfony/polyfill-php56": "^1.3" } } diff --git a/composer.lock b/composer.lock index 6b6c6b6..b71e7b1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,117 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "0d84b56a13144c05011409b106fd1812", - "packages": [], + "content-hash": "b73d1609ae16c93bd1ba9498fa9c89af", + "packages": [ + { + "name": "symfony/polyfill-php56", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2016-11-14T01:06:16+00:00" + } + ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", diff --git a/src/Rest/Plugin/BasicAuthentication.php b/src/Rest/Plugin/BasicAuthentication.php index ec3e0de..eafdedf 100644 --- a/src/Rest/Plugin/BasicAuthentication.php +++ b/src/Rest/Plugin/BasicAuthentication.php @@ -48,7 +48,7 @@ public function execute(Request $request) $requestBasicAuthUser = $request->getBasicAuthUser(); $requestBasicAuthPass = $request->getBasicAuthPass(); - if ($this->basicAuthUser !== $requestBasicAuthUser || $this->basicAuthPass !== $requestBasicAuthPass) { + if ($this->basicAuthUser !== $requestBasicAuthUser || !$this->checkPassword($this->basicAuthPass, $requestBasicAuthPass)) { $response = new JsonResponse(401); $response->setHeader( 'WWW-Authenticate', @@ -66,4 +66,16 @@ public function execute(Request $request) return true; } + + /** + * @param string $knownString the expected password + * @param string $userString the (user) provided password + * + * @return bool + */ + private function checkPassword($knownString, $userString) + { + // constant time string compare + return hash_equals($knownString, $userString); + } }