From 24356b322e045c7370026cba28448e34f64f1947 Mon Sep 17 00:00:00 2001 From: Michal Borychowski <807297+boryn@users.noreply.github.com> Date: Sun, 13 Mar 2022 09:38:09 +0100 Subject: [PATCH 01/18] Don't raise error if no permission to write cache file --- src/PasswordGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index e0aa6fa..de79ee6 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -126,7 +126,7 @@ private function get_url_data(string $url): string */ private function save_wordlist() { - file_put_contents($this->wordCacheFile, json_encode($this->wordList)); + @file_put_contents($this->wordCacheFile, json_encode($this->wordList)); } /** From 62f4bf4abdba08f8fc71881d676956dac7d575d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sun, 13 Mar 2022 18:36:47 +0100 Subject: [PATCH 02/18] add flag to force suppressed error message output --- src/PasswordGenerator.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index de79ee6..3b3062e 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -51,11 +51,15 @@ class PasswordGenerator * @param array $params optional config array * @param boolean $fetch true if data from URL should be fetched, false to * prefer cached wordlist; defaults to true + * @param boolean $verbose true if suppressed error messages should be + * printed; defaults to false * * @throws InvalidArgumentException if the URL is not valid */ - public function __construct(array $params = [], bool $fetch = true) + public function __construct(array $params = [], bool $fetch = true, bool $verbose = false) { + $this->verbose = $verbose; + set_error_handler(array($this, 'error_handler')); foreach ($params as $key => $value) { $this->$key = $value; } @@ -243,4 +247,15 @@ public function generate(string $pattern = 'wisw'): string return $result; } + private function error_handler(int $error_level, string $error_message): bool + { + if (error_reporting() !== 0) { + return false; + } + if ($this->verbose) { + echo 'WARN ', $error_message, "\n"; + } + return true; + } + } From 69ce75415cb49a2a30bbb701eb28382192208c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sun, 13 Mar 2022 18:37:57 +0100 Subject: [PATCH 03/18] update version --- LICENSE.md | 2 +- composer.json | 4 ++-- src/PasswordGenerator.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index f92f340..d5a0280 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright 2018 Johann Werner +Copyright 2018-2022 Johann Häger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/composer.json b/composer.json index 78c8a0f..905893f 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,8 @@ "type": "library", "authors": [ { - "name": "Johann Werner", - "email": "johann.werner@posteo.de" + "name": "Johann Häger", + "email": "johann.haeger@posteo.de" } ], "license": "MIT", diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 3b3062e..c499120 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -22,8 +22,8 @@ * * @example ../example/PasswordGenerator.example.php example Class in action. * - * @author Johann Werner - * @version 2.0.0 + * @author Johann Häger + * @version 2.0.2 * @since 1.0.1 * @license MIT License */ From 27a2fa1a0013042e5086a2e71626a29bf14f8823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sun, 13 Mar 2022 18:59:02 +0100 Subject: [PATCH 04/18] add some keywords for composer --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 905893f..9c350af 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "darkv/php-password-generator", "description": "The PHP class PasswordGenerator serves as a password generator to create memorable passwords like the keychain of macOS ≤ 10.14 did.", + "keywords": ["password", "memorable password", "memorable password generator", "password generator", "generator"], "type": "library", "authors": [ { From 722d2181293e4fed4bb57236196bd05d64e37ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sun, 13 Mar 2022 19:11:27 +0100 Subject: [PATCH 05/18] ignore test file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3a58966..73f550b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .DS_Store wordlist.json - +example/mywords.json From f3293e87f01f064e935770f2ec6d434f3bc062ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sun, 13 Mar 2022 21:37:21 +0100 Subject: [PATCH 06/18] small fixes to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b7c87f..136c658 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ composer require darkv/php-password-generator Copy the file *PasswordGenerator.php* into your project and include it in your own PHP file(s) with `include 'PasswordGenerator.php';`. Then create an instance either by using the predefined static methods for specific languages or customize it yourself by using the standard constructor. ```php -include 'PasswordGenerator.class.php'; +include 'PasswordGenerator.php'; // Import the namespace use \Darkv\PhpPasswordGenerator\PasswordGenerator; @@ -52,7 +52,7 @@ If you don't provide your own pattern the default pattern **wisw** is used. Some ## Word Lists -The class uses RSS feeds to build a word list from which random words are used for password generation. The class has some predefined configuration for the languages English and German but can be customized too: +The class uses RSS feeds to build a word list from which random words are used for password generation. The class has some predefined configurations for the languages English and German but can be customized too: ```php include 'PasswordGenerator.php'; @@ -73,7 +73,7 @@ $gen = new PasswordGenerator([ ]); ``` -The params *minLength* and *maxLength* denote the allowed lengths of the words from the URL source to get into the word list. If a word list has been successfully built that list is saved into the file `wordlist.json`. The next time you create an instance of PasswordGenerator and the URL source cannot be contacted or does not contain any usable words that cached list is loaded instead. If you reuse the very same instance the word list is also reused so no further HTTP requests are generated. +The params *minLength* and *maxLength* denote the allowed lengths of the words from the URL source to get into the word list. If a word list has been successfully built, that list is saved into the file `wordlist.json`. The next time you create an instance of PasswordGenerator and the URL source cannot be contacted or does not contain any usable words that cached list is loaded instead. If you reuse the very same instance the word list is also reused so no further HTTP requests are generated. Optionally you can specify *wordCacheFile* to control the location and name of the cached wordlist. From 27cf6798e95d702bb1b531517cf28d7c32aa06be Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 18 Oct 2023 22:08:02 +0200 Subject: [PATCH 07/18] Add redirect feature and tune the script a little --- .gitignore | 3 +- README.md | 4 +- composer.json | 3 +- src/PasswordGenerator.php | 167 +++++++++++++++++++++++++------------- 4 files changed, 118 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 73f550b..94a009c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store - +.idea +.ddev wordlist.json example/mywords.json diff --git a/README.md b/README.md index 136c658..328626f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ include 'PasswordGenerator.php'; // Import the namespace use \Darkv\PhpPasswordGenerator\PasswordGenerator; -// create instance with English word list +// create instance with an English word list $gen = PasswordGenerator::EN(); // generate a password @@ -110,7 +110,7 @@ Location of the cache file can be passed as parameter to `CACHED`, by default wo ### URL Sources -As source for word lists this class uses a configurable RSS feed. The feed has to be in XML format and contain *description* tags from which the textual content is extracted. +As a source for word lists, this class uses a configurable RSS feed. The feed has to be in XML format and contain *description* tags from which the textual content is extracted. ## License diff --git a/composer.json b/composer.json index 9c350af..4318d02 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "require": { "php" : ">=7.4", "ext-curl": "*", - "ext-dom": "*" + "ext-dom": "*", + "ext-json": "*" } } diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index c499120..411d82d 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -10,17 +10,19 @@ /** * A password generator that generates memorable passwords similar to the - * keychain of macOS ≤ 10.14. For this it uses a public RSS feed to build up a list of + * keychain of macOS ≤ 10.14. + * For this, it uses a public RSS feed to build up a list of * words to be used in passwords. * - * After successfully creating a list of words that list is written to disk - * in the file wordlist.json. The next time you create an instance + * After successfully creating a list of words, that list is written to disk + * in the file wordlist.json. + * The next time you create an instance * of this class and the URL is unavailable that cached version is then used. * * Consecutive password generations with the very same instance won't * recreate the word list but reuse the former one. * - * @example ../example/PasswordGenerator.example.php example Class in action. + * @example ./example/PasswordGenerator.example.php example Class in action. * * @author Johann Häger * @version 2.0.2 @@ -29,12 +31,30 @@ */ class PasswordGenerator { + /** @var string $url Url of word ressource */ private string $url; + + /** @var int $minLength Minimum length of a password */ private int $minLength; + + /** @var int $maxLength Maximum length of a password */ private int $maxLength; + + /** @var array $wordList Cache of the words */ private array $wordList = []; + + /** @var string $wordCacheFile Filename of the cache file */ private string $wordCacheFile = 'wordlist.json'; + /** @var bool $follow_redirects Follow redirects in case, the ressource moved */ + private bool $follow_redirects = false; + + /** @var int $allowed_redirects Number of redirects the script will follow */ + private int $allowed_redirects = 2; + + /** @var bool $verbose */ + private bool $verbose; + /** * Creates an instance of the password generator. You can pass an optional * array with values that should override the default values for the keys: @@ -69,7 +89,7 @@ public function __construct(array $params = [], bool $fetch = true, bool $verbos return; } } - // if cache failed retrieve anyway + // if cache failed retrieves anyway if (!isset($this->url) || !filter_var($this->url, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException(sprintf('Invalid URL: %s', $this->url)); } @@ -80,38 +100,66 @@ public function __construct(array $params = [], bool $fetch = true, bool $verbos } /** - * Populate the wordlist + * Reads in the wordlist from the filesystem. */ - private function populate_wordlist() + private function read_wordlist() { - $input = $this->get_url_data($this->url); - $doc = new DOMDocument(); - @$doc->loadXML($input); - $descriptions = $doc->getElementsByTagName('description'); - $wordlist = array(); - foreach ($descriptions as $description) { - $text = $description->textContent; - $words = explode(' ', $text); - foreach ($words as $word) { - $cleanword = preg_replace('/[,.;:?!\'"]+/', '', trim($word)); - $wordlength = strlen($cleanword); - - if ($wordlength >= $this->minLength && $wordlength <= $this->maxLength && ctype_alpha($cleanword)) { - $wordlist[strtoupper(substr($cleanword, 0, 1)) . strtolower(substr($cleanword, 1))] = 1; + if (file_exists($this->wordCacheFile)) { + try { + $this->wordList = json_decode(file_get_contents($this->wordCacheFile), true); + } catch (Exception $e) { + if ($this->verbose) { + echo 'WARN ', $e->getMessage(), "\n"; } } } - $this->wordList = array_keys($wordlist); + } + + /** + * Populate the wordlist from the URL. + * @return void + */ + private function populate_wordlist(): void + { + try { + $input = $this->get_url_data($this->url); + + if (empty($input)) { + throw new Exception('Empty input. Check URL.'); + } + + $doc = new DOMDocument(); + $doc->loadXML($input); + $descriptions = $doc->getElementsByTagName('description'); + $wordlist = array(); + foreach ($descriptions as $description) { + $text = $description->textContent; + $words = explode(' ', $text); + foreach ($words as $word) { + $cleanword = preg_replace('/[,.;:?!\'"]+/', '', trim($word)); + $wordlength = strlen($cleanword); + + if ($wordlength >= $this->minLength && $wordlength <= $this->maxLength && ctype_alpha($cleanword)) { + $wordlist[strtoupper(substr($cleanword, 0, 1)) . strtolower(substr($cleanword, 1))] = 1; + } + } + } + $this->wordList = array_keys($wordlist); - if (count($wordlist) > 0) { - $this->save_wordlist(); + if (count($wordlist) > 0) { + $this->save_wordlist(); + } + } catch (Exception $e) { + if ($this->verbose) { + echo 'WARN ', $e->getMessage(), "\n"; + } } } /** - * - * @param $url - * @return bool|string + * Fetches data from the given URL. + * @param string $url + * @return string */ private function get_url_data(string $url): string { @@ -119,6 +167,12 @@ private function get_url_data(string $url): string curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + + if ($this->follow_redirects) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->allowed_redirects); + } + $data = curl_exec($ch); curl_close($ch); @@ -127,50 +181,47 @@ private function get_url_data(string $url): string /** * Saves the wordlist to filesystem. + * @return void */ - private function save_wordlist() + private function save_wordlist(): void { - @file_put_contents($this->wordCacheFile, json_encode($this->wordList)); - } - - /** - * Reads in the wordlist from the filesystem. - */ - private function read_wordlist() - { - if (file_exists($this->wordCacheFile)) { - $this->wordList = json_decode(file_get_contents($this->wordCacheFile), true); + try { + file_put_contents($this->wordCacheFile, json_encode($this->wordList)); + } catch (Exception $e) { + if ($this->verbose) { + echo 'WARN ', $e->getMessage(), "\n"; + } } } /** * Creates an instance of password generator that will use German wordlist. - * + * @param array|null $params optional config array * @static * @return PasswordGenerator configured instance */ - public static function DE(): PasswordGenerator + public static function DE(?array $params = []): PasswordGenerator { return new self([ - 'url' => 'https://www.tagesschau.de/newsticker.rdf', - 'minLength' => 8, - 'maxLength' => 15, - ]); + 'url' => 'https://www.tagesschau.de/newsticker.rdf', + 'minLength' => 8, + 'maxLength' => 15, + ] + $params); } /** * Creates an instance of password generator that will use English wordlist. - * + * @param array|null $params optional config array * @static * @return PasswordGenerator configured instance */ - public static function EN(): PasswordGenerator + public static function EN(?array $params = []): PasswordGenerator { return new self([ - 'url' => 'https://rss.dw.com/rdf/rss-en-all', - 'minLength' => 4, - 'maxLength' => 12, - ]); + 'url' => 'https://rss.dw.com/rdf/rss-en-all', + 'minLength' => 4, + 'maxLength' => 12, + ] + $params); } /** @@ -179,17 +230,17 @@ public static function EN(): PasswordGenerator * appropriate file wordlist.json present. * * @static - * @param $wordCacheFile path of custom cache file to use, defaults to default - * wordlist.json file - * @return PasswordGenerator configured instance that uses cache only + * @param string|null $wordCacheFile path of custom cache file to use, defaults to default wordlist.json file + * @param array|null $params + * @return PasswordGenerator configured instance */ - public static function CACHED(string $wordCacheFile = null): PasswordGenerator + public static function CACHED(string $wordCacheFile = null, ?array $params = []): PasswordGenerator { $a = []; if (!empty($wordCacheFile)) { $a['wordCacheFile'] = $wordCacheFile; } - return new self($a, false); + return new self($a + $params, false); } /** @@ -247,6 +298,12 @@ public function generate(string $pattern = 'wisw'): string return $result; } + /** + * Handles errors. + * @param int $error_level + * @param string $error_message + * @return bool + */ private function error_handler(int $error_level, string $error_message): bool { if (error_reporting() !== 0) { From 31788911ae858b0fee538a22c6739ff01d286cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:11:18 +0200 Subject: [PATCH 08/18] some reformatting --- composer.json | 2 +- src/PasswordGenerator.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4318d02..20d49c6 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } }, "require": { - "php" : ">=7.4", + "php": ">=7.4", "ext-curl": "*", "ext-dom": "*", "ext-json": "*" diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 411d82d..2e9355a 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -46,10 +46,10 @@ class PasswordGenerator /** @var string $wordCacheFile Filename of the cache file */ private string $wordCacheFile = 'wordlist.json'; - /** @var bool $follow_redirects Follow redirects in case, the ressource moved */ + /** @var bool $follow_redirects Follow redirects in case the resource moved */ private bool $follow_redirects = false; - /** @var int $allowed_redirects Number of redirects the script will follow */ + /** @var int $allowed_redirects Number of maximum redirects the script will follow */ private int $allowed_redirects = 2; /** @var bool $verbose */ From 6a8ff8c8e36986af6bc1d36d156572eee1c26a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:11:40 +0200 Subject: [PATCH 09/18] improve error output --- src/PasswordGenerator.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 2e9355a..a238635 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -108,9 +108,7 @@ private function read_wordlist() try { $this->wordList = json_decode(file_get_contents($this->wordCacheFile), true); } catch (Exception $e) { - if ($this->verbose) { - echo 'WARN ', $e->getMessage(), "\n"; - } + $this->warn($e->getMessage()); } } } @@ -150,9 +148,7 @@ private function populate_wordlist(): void $this->save_wordlist(); } } catch (Exception $e) { - if ($this->verbose) { - echo 'WARN ', $e->getMessage(), "\n"; - } + $this->warn($e->getMessage()); } } @@ -188,9 +184,7 @@ private function save_wordlist(): void try { file_put_contents($this->wordCacheFile, json_encode($this->wordList)); } catch (Exception $e) { - if ($this->verbose) { - echo 'WARN ', $e->getMessage(), "\n"; - } + $this->warn($e->getMessage()); } } @@ -309,10 +303,14 @@ private function error_handler(int $error_level, string $error_message): bool if (error_reporting() !== 0) { return false; } + $this->warn($error_message); + return true; + } + + private function warn(string $error_message): void + { if ($this->verbose) { echo 'WARN ', $error_message, "\n"; } - return true; } - } From 10f38c4e9735b22bffa59d2283b56f53685e454b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:12:23 +0200 Subject: [PATCH 10/18] fix redirect issue for DE constant --- src/PasswordGenerator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index a238635..a91535e 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -200,6 +200,7 @@ public static function DE(?array $params = []): PasswordGenerator 'url' => 'https://www.tagesschau.de/newsticker.rdf', 'minLength' => 8, 'maxLength' => 15, + 'follow_redirects' => true, ] + $params); } From c63dfc46af71594af44eaf9ae0363ea5e52b73f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:57:47 +0200 Subject: [PATCH 11/18] simplify redirect param --- src/PasswordGenerator.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index a91535e..9615615 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -46,11 +46,8 @@ class PasswordGenerator /** @var string $wordCacheFile Filename of the cache file */ private string $wordCacheFile = 'wordlist.json'; - /** @var bool $follow_redirects Follow redirects in case the resource moved */ - private bool $follow_redirects = false; - - /** @var int $allowed_redirects Number of maximum redirects the script will follow */ - private int $allowed_redirects = 2; + /** @var int $httpRedirects Number of maximum HTTP redirects the script will follow */ + private int $httpRedirects = 2; /** @var bool $verbose */ private bool $verbose; @@ -164,9 +161,9 @@ private function get_url_data(string $url): string curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); - if ($this->follow_redirects) { + if ($this->httpRedirects > 0) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, $this->allowed_redirects); + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->httpRedirects); } $data = curl_exec($ch); @@ -200,7 +197,6 @@ public static function DE(?array $params = []): PasswordGenerator 'url' => 'https://www.tagesschau.de/newsticker.rdf', 'minLength' => 8, 'maxLength' => 15, - 'follow_redirects' => true, ] + $params); } From 69f823dbe8fbca4ad9b49607dd0c9fc72c66b8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:58:06 +0200 Subject: [PATCH 12/18] add some more documentation --- README.md | 43 +++++++++++++++++++++++++++++++++++++++ src/PasswordGenerator.php | 4 ++++ 2 files changed, 47 insertions(+) diff --git a/README.md b/README.md index 328626f..9025f03 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,49 @@ Location of the cache file can be passed as parameter to `CACHED`, by default wo As a source for word lists, this class uses a configurable RSS feed. The feed has to be in XML format and contain *description* tags from which the textual content is extracted. +### HTTP Redirects + +If, for security reasons, you want to prevent PasswordGenerator to follow redirects when fetching the given URL, you can set the parameter *httpRedirects* to *0*. The default is *2* which follows a maximum of two redirects. + +Example: +```php +include 'PasswordGenerator.php'; + +use \Darkv\PhpPasswordGenerator\PasswordGenerator; + +// create instance with custom parameters +$gen = new PasswordGenerator([ + 'url' => 'https://www.some-url.com/source', + 'minLength' => 3, + 'maxLength' => 6, + 'httpRedirects' => 0, +]); +``` + +## Parameters + +When creating an instance of PasswordGenerator you can provide the following parameters: + +* **url** + + The URL to use to retrieve some document to extract words from. + +* **minLength** + + The minimum number of characters a word must have to be included into the word list. + +* **maxLength** + + The maximum number of characters a word may have to be included into the word list. + +* **wordCacheFile** + + The name for the cache file. Defaults to ‘wordlist.json’. + +* **httpRedirects** + + Controls if and how many HTTP redirects should be followed during URL access. Defaults to *2*. To prevent following redirects set its value to *0*. + ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 9615615..10d9fd3 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -63,6 +63,10 @@ class PasswordGenerator *
The miminum length of characters a word must have.
*
maxlength
*
The maxinum length of characters a word must have.
+ *
wordCacheFile
+ *
Filename of the cache file.
+ *
httpRedirects
+ *
Number of maximum HTTP redirects the script will follow.
* * * @param array $params optional config array From d2298e6ad04969b378ec24596757a007cb312426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Fri, 20 Oct 2023 13:58:30 +0200 Subject: [PATCH 13/18] update version --- src/PasswordGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 10d9fd3..60620c2 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -25,7 +25,7 @@ * @example ./example/PasswordGenerator.example.php example Class in action. * * @author Johann Häger - * @version 2.0.2 + * @version 2.0.3 * @since 1.0.1 * @license MIT License */ From 9510ee2c025f9f6037774156f9461948f2375a3f Mon Sep 17 00:00:00 2001 From: Lorenzo Milesi Date: Fri, 10 May 2024 11:10:58 +0200 Subject: [PATCH 14/18] Allow appending to wordlist file --- README.md | 22 ++++++++++++++++++++ example/PasswordGenerator.example.php | 12 +++++++++++ src/PasswordGenerator.php | 30 +++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9025f03..e9e6a12 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,9 @@ Optionally you can specify *wordCacheFile* to control the location and name of t To a custom instance you can pass an optional boolean parameter *fetch*. When false, the cached wordlist will be preferred but falls back to fetching if it could not be loaded. +You can also mix different RSS sources into the same wordlist file with the `appendWordlist` parameter, increasing entropy, or regularly rebuild your cache file from time to time to feed it with new words. Altought having more words is always desirable, +you can limit the number of items in the wordlist with the `limitWordlist` configuration parameter. When using append, wordlist is shuffled before being limited to the desired quantity. + ```php include 'PasswordGenerator.php'; @@ -90,6 +93,16 @@ $gen = PasswordGenerator::EN(); echo 'Password 1: ', $gen->generate(); echo 'Password 2: ', $gen->generate(); echo 'Password 3: ', $gen->generate(); + +// Append NYTimes feed to the specified wordlist, limiting to 7000 items max +$gen = new PasswordGenerator([ + 'wordCacheFile' => 'mywords.json', + 'url' => 'https://rss.nytimes.com/services/xml/rss/nyt/World.xml', + 'minLength' => 3, + 'maxLength' => 8, + 'appendWordlist' => true, + 'limitWordlist' => 7000, +]); ``` ### Caching @@ -155,6 +168,15 @@ When creating an instance of PasswordGenerator you can provide the following par Controls if and how many HTTP redirects should be followed during URL access. Defaults to *2*. To prevent following redirects set its value to *0*. +* **appendWordlist** + + If true, the fetched wordlist will be appended to the current one. If false, wordlist is generated from scratch. + +* **limitWordlist** + + Int value declaring the maximum words the wordlist must contain. Useful when using appendWordList. + + ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. diff --git a/example/PasswordGenerator.example.php b/example/PasswordGenerator.example.php index 4147236..65b3510 100644 --- a/example/PasswordGenerator.example.php +++ b/example/PasswordGenerator.example.php @@ -51,3 +51,15 @@ // generate a password with custom pattern echo $gen->generate('wiwsw'), "\n"; + +// Append NYTimes feed to the last wordlist, limiting to 700 items max +$gen = new PasswordGenerator([ + 'wordCacheFile' => 'mywords.json', + 'url' => 'https://rss.nytimes.com/services/xml/rss/nyt/World.xml', + 'minLength' => 3, + 'maxLength' => 8, + 'appendWordlist' => true, + 'limitWordlist' => 700, +]); + +echo $gen->generate('wiws'), "\n"; diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 60620c2..68714a0 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -34,10 +34,10 @@ class PasswordGenerator /** @var string $url Url of word ressource */ private string $url; - /** @var int $minLength Minimum length of a password */ + /** @var int $minLength Minimum length of a word to be considered for the list */ private int $minLength; - /** @var int $maxLength Maximum length of a password */ + /** @var int $maxLength Maximum length of a word to be considered for the list */ private int $maxLength; /** @var array $wordList Cache of the words */ @@ -52,6 +52,12 @@ class PasswordGenerator /** @var bool $verbose */ private bool $verbose; + /** @var bool $appendWordlist If true the fetched words will be appended to the cached wordlist, rather than replacing the current file */ + private bool $appendWordlist = false; + + /** @var int $limitWordlist Limit the max number of words in the wordlist files, defaults to no limit */ + private int $limitWordlist = 0; + /** * Creates an instance of the password generator. You can pass an optional * array with values that should override the default values for the keys: @@ -65,6 +71,10 @@ class PasswordGenerator *
The maxinum length of characters a word must have.
*
wordCacheFile
*
Filename of the cache file.
+ *
appendWordlist
+ *
If true, the fetched wordlist will be appended to the current one. If false, wordlist is generated from scratch.
+ *
limitWordlist
+ *
Int value declaring the maximum words the wordlist must contain. Useful when using appendWordList.
*
httpRedirects
*
Number of maximum HTTP redirects the script will follow.
* @@ -143,9 +153,21 @@ private function populate_wordlist(): void } } } - $this->wordList = array_keys($wordlist); + if ((bool)$this->appendWordlist) { + $this->wordList = array_unique($this->wordList + array_keys($wordlist)); + } else { + $this->wordList = array_keys($wordlist); + } + $wordlistLimit = (int)$this->limitWordlist; + if ($wordlistLimit > 0) { + // Shuffle the array to mix existing and new words, if in append mode + if ((bool)$this->appendWordlist) { + shuffle($this->wordList); + } + $this->wordList = array_slice($this->wordList, 0, $wordlistLimit); + } - if (count($wordlist) > 0) { + if (count($this->wordList) > 0) { $this->save_wordlist(); } } catch (Exception $e) { From ab9776e3522d6e5851eeb524404bef616f026e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sat, 11 May 2024 08:55:28 +0200 Subject: [PATCH 15/18] apply limitWordlist generally --- src/PasswordGenerator.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 68714a0..26a7092 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -56,7 +56,7 @@ class PasswordGenerator private bool $appendWordlist = false; /** @var int $limitWordlist Limit the max number of words in the wordlist files, defaults to no limit */ - private int $limitWordlist = 0; + private int $limitWordlist = PHP_INT_MAX; /** * Creates an instance of the password generator. You can pass an optional @@ -107,6 +107,9 @@ public function __construct(array $params = [], bool $fetch = true, bool $verbos if ($this->minLength > $this->maxLength) { throw new InvalidArgumentException(sprintf('Invalid word lengths: min=%s max=%s', $this->minLength, $this->maxLength)); } + if ($this->limitWordlist < 1) { + throw new InvalidArgumentException(sprintf('Invalid limit for wordlist, must be at least 1: %s', $this->limitWordlist)); + } $this->populate_wordlist(); } @@ -153,18 +156,15 @@ private function populate_wordlist(): void } } } - if ((bool)$this->appendWordlist) { + if ($this->appendWordlist) { $this->wordList = array_unique($this->wordList + array_keys($wordlist)); } else { $this->wordList = array_keys($wordlist); } - $wordlistLimit = (int)$this->limitWordlist; - if ($wordlistLimit > 0) { - // Shuffle the array to mix existing and new words, if in append mode - if ((bool)$this->appendWordlist) { - shuffle($this->wordList); - } - $this->wordList = array_slice($this->wordList, 0, $wordlistLimit); + if (count($this->wordList) > $this->limitWordlist) { + // wordlist has more entries than wanted + shuffle($this->wordList); + $this->wordList = array_slice($this->wordList, 0, $this->limitWordlist); } if (count($this->wordList) > 0) { From e9fde7ff059965e6d66b05040ba34beffbac41a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sat, 11 May 2024 08:55:40 +0200 Subject: [PATCH 16/18] update documentation --- README.md | 17 +++++++++++------ example/PasswordGenerator.example.php | 2 +- src/PasswordGenerator.php | 6 ++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e9e6a12..5a83044 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,6 @@ Optionally you can specify *wordCacheFile* to control the location and name of t To a custom instance you can pass an optional boolean parameter *fetch*. When false, the cached wordlist will be preferred but falls back to fetching if it could not be loaded. -You can also mix different RSS sources into the same wordlist file with the `appendWordlist` parameter, increasing entropy, or regularly rebuild your cache file from time to time to feed it with new words. Altought having more words is always desirable, -you can limit the number of items in the wordlist with the `limitWordlist` configuration parameter. When using append, wordlist is shuffled before being limited to the desired quantity. - ```php include 'PasswordGenerator.php'; @@ -93,8 +90,16 @@ $gen = PasswordGenerator::EN(); echo 'Password 1: ', $gen->generate(); echo 'Password 2: ', $gen->generate(); echo 'Password 3: ', $gen->generate(); +``` + +When fetching an RSS source the normal behaviour is to create a new list, overwriting an existing cache file. You can opt-in to merge the new wordlist with an exisiting cache file instead, by setting the `appendWordlist` parameter to true. On the one hand this will result in bigger word lists, increasing entropy, on the other hand this may lead to very long lists. You can limit the number of items in the wordlist with the `limitWordlist` configuration parameter. When using this option and the wordlist is exceeding that limit, it is shuffled and then sliced to that number. + +```php +include 'PasswordGenerator.php'; + +use \Darkv\PhpPasswordGenerator\PasswordGenerator; -// Append NYTimes feed to the specified wordlist, limiting to 7000 items max +// append NYTimes feed to the specified wordlist, limiting to 7000 items max $gen = new PasswordGenerator([ 'wordCacheFile' => 'mywords.json', 'url' => 'https://rss.nytimes.com/services/xml/rss/nyt/World.xml', @@ -170,11 +175,11 @@ When creating an instance of PasswordGenerator you can provide the following par * **appendWordlist** - If true, the fetched wordlist will be appended to the current one. If false, wordlist is generated from scratch. + If true, the fetched wordlist will be appended to an existing cache file. If false, cache will be overwritten. * **limitWordlist** - Int value declaring the maximum words the wordlist must contain. Useful when using appendWordList. + Int value declaring the maximum number if words the wordlist may contain. Useful when using appendWordList. ## License diff --git a/example/PasswordGenerator.example.php b/example/PasswordGenerator.example.php index 65b3510..19ed9f8 100644 --- a/example/PasswordGenerator.example.php +++ b/example/PasswordGenerator.example.php @@ -52,7 +52,7 @@ // generate a password with custom pattern echo $gen->generate('wiwsw'), "\n"; -// Append NYTimes feed to the last wordlist, limiting to 700 items max +// append NYTimes feed to the existing cache file, limiting to 700 items max $gen = new PasswordGenerator([ 'wordCacheFile' => 'mywords.json', 'url' => 'https://rss.nytimes.com/services/xml/rss/nyt/World.xml', diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 26a7092..5e1ba91 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -72,9 +72,11 @@ class PasswordGenerator *
wordCacheFile
*
Filename of the cache file.
*
appendWordlist
- *
If true, the fetched wordlist will be appended to the current one. If false, wordlist is generated from scratch.
+ *
If true, the fetched wordlist will be appended to the an existing + * one. If false, wordlist is generated from scratch.
*
limitWordlist
- *
Int value declaring the maximum words the wordlist must contain. Useful when using appendWordList.
+ *
Int value declaring the maximum number of words the wordlist may + * contain. Useful when using appendWordList.
*
httpRedirects
*
Number of maximum HTTP redirects the script will follow.
* From a70c9ae9e6ae5793517ad8653d54d12555110511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sat, 11 May 2024 08:58:48 +0200 Subject: [PATCH 17/18] update version --- src/PasswordGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PasswordGenerator.php b/src/PasswordGenerator.php index 5e1ba91..d2ce605 100644 --- a/src/PasswordGenerator.php +++ b/src/PasswordGenerator.php @@ -25,7 +25,7 @@ * @example ./example/PasswordGenerator.example.php example Class in action. * * @author Johann Häger - * @version 2.0.3 + * @version 2.0.4 * @since 1.0.1 * @license MIT License */ From d5815a73ffa04226c1b5e9f53b27eafd8a1686ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Ha=CC=88ger?= Date: Sat, 11 May 2024 11:01:20 +0200 Subject: [PATCH 18/18] fix indentation --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5a83044..5d5638c 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,9 @@ $gen = PasswordGenerator::DE(); // create instance with custom parameters $gen = new PasswordGenerator([ - 'url' => 'https://www.tagesschau.de/newsticker.rdf', - 'minLength' => 3, - 'maxLength' => 6, + 'url' => 'https://www.tagesschau.de/newsticker.rdf', + 'minLength' => 3, + 'maxLength' => 6, ]); ``` @@ -142,10 +142,10 @@ use \Darkv\PhpPasswordGenerator\PasswordGenerator; // create instance with custom parameters $gen = new PasswordGenerator([ - 'url' => 'https://www.some-url.com/source', - 'minLength' => 3, - 'maxLength' => 6, - 'httpRedirects' => 0, + 'url' => 'https://www.some-url.com/source', + 'minLength' => 3, + 'maxLength' => 6, + 'httpRedirects' => 0, ]); ```