From 36e29f933aefb1f8c19c6cb9b994e4dc629870a2 Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 16:43:06 -0700 Subject: [PATCH 1/8] Add initial refactor to package - PHP 7.1-7.3 support - Update examples (add hashed example) - Fix coding standards (PSR-2, PSR-12) - Add & update READMEs (Some are WIP) - Update tests - Add .gitignore - Add travis build - Add composer.json --- .editorconfig | 15 + .github/CODE_OF_CONDUCT.md | 46 + .github/CONTRIBUTING.md | 12 + .github/ISSUE_TEMPLATE/BUG_REPORT.md | 37 + .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md | 24 + .github/PULL_REQUEST_TEMPLATE.md | 18 + .gitignore | 5 + .travis.yml | 37 + README.md | 70 + ReadMe.md => _examples/README.md | 22 +- _examples/append.php | 105 +- _examples/basic.php | 105 +- _examples/complete.php | 101 -- _examples/hashed.php | 74 + _examples/increment.php | 94 +- _examples/regenerate.php | 105 +- _tests/README.md | 1 + _tests/{SessionTest.php => TestSession.php} | 60 +- _tests/phpunit.xml | 43 +- cl_session.php | 433 ----- composer.json | 31 + composer.lock | 1665 +++++++++++++++++++ src/Session.php | 728 ++++++++ 23 files changed, 3022 insertions(+), 809 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/BUG_REPORT.md create mode 100644 .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md rename ReadMe.md => _examples/README.md (51%) delete mode 100644 _examples/complete.php create mode 100644 _examples/hashed.php create mode 100644 _tests/README.md rename _tests/{SessionTest.php => TestSession.php} (86%) delete mode 100644 cl_session.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Session.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8e72ded --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.yml] +indent_size = 2 diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a5f5a33 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@asdf.dev. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..76e84b5 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing to session + +**Find a bug?** + +- If the bug is security related please do not submit a GitHub issue. Email hello@asdf.dev with details and we will be in touch. + +- All other bug should be [submitted via GitHub](https://github.com/asdfdotdev/session/issues) issues. Please follow the provided template and include as much detail as possible. + - Before submtiting a new issue please make sure it hasn't previously been reported by searching existing issues. + +**Fix a bug, add something cool, or make a change?** + +- [Submit a pull request](https://github.com/asdfdotdev/session/pulls) so we can add it! diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..cde32fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve session +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. + +**To Reproduce** + +Steps to reproduce the behavior: + + +**Expected behavior** + +A clear and concise description of what you expected to happen. + +**Screenshots** + +If applicable, add screenshots to help explain your problem. + + +**System Information** + +- session class version: +- Operating System: +- Python Version: + + +**Additional context** + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000..f0291e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** + +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** + +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** + +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5a3f2c9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +# Description + +Please include a summary of the change and which GitHub issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please describe the type of change. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0034892 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/.DS_Store +.idea +**/_tests/build/**/* +vendor/ +_dev/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..415dd1f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,37 @@ +language: php + +notifications: + email: + on_success: never + on_failure: change + +branches: + only: + - master + - development + +cache: + directories: + - $HOME/.composer/cache + +matrix: + include: + - php: 7.1 + - php: 7.2 + - php: 7.3 + +before_script: + - composer install + - ./vendor/bin/phpcs --version + - ./vendor/bin/phpcs -i + - ./vendor/bin/phpcs src/* --report=summary --standard=PSR2 + - ./vendor/bin/phpcs src/* --report=summary --standard=PSR12 + - ./vendor/bin/phpcs --standard=PHPCompatibility -p --runtime-set testVersion 7.1- src/* + +script: + - cd _tests + - mkdir -p build/logs + - ../vendor/bin/phpunit + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md new file mode 100644 index 0000000..7464a77 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# Session + +[![Build Status](https://travis-ci.org/asdfdotdev/session.svg?branch=master)](https://travis-ci.org/asdfdotdev/session) [![codecov](https://codecov.io/gh/asdfdotdev/session/branch/master/graph/badge.svg)](https://codecov.io/gh/asdfdotdev/session) [![Packagist](https://img.shields.io/packagist/dm/asdfdotdev/session)](https://packagist.org/packages/asdfdotdev/session) + +This class endeavors to make it easy to use basic session best practices in PHP scripts. + +* Easily set, increment, append, hash, and drop session values +* Custom session naming +* Session fingerprint validation +* Regenerate session id at random intervals +* Change session id length & bits per character* +* HTTPOnly session cookie +* Decoy PHPSESSID cookie +* Easy to create, manage, and destroy session values +* Force session strict mode* +* Force session use only cookies* +* Force HTTPS only session cookies* +* Supports in PHP 7.1+ + +\* Requires access to `ini_set()` method. + +### Compatibility + +[![PHP Compatibility](https://img.shields.io/badge/PHP-7.1_to_7.3-%238892BF.svg?logo=php)](https://php.net/) + +Session class is developed for and tested with recent PHP Version: + +- PHP 7.1, 7.2, and 7.3 + + +## Installation + +``` +composer require asdfdotdev/session +``` + +If you don't use composer just clone/download the [Session](./src/Session.php) class file and include it in your project. + +## Use + +A number of usage examples are included in `_examples/`. Check out the examples [README](./_examples/README.md) for further details. + +## Tests + +Information regarding the included tests is available in the tests the [README](./_test#readme). + +[Build history can be browsed at Travis-CI.](https://travis-ci.org/asdfdotdev/session) + +## Standards + +This class follows both the [PSR-2](https://www.php-fig.org/psr/psr-2/) and [PSR-12](https://www.php-fig.org/psr/psr-12/) code standards. + +## Debugging + +By default basic checks are performed when creating a session: + +- **Session Lifespan:** Prevents min lifespan from being greater than max lifespan. +- **System Timezone:** Confirms default timezone is configured for PHP, if not UTC is set. + +Additional optional debugging can be enabled in session settings: + +- **PHP Version:** Confirms the version available is 7.1.0 or newer +- **Session Directory:** Confirms write access to PHP session directory +- **Session Domain:** Confirms session domain setting matches the request domain + +## Contributing + +Feedback, bug reports, feature requests, and pull requests are welcome! + +If you'd like to contribute please reference our [code of conduct](./.github/CODE_OF_CONDUCT.md) and [contributing](./.github/CONTRIBUTING.md) guides. diff --git a/ReadMe.md b/_examples/README.md similarity index 51% rename from ReadMe.md rename to _examples/README.md index 84cd6c1..d5b434b 100644 --- a/ReadMe.md +++ b/_examples/README.md @@ -1,18 +1,4 @@ -# ChristopherL Session Class - -The ChristopherL Session class endeavors to make it easy to use basic session best practices in PHP scripts. - -* Regenerate session id at random intervals. -* Default to SHA1 for session hash -* Custom session naming -* Session fingerprint validation -* HTTPOnly session cookie -* Decoy PHPSESSID cookie -* Easy to create, manage, and destroy session values. -* Works in PHP 5.5-7.0 - ----- -## Examples +todo: fix it Creating a session: ``` @@ -45,9 +31,3 @@ Force Session ID Regeneration ``` $session->regenerate(); ``` - -Additional examples included in /examples - ----- -## License -cl_session is made available under the [LGPL](http://www.gnu.org/licenses/lgpl-2.1.html). \ No newline at end of file diff --git a/_examples/append.php b/_examples/append.php index 1aebd34..f44863d 100644 --- a/_examples/append.php +++ b/_examples/append.php @@ -1,59 +1,68 @@ 'AppendSession', - 'decoy' => false, - ); - $session = new ChristopherL\Session($session_values); - $session->start(); - - - // Create/Update Session Variable (Refresh the page to add additional) - $session->appValue('my_var', 'littering and '); - - - // Retrieve and output Session Variable - $my_session_variable = $session->getValue('my_var'); - - echo <<Session Variables -

- my_var: {$my_session_variable} -

-HTML; +include('../src/Session.php'); + +$session = new Asdfdotdev\Session([ + 'name' => 'AppendSession', + 'decoy' => false, + 'min' => 5, + 'max' => 10, + 'debug' => true, +]); +$session->start(); + +/** + * About this example. + */ - // Output Session Settings - $session_id = session_id(); - $epoch_time = date("U"); - $session_lifespan = $session->getValue('lifespan'); - - echo << -

Session Settings

-

- Session ID: {$session_id} -

-

- Current Time: {$epoch_time} -

-

- Regenerate At: {$session_lifespan} -

+echo <<About this Example +

+ Each page refresh will append to both the array and string session values. + Delete the session cookie, or close your browser, to generate a new session and reset the values. +

HTML; - // Output Session Contents - $session_content = $session->dump(); +/** + * Increment the session value. + */ + +$session->appValue('my_var_string', 'text and more '); +$session->appValue('my_var_array', [rand()]); + +$my_var_string = $session->getValue('my_var_string'); +$my_var_array = print_r( + $session->getValue('my_var_array'), + true +); - echo << -

Debug

-
{$session_content}
+ +/** + * Output results + */ +echo << +

Session Variable

+

+ String: {$my_var_string} +

+

+ Array: {$my_var_array} +

+HTML; + + +/** + * Debugging + */ +$session_content = $session->dump(); +echo << +

Debug

+
{$session_content}
HTML; diff --git a/_examples/basic.php b/_examples/basic.php index 1e44568..dda8a76 100644 --- a/_examples/basic.php +++ b/_examples/basic.php @@ -3,57 +3,70 @@ * A simple session with a single value. */ - // Create Session - include('../cl_session.php'); +include('../src/Session.php'); - $session_values = array( - 'name' => 'BasicSession', - 'decoy' => false, - ); - $session = new ChristopherL\Session($session_values); - $session->start(); - - - // Populate Session Variable - $session->setValue('my_var','some value'); - - - // Retrieve and output Session Variable - $my_session_variable = $session->getValue('my_var'); - - echo <<Session Variables -

- my_var: {$my_session_variable} -

-HTML; +$session = new Asdfdotdev\Session([ + 'name' => 'BasicSession', + 'decoy' => false, + 'min' => 5, + 'max' => 10, + 'debug' => true, +]); +$session->start(); - // Output Session Settings - $session_id = session_id(); - $epoch_time = date("U"); - $session_lifespan = $session->getValue('lifespan'); - - echo << -

Session Settings

-

- Session ID: {$session_id} -

-

- Current Time: {$epoch_time} -

-

- Regenerate At: {$session_lifespan} -

+/** + * About this example. + */ + +echo <<About this Example +

+ On initial creation a random number will be appended to the my_var + string. In subsequent page refreshes this number will not change as it will be + retrieved from the saved session value. Delete the session cookie, or close your + browser, to generate a new session and value. +

HTML; - // Output Session Contents - $session_content = $session->dump(); +/** + * Create, or retrieve, the session variable. + */ +$my_var = $session->getValue('my_var'); +if (!isset($my_var)) { + $session->setValue( + 'my_var', + 'A string that ends in a random number. ' . rand() + ); + $my_var = $session->getValue('my_var'); + $result = 'Created session variable.'; +} else { + $result = 'Retrieved existing session variable.'; +} + + +/** + * Output results + */ +echo << +

Session Variable

+

+ Status: {$result} +

+

+ Value: {$my_var} +

+HTML; + - echo << -

Debug

-
{$session_content}
+/** + * Debugging + */ +$session_content = $session->dump(); +echo << +

Debug

+
{$session_content}
HTML; diff --git a/_examples/complete.php b/_examples/complete.php deleted file mode 100644 index e69c25f..0000000 --- a/_examples/complete.php +++ /dev/null @@ -1,101 +0,0 @@ - 'CompleteSession', - 'path' => '/', - 'domain' => 'localhost', - 'secure' => false, - 'hash' => 'sha512', - 'decoy' => true, - 'min' => 5, - 'max' => 10 - ); - $session = new ChristopherL\Session($session_values); - $session->start(); - - // Populate Session Variables - $session->setValue('boolean', true); - $session->appValue('extending_string','littering and '); - $session->incValue('count', 5); - $session->setValue('random', (string)rand(0, 5000)); - $session->setValue('fixed','some value'); - $session->setValue('array', array('one thing', 'two thing', 'has_key' => 'red thing', 'blue_thing' => array('text', false, 50))); - $session->setValue('hashed', 'this is my string', true); - - // Session Variable Set Outside of Class - $_SESSION['Outside'] = 'a session value set the old fashioned way'; - - // Retrieve and output Session Variables - $boolean = ($session->getValue('boolean') ? 'is true' : 'is false'); - $extending = $session->getValue('extending_string'); - $incrementing = $session->getValue('count'); - $random = $session->getValue('random'); - $fixed = $session->getValue('fixed'); - $array = print_r($session->getValue('array'), true); - $hashed = $session->getValue('hashed'); - - - // Output Session Variables - echo <<Session Variables -

- boolean: {$boolean} -

-

- extending: {$extending} -

-

- incrementing: {$incrementing} -

-

- random: {$random} -

-

- fixed: {$fixed} -

-

- array: {$array} -

-

- hashed: {$hashed} -

-

- Outside: {$_SESSION['Outside']} -

-HTML; - - - // Output Session Settings - $session_id = session_id(); - $epoch_time = date("U"); - $session_lifespan = $session->getValue('lifespan'); - - echo << -

Session Settings

-

- Session ID: {$session_id} -

-

- Current Time: {$epoch_time} -

-

- Regenerate At: {$session_lifespan} -

-HTML; - - // Output Session Contents - $session_content = $session->dump(); - - - echo << -

Debug

-
{$session_content}
-HTML; diff --git a/_examples/hashed.php b/_examples/hashed.php new file mode 100644 index 0000000..9ffa86d --- /dev/null +++ b/_examples/hashed.php @@ -0,0 +1,74 @@ + 'HashedSession', + 'decoy' => false, + 'min' => 5, + 'max' => 10, + 'debug' => true, +]); +$session->start(); + + +/** + * About this example. + */ + +echo <<About this Example +

+ On initial session creation a sha256 hash of a random number stored in + my_var. In subsequent page refreshes this hashed value will + not change as it will be retrieved from the saved session value. Delete + the session cookie, or close your browser, to generate a new session and + value. +

+HTML; + + +/** + * Create, or retrieve, the session variable. + */ +$my_var = $session->getValue('my_var'); +if (!isset($my_var)) { + $session->setValue( + 'my_var', + rand(), + true + ); + $my_var = $session->getValue('my_var'); + $result = 'Created session variable.'; +} else { + $result = 'Retrieved existing session variable.'; +} + + +/** + * Output results + */ +echo << +

Session Variable

+

+ Status: {$result} +

+

+ Value: {$my_var} +

+HTML; + + +/** + * Debugging + */ +$session_content = $session->dump(); +echo << +

Debug

+
{$session_content}
+HTML; diff --git a/_examples/increment.php b/_examples/increment.php index cfae7e8..fceb1d5 100644 --- a/_examples/increment.php +++ b/_examples/increment.php @@ -1,59 +1,67 @@ 'IncrementSession', - 'decoy' => false, - ); - $session = new ChristopherL\Session($session_values); - $session->start(); +$session = new Asdfdotdev\Session([ + 'name' => 'IncrementSession', + 'decoy' => false, + 'min' => 5, + 'max' => 10, + 'debug' => true, +]); +$session->start(); +/** + * About this example. + */ - // Create/Update Session Variable (Refresh the page to increment) - $session->incValue('my_var', 5.3); +echo <<About this Example +

+ Each page refresh will increment the session value by a random amount of between 0 and 10. + Delete the session cookie, or close your browser, to generate a new session and reset the value to 0. +

+HTML; - // Retrieve and output Session Variable - $my_session_variable = $session->getValue('my_var'); +/** + * Increment the session value. + */ - echo <<Session Variables -

- my_var: {$my_session_variable} -

-HTML; +$increment_by = rand(0, 10); +$session->incValue( + 'my_var', + $increment_by +); - // Output Session Settings - $session_id = session_id(); - $epoch_time = date("U"); - $session_lifespan = $session->getValue('lifespan'); - - echo << -

Session Settings

-

- Session ID: {$session_id} -

-

- Current Time: {$epoch_time} -

-

- Regenerate At: {$session_lifespan} -

-HTML; +$my_var = $session->getValue('my_var'); - // Output Session Contents - $session_content = $session->dump(); +/** + * Output results + */ +echo << +

Session Variable

+

+ Value: {$my_var} +

+

+ Incremented by: {$increment_by} +

+HTML; - echo << -

Debug

-
{$session_content}
+ +/** + * Debugging + */ +$session_content = $session->dump(); +echo << +

Debug

+
{$session_content}
HTML; diff --git a/_examples/regenerate.php b/_examples/regenerate.php index a1441a2..275d624 100644 --- a/_examples/regenerate.php +++ b/_examples/regenerate.php @@ -1,63 +1,72 @@ 'RegenerateSession', - 'decoy' => false, - ); - $session = new ChristopherL\Session($session_values); - $session->start(); - - - // Regenerate Session - $session->regenerate(); +include('../src/Session.php'); +$session = new Asdfdotdev\Session([ + 'name' => 'RegenerateSession', + 'decoy' => false, + 'min' => 5, + 'max' => 10, + 'debug' => true, +]); +$session->start(); - // Create/Update Session Variable (Demonstrates persisting, incrementing, value when regenerating session) - $session->incValue('my_var', 1); +$session->regenerate(); +/** + * About this example. + */ - // Retrieve and output Session Variable - $my_session_variable = $session->getValue('my_var'); - - echo <<Session Variables -

- my_var: {$my_session_variable} -

+echo <<About this Example +

+ On initial creation a random number will be appended to the my_var + string. This example forces the session id to be regenerated on each request. The + regenerated session id can be viewed in the session cookie. Delete the session + cookie, or close your browser, to generate a new session and value. +

HTML; +/** + * Create, or retrieve, the session variable. + */ +$my_var = $session->getValue('my_var'); +if (!isset($my_var)) { + $session->setValue( + 'my_var', + 'A string that ends in a random number. ' . rand() + ); + $my_var = $session->getValue('my_var'); + $result = 'Created session variable.'; +} else { + $result = 'Retrieved existing session variable.'; +} + - // Output Session Settings - $session_id = session_id(); - $epoch_time = date("U"); - $session_lifespan = $session->getValue('lifespan'); - - echo << -

Session Settings

-

- Session ID: {$session_id} -

-

- Current Time: {$epoch_time} -

-

- Regenerate At: {$session_lifespan} -

+/** + * Output results + */ +echo << +

Session Variable

+

+ Status: {$result} +

+

+ Value: {$my_var} +

HTML; - // Output Session Contents - $session_content = $session->dump(); - - echo << -

Debug

-
{$session_content}
+/** + * Debugging + */ +$session_content = $session->dump(); +echo << +

Debug

+
{$session_content}
HTML; diff --git a/_tests/README.md b/_tests/README.md new file mode 100644 index 0000000..8f33a8b --- /dev/null +++ b/_tests/README.md @@ -0,0 +1 @@ +todo: write it diff --git a/_tests/SessionTest.php b/_tests/TestSession.php similarity index 86% rename from _tests/SessionTest.php rename to _tests/TestSession.php index 7b7d95d..9cfc8a7 100644 --- a/_tests/SessionTest.php +++ b/_tests/TestSession.php @@ -15,9 +15,9 @@ * testExceptionThrow Validates exception handling by class */ -namespace ChristopherL; +namespace Asdfdotdev; -class SessionTest extends \PHPUnit\Framework\TestCase +class TestSession extends \PHPUnit\Framework\TestCase { /** * @runInSeparateProcess @@ -27,7 +27,7 @@ public function testSessionNoSettings() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -40,7 +40,7 @@ public function testSessionNoSettings() $this->assertLessThanOrEqual($session_max, $session_lifespan); // Verify default session name is in use - $this->assertEquals(session_name(), 'clsession'); + $this->assertEquals(session_name(), 'asdfdotdev'); // Verify default session cookie settings $array_details = session_get_cookie_params(); @@ -51,7 +51,7 @@ public function testSessionNoSettings() // Verify decoy value is in session array (decoy cookie in use) $session_dump = $session->dump(2); - $this->assertTrue(array_key_exists('decoy_value', $session_dump['clValues'])); + $this->assertTrue(array_key_exists('decoy_value', $session_dump['asdfValues'])); } /** @@ -64,15 +64,14 @@ public function testSessionCustomSettings() $session_settings = array( 'name' => 'TestSession', 'path' => '/subdirectory', - 'domain' => '.christopherl.com', + 'domain' => '.testing.edu', 'secure' => true, - 'hash' => 1, 'decoy' => false, 'min' => 150, 'max' => 200, ); - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session($session_settings); $session->start(); @@ -90,13 +89,13 @@ public function testSessionCustomSettings() // Verify default session cookie settings $array_details = session_get_cookie_params(); $this->assertEquals($array_details['path'], '/subdirectory'); - $this->assertEquals($array_details['domain'], '.christopherl.com'); + $this->assertEquals($array_details['domain'], '.testing.edu'); $this->assertEquals($array_details['secure'], true); $this->assertEquals($array_details['httponly'], true); // Verify decoy value isn't in session array (no decoy cookie in use) $session_dump = $session->dump(2); - $this->assertFalse(array_key_exists('decoy_value', $session_dump['clValues'])); + $this->assertFalse(array_key_exists('decoy_value', $session_dump['asdfValues'])); } /** @@ -107,7 +106,7 @@ public function testSessionValue() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -132,7 +131,7 @@ public function testSessionAppendValue() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -157,7 +156,7 @@ public function testSessionIncrementValue() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -182,7 +181,7 @@ public function testSessionDeleteValue() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -197,26 +196,7 @@ public function testSessionDeleteValue() // Verify decoy value isn't in session array (no decoy cookie in use) $session_dump = $session->dump(2); - $this->assertFalse(array_key_exists('my_variable', $session_dump['clValues'])); - } - - /** - * @runInSeparateProcess - */ - public function testSessionHashValue() - { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; - $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - - require 'cl_session.php'; - $session = new Session(); - $session->start(); - - // Create a new session string value - $session->setValue('my_hashed_variable', 'plain text value', true); - - // Verifiy creation of hashed session value - $this->assertEquals($session->getValue('my_hashed_variable'), hash($session->getHash(), 'plain text value')); + $this->assertFalse(array_key_exists('my_variable', $session_dump['asdfValues'])); } /** @@ -227,7 +207,7 @@ public function testSessionRegenerate() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); @@ -249,16 +229,16 @@ public function testSessionFingerprint() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); // Verify fingerprint matches expected recipe - $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'].$_SERVER['REMOTE_ADDR'].session_id())); + $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); // Verify regenerating session id maintains valid fingerprint $session->regenerate(); - $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'].$_SERVER['REMOTE_ADDR'].session_id())); + $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); // Randomly change either the user agent or ip address if (rand(0, 1) == 1) { @@ -268,7 +248,7 @@ public function testSessionFingerprint() } // Verify that whatever we changed causes the fingerprint comparison to fail - $this->assertNOTEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'].$_SERVER['REMOTE_ADDR'].session_id())); + $this->assertNOTEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); } /** @@ -279,7 +259,7 @@ public function testExceptionThrow() $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - require 'cl_session.php'; + require '../src/Session.php'; $session = new Session(); $session->start(); diff --git a/_tests/phpunit.xml b/_tests/phpunit.xml index f8645b1..f7926b3 100644 --- a/_tests/phpunit.xml +++ b/_tests/phpunit.xml @@ -1,36 +1,21 @@ - - + backupGlobals="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" +> - - - ./ + + ./ - - - - - - - - + + ../src/ - \ No newline at end of file + + + + diff --git a/cl_session.php b/cl_session.php deleted file mode 100644 index 38a3b7b..0000000 --- a/cl_session.php +++ /dev/null @@ -1,433 +0,0 @@ - isset($config['name']) ? $config['name'] : 'clsession', - 'path' => isset($config['path']) ? $config['path'] : '/', - 'domain' => isset($config['domain']) ? $config['domain'] : 'localhost', - 'secure' => isset($config['secure']) ? $config['secure'] : false, - 'hash' => isset($config['hash']) ? $config['hash'] : 1, - 'decoy' => isset($config['decoy']) ? $config['decoy'] : true, - 'min' => isset($config['min']) ? $config['min'] : 60, - 'max' => isset($config['max']) ? $config['max'] : 600, - ); - - // If min is greater than max swap values so we can construct valid lifespan - if ($settings['min'] > $settings['max']) { - $settings['min'] = $settings['min'] + $settings['max']; - $settings['max'] = $settings['min'] - $settings['max']; - $settings['min'] -= $settings['max']; - } - - // Apply session settings - $this->setName($settings['name']); - $this->setPath($settings['path']); - $this->setDomain($settings['domain']); - $this->setSecure($settings['secure']); - $this->setHash($settings['hash']); - $this->min_time = $settings['min']; - $this->decoy = $settings['decoy']; - $this->max_time = $settings['max']; - - // Sometimes a timezone isn't set. This will avoid an error in those instances. - if (function_exists('ini_get') && ini_get('date.timezone') == '') { - date_default_timezone_set('UTC'); - } - } - - /** - * Set session name, this is also used as the name of the session cookie. - * - * @param string $name Session Name - */ - protected function setName($name) - { - $this->name = $name; - } - - /** - * Get session name. - * - * @return string Session Name - */ - protected function getName() - { - return $this->name; - } - - /** - * Set path on the domain where the cookies will work - * Use a single slash (default) for all paths on the domain. - * - * @param string $path Cookie Path - */ - protected function setPath($path) - { - $this->path = $path; - } - - /** - * Get cookie path. - * - * @return string Cookie Path - */ - protected function getPath() - { - return $this->path; - } - - /** - * Set cookie domain. To make cookie visible on all subdomains prefixed with a dot - * Ex) .christopherl.com. - * - * @param string $domain Cookie Domain - */ - protected function setDomain($domain = '') - { - $domain = ($domain == '') ? $_SERVER['SERVER_NAME'] : $domain; - $this->domain = $domain; - } - - /** - * Get session cookie domain. - * - * @return string Cookie Domain - */ - protected function getDomain() - { - return $this->domain; - } - - /** - * Set cookie secure status. If TRUE cookie will only be sent over secure connections. - * - * @param bool $secure Cookie Secure Status - */ - protected function setSecure($secure = false) - { - $this->secure = $secure; - } - - /** - * Get cookie secure status. - * - * @return bool Cookie Secure Status - */ - protected function getSecure() - { - return $this->secure; - } - - /** - * Set cookie id hash method. - * - * @param int/string $hash 0 = MD5, 1 = SHA1, or supported hash name (Default: 1) - */ - protected function setHash($hash = 1) - { - if ($hash === 0) { - $hash = 'md5'; - } - else if ($hash === 1) { - $hash = 'sha256'; - } - else if (in_array($hash, hash_algos())) { - $hash = $hash; - } - else { - $this->Error('Invalid hash algorithm selected.'); - } - - $this->hash = $hash; - } - - /** - * Get session hash setting. - * - * @return int Cookie Hash Setting - */ - public function getHash() - { - return $this->hash; - } - - /** - * Create decoy cookie if it hasn't been set. - * - * This cookie intentionally exhibits signs of a week session cookie so that it looks attractive - * to would be scoundrels. These vulnerabilities include: PHPSESSID name, MD5 hash value, and not HTTPOnly. - */ - protected function generateDecoyCookie() - { - if (!isset($_COOKIE['PHPSESSID'])) { - $this->setValue('decoy_value', md5(mt_rand())); - setcookie('PHPSESSID', $this->getValue('decoy_value'), 0, $this->getPath(), $this->getDomain(), $this->getSecure(), 0); - } - } - - /** - * Destroy PHPSESSID decoy cookie. - */ - protected function killDecoyCookie() - { - if (isset($_COOKIE['PHPSESSID'])) { - unset($_COOKIE['PHPSESSID']); - } - } - - /** - * Create session fingerprint from user agent, ip and session id in an attempt to discourage session hijacking. - */ - protected function generateFingerprint() - { - $this->setValue('fingerprint', sha1($_SERVER['HTTP_USER_AGENT'].$_SERVER['REMOTE_ADDR'].session_id())); - } - - /** - * Compare current user agent, ip and session id against stored session fingerprint - * If compared value doesn't match stored value session end the session. - */ - protected function validateFingerprint() - { - if ($this->getValue('fingerprint') == '') { - $this->generateFingerprint(); - } elseif ($this->getValue('fingerprint') != sha1($_SERVER['HTTP_USER_AGENT'].$_SERVER['REMOTE_ADDR'].session_id())) { - $this->end(); - } - } - - /** - * Reset session lifespan time using random value between min_time and max_time. - */ - protected function resetLifespan() - { - $this->setValue('lifespan', date('U') + mt_rand($this->min_time, $this->max_time)); - } - - /** - * Compare session lifespan time to current time - * If current time is beyond session lifespan regenerate session id. - */ - protected function checkLifespan() - { - if ($this->getValue('lifespan') == '') { - $this->resetLifespan(); - } elseif ($this->getValue('lifespan') < date('U')) { - $this->regenerate(); - } - } - - /** - * Start Session. - * - * @param bool $restart Force session id regeneration - */ - public function start($restart = false) - { - - // when restarting regenerate session id - if ($restart) { - session_regenerate_id(true); - $new_id = session_id(); - session_write_close(); - session_id($new_id); - } - - if (function_exists('ini_set') && !$restart) { - ini_set('session.hash_function', $this->getHash()); - ini_set('session.use_strict_mode', 1); - ini_set('session.cookie_secure', 1); - ini_set('session.use_only_cookies', 1); - } - - session_set_cookie_params(0, $this->getPath(), $this->getDomain(), $this->getSecure(), true); - session_name($this->getName()); - session_start(); - $_SESSION['clValues'] = (isset($_SESSION['clValues'])) ? $_SESSION['clValues'] : array(); - - // on restart or initial creation (empty clValues) generate fingerprint & lifespan - if ($restart || count($_SESSION['clValues']) == 0) { - $this->generateFingerprint(); - $this->resetLifespan(); - } - - if ($this->decoy) { - $this->generateDecoyCookie(); - } else { - $this->dropValue('decoy_value'); - } - - $this->validateFingerprint(); - $this->checkLifespan(); - $this->setValue('session_load', date('U')); - } - - /** - * Get session variable value. - * - * @param string $key Name of the session variable value to retrieve - * - * @return mixed Value of the variable requested - */ - public function getValue($key) - { - if (!isset($_SESSION['clValues'][$key])) { - $this->Error('Invalid Session Value Name'); - } - - return $_SESSION['clValues'][$key]; - } - - /** - * Create session value if not present, otherwise the value is updated. - * - * @param string $key Name of the session variable to create/update - * @param string $value Value of the session variable to create/update - * @param int $hash 0 = store $value in session array as plain text, - * 1 = store SHA1 hash of $value in session array - */ - public function setValue($key, $value, $hash = false) - { - // if requested, hash the value before saving it - if ($hash) { - $value = hash($this->getHash(), $value); - } - - $_SESSION['clValues'][$key] = $value; - } - - /** - * Append session value. - * - * @param string $key Name of the session variable to create/update - * @param string $value String to append to the end of the current value - */ - public function appValue($key, $value) - { - if (isset($_SESSION['clValues'][$key])) { - $_SESSION['clValues'][$key] = ($this->getValue($key).$value); - } else { - $this->setValue($key, $value); - } - } - - /** - * Increment session value. - * - * @param string $key Name of the session variable to create/increment - * @param int $amount Amount to add to the current value - */ - public function incValue($key, $amount) - { - if (isset($_SESSION['clValues'][$key])) { - $_SESSION['clValues'][$key] += $amount; - } else { - $this->setValue($key, $amount); - } - } - - /** - * Drop session value. - * - * @param string $key Name of the session variable to drop - */ - public function dropValue($key) - { - unset($_SESSION['clValues'][$key]); - } - - /** - * Regenerate session id. - */ - public function regenerate() - { - $this->start(true); - } - - /** - * End session. - */ - public function end() - { - session_unset(); - session_destroy(); - } - - /** - * Dump session contents for debugging or testing. - * - * @param int $format 0 = string - * 1 = array - * 2 = json encoded string - * - * @return mixed - */ - public function dump($format = 1) - { - switch ($format) { - // string - case 1: - return print_r($_SESSION, true); - break; - // array - case 2: - return $_SESSION; - break; - // json string - case 3: - default: - return json_encode($_SESSION); - break; - } - } - - /** - * Throw exception on error. - * - * @param string $response Explain to them what they screwed up - * - * @throws \Exception - */ - protected function Error($response) - { - throw new \Exception($response, null, null); - } -} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9ab7462 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "asdfdotdev/session", + "version": "1.0.0", + "description": "endeavors to make it easy to use basic session best practices in PHP scripts.", + "type": "library", + "license": "LGPL-2.1-only", + "authors": [ + { + "name": "Chris Carlevato", + "email": "hello@asdf.dev" + } + ], + "support": { + "issues": "https://github.com/asdfdotdev/session/issues", + "source": "https://github.com/asdfdotdev/session" + }, + "autoload": { + "psr-4": { + "Asdfdotdev\\": "src/" + } + }, + "require": {}, + "require-dev": { + "squizlabs/php_codesniffer": "^3.4", + "phpcompatibility/php-compatibility": "^9.1", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5", + "phpunit/phpunit": "^7.5" + }, + "keywords": ["sessions"], + "minimum-stability": "stable" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..4611be1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1665 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "25e3a8a9b90e6d7d6f3e5b247a976179", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "e749410375ff6fb7a040a68878c656c2e610b132" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e749410375ff6fb7a040a68878c656c2e610b132", + "reference": "e749410375ff6fb7a040a68878c656c2e610b132", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "^2|^3" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "role": "Developer / IT Manager", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2018-10-26T13:21:45+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "role": "Developer", + "email": "arne@blankerts.de" + }, + { + "name": "Sebastian Heuer", + "role": "Developer", + "email": "sebastian@phpeople.de" + }, + { + "name": "Sebastian Bergmann", + "role": "Developer", + "email": "sebastian@phpunit.de" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "role": "Developer", + "email": "arne@blankerts.de" + }, + { + "name": "Sebastian Heuer", + "role": "Developer", + "email": "sebastian@phpeople.de" + }, + { + "name": "Sebastian Bergmann", + "role": "Developer", + "email": "sebastian@phpunit.de" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/3db1bf1e28123fd574a4ae2e9a84072826d51b5e", + "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + }, + { + "name": "Wim Godden", + "role": "lead", + "homepage": "https://github.com/wimg" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead", + "homepage": "https://github.com/jrfnl" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2019-06-27T19:58:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-04-30T17:48:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1 || ^4.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-10-31T16:06:48+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-07-25T05:29:42+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2834789aeb9ac182ad69bfdf9ae91856a59945ff", + "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-07-15T06:24:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-05-05T09:05:15+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "06a9a5947f47b3029d76118eb5c22802e5869687" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687", + "reference": "06a9a5947f47b3029d76118eb5c22802e5869687", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-08-11T12:43:14+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2019-04-10T23:49:02+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "role": "Developer", + "email": "arne@blankerts.de" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-12-25T11:19:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000..995577a --- /dev/null +++ b/src/Session.php @@ -0,0 +1,728 @@ + + * @copyright 2015-2019 Chris Carlevato + * @license http://www.gnu.org/licenses/lgpl-2.1.html + * @version 1.0.0 + * @link https://github.com/asdfdotdev/session + */ + +namespace Asdfdotdev; + +/** + * Class Session + * + * @package Asdfdotdev + */ +class Session +{ + /** @var string Key for session value array */ + protected $valuesKey = 'asdfdotdev.session'; + + /** @var string Name of the session */ + protected $name; + + /** @var string Path the session cookie is available on */ + protected $path; + + /** @var string Domain the cookie is available on */ + protected $domain; + + /** @var boolean Only transmit the session cookie over https */ + protected $secure; + + /** @var string Name of hashing algorithm to use for hashed values */ + protected $hash; + + /** @var int Length of Session ID string */ + protected $idLength; + + /** @var int Number of bits in encoded Session ID characters */ + protected $idBits; + + /** @var boolean Generate fake PHPSESSID cookie */ + protected $decoy; + + /** @var int Minimum time in seconds to regenerate session id */ + protected $timeMin; + + /** @var int Maximum time in seconds to regenerate session id */ + protected $timeMax; + + /** + * Config settings can include: + * - name: Name of the session (Default: clsession) + * - path: Server path the cookie is available on (Default: /) + * - domain: Domain the cookie is available to (Default: localhost) + * - secure: Only transmit the cookie over https (Default: false) + * - hash: 0 = MD5, 1 = SHA1, or supported hash name (Default: 1) + * - decoy: True/False to generate fake PHPSESSID cookie (Default: true) + * - min: Min time, in seconds, to regenerate session (Default: 60) + * - max: Max time, in seconds, to regenerate session (Default: 600). + * @param array $config Session Configuration + * + * @throws \Exception + */ + public function __construct(array $config = []) + { + $settings = array_merge( + [ + 'name' => 'asdfdotdev', + 'path' => '/', + 'domain' => 'localhost', + 'secure' => false, + 'bits' => 4, + 'length' => 32, + 'hash' => 'sha256', + 'decoy' => true, + 'min' => 60, + 'max' => 600, + 'debug' => false, + ], + $config + ); + + /** Configure settings */ + $this->setName($settings['name']); + $this->setPath($settings['path']); + $this->setDomain($settings['domain']); + $this->setSecure($settings['secure']); + $this->setIdBits($settings['bits']); + $this->setIdLength($settings['length']); + $this->setHash($settings['hash']); + $this->decoy = $settings['decoy']; + $this->timeMin = $settings['min']; + $this->timeMax = $settings['max']; + $this->debug = $settings['debug']; + + $this->verifySettings(); + + return $this; + } + + /** + * Set session name, this is also used as the name of the session cookie. + * + * @param string $name Session Name + */ + protected function setName(string $name) + { + $this->name = $name; + } + + /** + * Get session name. + * + * @return string Session Name + */ + protected function getName() + { + return $this->name; + } + + /** + * Set path on the domain where the cookies will work + * Use a single slash (default) for all paths on the domain. + * + * @param string $path Cookie Path + * @return void + */ + protected function setPath(string $path) + { + $this->path = $path; + } + + /** + * Get cookie path. + * + * @return string Cookie Path + */ + protected function getPath() + { + return $this->path; + } + + /** + * Set cookie domain. To make cookie visible on all subdomains prefixed with a dot + * Ex) .christopherl.com. + * + * @param string $domain Cookie Domain + */ + protected function setDomain(string $domain = '') + { + $domain = ($domain == '') ? $_SERVER['SERVER_NAME'] : $domain; + $this->domain = $domain; + } + + /** + * Get session cookie domain. + * + * @return string Cookie Domain + */ + protected function getDomain() + { + return $this->domain; + } + + /** + * Set cookie secure status. If TRUE cookie will only be sent over secure connections. + * + * @param bool $secure Cookie Secure Status + * + * @return void + */ + protected function setSecure(bool $secure = false) + { + $this->secure = $secure; + } + + /** + * Get cookie secure status. + * + * @return bool Cookie Secure Status + */ + protected function getSecure() + { + return $this->secure; + } + + /** + * Set cookie id hash method. + * + * @param int/string $hash 0 = MD5, 1 = SHA1, or supported hash name (Default: 1) + * + * @throws \Exception + */ + protected function setHash(string $hash = '') + { + if (in_array($hash, hash_algos())) { + $this->hash = $hash; + } else { + $this->Error( + 'Server does not support selected hash algorithm selected.' + ); + } + } + + /** + * Get session hash setting. + * + * @return int Cookie Hash Setting + */ + public function getHash() + { + return $this->hash; + } + + /** + * Set length of session id string. + * + * @param int $length + * @throws \Exception + * + * @return void + */ + protected function setIdLength(int $length) + { + if (in_array($length, range(22, 256))) { + $this->idLength = $length; + } else { + $this->error( + 'Session ID length invalid. Length must be between 22 to 256.' + ); + } + } + + protected function getIdLength() + { + return $this->idLength; + } + + protected function setIdBits(int $bits) + { + if (in_array($bits, range(4, 6))) { + $this->idBits = $bits; + } else { + $this->error( + 'Session ID bits per character invalid. Options are 4, 5, or 6.' + ); + } + } + + protected function getIdBits() + { + return $this->idBits; + } + + /** + * Create decoy cookie if it hasn't been set. + * + * This cookie intentionally exhibits signs of a week session cookie so that it + * looks attractive to would be scoundrels. These vulnerabilities include: + * - PHPSESSID name + * - MD5 hash value + * - not HTTPOnly + * + * @throws \Exception + * + * @return void + */ + protected function generateDecoyCookie() + { + if (!isset($_COOKIE['PHPSESSID'])) { + $this->setValue('decoy_value', md5(mt_rand())); + setcookie( + 'PHPSESSID', + $this->getValue('decoy_value'), + 0, + $this->getPath(), + $this->getDomain(), + $this->getSecure(), + 0 + ); + } + } + + /** + * Destroy PHPSESSID decoy cookie. + * + * @return void + */ + protected function killDecoyCookie() + { + if (isset($_COOKIE['PHPSESSID'])) { + unset($_COOKIE['PHPSESSID']); + } + } + + /** + * Generate sha256 fingerprint hash from current settings. + * + * @return string + */ + protected function generateFingerprint() + { + return hash( + 'sha256', + $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id() + ); + } + + /** + * Create session fingerprint from user agent, ip and session id in an attempt to discourage session hijacking. + * + * @return void + */ + protected function setFingerprint() + { + $this->setValue( + 'fingerprint', + $this->generateFingerprint() + ); + } + + /** + * Compare current user agent, ip and session id against stored session fingerprint + * If compared value doesn't match stored value session end the session. + * + * @throws \Exception + * + * @return void + */ + protected function validateFingerprint() + { + $valid = $this->generateFingerprint(); + + if ($this->getValue('fingerprint') == '') { + $this->setFingerprint(); + } elseif ($this->getValue('fingerprint') != $valid) { + $this->end(); + } + } + + /** + * Reset session lifespan time using random value between timeMin and timeMax. + * + * @return void + */ + protected function resetLifespan() + { + $this->setValue( + 'lifespan', + date('U') + mt_rand($this->timeMin, $this->timeMax) + ); + } + + /** + * Compare session lifespan time to current time + * If current time is beyond session lifespan regenerate session id. + * + * @throws \Exception + * + * @return void + */ + protected function checkLifespan() + { + if ($this->getValue('lifespan') == '') { + $this->resetLifespan(); + } elseif ($this->getValue('lifespan') < date('U')) { + $this->regenerate(); + } + } + + /** + * Start Session. + * + * @param bool $restart Force session id regeneration + * + * @throws \Exception + * + * @return void + */ + public function start($restart = false) + { + $foo = 'bar'; + + if ($restart) { + $this->regenerateId(); + } + + if (function_exists('ini_set') && !$restart) { + $this->configureSystemSessionSettings(); + } + + session_set_cookie_params( + 0, + $this->getPath(), + $this->getDomain(), + $this->getSecure(), + true + ); + + session_name($this->getName()); + session_start(); + + if (!isset($_SESSION[$this->valuesKey])) { + $_SESSION[$this->valuesKey] = []; + } + + $valuesEmpty = (count($_SESSION[$this->valuesKey]) == 0); + + if ($restart || $valuesEmpty) { + $this->setFingerprint(); + $this->resetLifespan(); + } + + if ($this->decoy) { + $this->generateDecoyCookie(); + } else { + $this->dropValue('decoy_value'); + } + + $this->validateFingerprint(); + $this->checkLifespan(); + $this->setValue('session_loaded', date('U')); + $this->setValue( + 'ttl', + ($this->getValue('lifespan') - $this->getValue('session_loaded')) + ); + } + + /** + * Get session variable value. + * + * @param string $key Name of the session variable value to retrieve + * + * @return mixed Value of the variable requested + */ + public function getValue($key) + { + if (!isset($_SESSION[$this->valuesKey][$key])) { + return null; + } + + return $_SESSION[$this->valuesKey][$key]; + } + + /** + * Create session value if not present, otherwise the value is updated. + * + * @param string $key Name of the session variable to create/update + * @param mixed $value Value of the session variable to create/update + * @param bool $hash false = store $value in session array as plain text, + * true = store hash of $value in session array + * + * @return void + */ + public function setValue($key, $value, $hash = false) + { + if ($hash) { + $value = hash($this->getHash(), $value); + } + + $_SESSION[$this->valuesKey][$key] = $value; + } + + /** + * Append to session value. + * Note: Append behavior varies by current value type: + * - Array: passed value added to array (array_merge) + * - String: passed value added to the end of the string (concatenation) + * - Other: passed value replaces the saved value (replace) + * + * @param string $key Name of the session variable to create/update + * @param string $value String to append to the end of the current value + * + * @throws \Exception + * + * @return void + */ + public function appValue($key, $value) + { + $currentValue = $this->getValue($key); + + if (isset($currentValue)) { + if (is_array($currentValue)) { + $updatedValue = array_merge($currentValue, $value); + } elseif (is_string($currentValue)) { + $updatedValue = $currentValue . $value; + } else { + $updatedValue = $value; + } + } else { + $updatedValue = $value; + } + + $this->setValue($key, $updatedValue); + } + + /** + * Increment session value. + * + * @param string $key Name of the session variable to create/increment + * @param int $value Amount to add to the current value + * + * @throws \Exception + * + * @return void + */ + public function incValue($key, $value) + { + if (!is_numeric($value)) { + $this->error( + sprintf( + 'Only numeric values can be passed to %s', + __METHOD__ + ) + ); + } + + $currentValue = $this->getValue($key); + + if (isset($currentValue)) { + $updatedValue = $currentValue + $value; + } else { + $updatedValue = $value; + } + + $this->setValue($key, $updatedValue); + } + + /** + * Drop session value. + * + * @param string $key Name of the session variable to drop + * + * @return void + */ + public function dropValue($key) + { + unset($_SESSION[$this->valuesKey][$key]); + } + + /** + * Restart session with reset flag true. + * + * @throws \Exception + * + * @return void + */ + public function regenerate() + { + $this->start(true); + } + + /** + * Regenerate session id. + */ + private function regenerateId() + { + session_regenerate_id(true); + $new_id = session_id(); + session_write_close(); + session_id($new_id); + } + + /** + * Update system session values. + * + * @see https://www.php.net/manual/en/session.configuration.php + */ + private function configureSystemSessionSettings() + { + ini_set('session.sid_length', $this->getIdLength()); + ini_set('session.sid_bits_per_character', $this->getIdBits()); + ini_set('session.cookie_secure', $this->getSecure()); + ini_set('session.use_strict_mode', 1); + ini_set('session.use_only_cookies', 1); + } + + /** + * End session. + * + * @return void + */ + public function end() + { + session_unset(); + session_destroy(); + } + + /** + * Dump session contents for debugging or testing. + * + * @param int $format 0 = string + * 1 = array + * 2 = json encoded string + * + * @return mixed + */ + public function dump($format = 1) + { + switch ($format) { + // string + case 1: + return print_r($_SESSION, true); + break; + // array + case 2: + return $_SESSION; + break; + // json string + case 3: + default: + return json_encode($_SESSION); + break; + } + } + + /** + * Prevents incorrect configuration of timeMin/time/Max lifespan values. + * + * @throws \Exception + * + * @return void + */ + private function validateSessionLifespan() + { + if ($this->timeMin > $this->timeMax) { + $this->timeMin = $this->timeMin + $this->timeMax; + $this->timeMax = $this->timeMin - $this->timeMax; + $this->timeMin -= $this->timeMax; + } + } + + /** + * In the event timezone is unset, set it if possible. + * + * @throws \Exception + * + * @return void + */ + private function validateSystemTimezone() + { + if (function_exists('ini_get') && ini_get('date.timezone') == '') { + date_default_timezone_set('UTC'); + } + } + + /** + * Confirm session path is writable. + * + * @throws \Exception + * + * @return void + */ + private function validateSessionDir() + { + if (!is_writable(session_save_path())) { + $this->error( + 'Session directory is not writable.' + ); + } + } + + private function validateSessionDomain() + { + if ($_SERVER['HTTP_HOST'] != $this->getDomain()) { + $this->error( + sprintf( + 'Session cookie domain (%s) and request domain (%s) mismatch.', + $_SERVER['HTTP_HOST'], + $this->getDomain() + ) + ); + } + } + + private function validatePHPVersion() + { + if (version_compare(phpversion(), '7.1.0', '<')) { + $this->error( + 'PHP v7.1.0 or newer is required.', + ); + } + } + + /** + * Throw exception on error. + * + * @param string $response Explain to them what they screwed up + * + * @throws \Exception + * + * @return void + */ + protected function error($response) + { + throw new \Exception($response, null, null); + } + + /** + * Validate various requirements + * @throws \Exception + */ + protected function verifySettings() + { + $this->validateSystemTimezone(); + $this->validateSessionLifespan(); + + if ($this->debug) { + $this->validatePHPVersion(); + $this->validateSessionDir(); + $this->validateSessionDomain(); + } + } +} From dfa9e577cca3e823c876f134628c661ed2baa824 Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 16:46:53 -0700 Subject: [PATCH 2/8] Fix typo --- src/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Session.php b/src/Session.php index 995577a..7632037 100644 --- a/src/Session.php +++ b/src/Session.php @@ -691,7 +691,7 @@ private function validatePHPVersion() { if (version_compare(phpversion(), '7.1.0', '<')) { $this->error( - 'PHP v7.1.0 or newer is required.', + 'PHP v7.1.0 or newer is required.' ); } } From bf356e6a6de3b8105aac068838a8df205e9d2683 Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 17:45:08 -0700 Subject: [PATCH 3/8] Fix tests --- _tests/TestSession.php | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/_tests/TestSession.php b/_tests/TestSession.php index 9cfc8a7..290658d 100644 --- a/_tests/TestSession.php +++ b/_tests/TestSession.php @@ -51,7 +51,7 @@ public function testSessionNoSettings() // Verify decoy value is in session array (decoy cookie in use) $session_dump = $session->dump(2); - $this->assertTrue(array_key_exists('decoy_value', $session_dump['asdfValues'])); + $this->assertTrue(array_key_exists('decoy_value', $session_dump['asdfdotdev.session'])); } /** @@ -95,7 +95,7 @@ public function testSessionCustomSettings() // Verify decoy value isn't in session array (no decoy cookie in use) $session_dump = $session->dump(2); - $this->assertFalse(array_key_exists('decoy_value', $session_dump['asdfValues'])); + $this->assertFalse(array_key_exists('decoy_value', $session_dump['asdfdotdev.session'])); } /** @@ -196,7 +196,7 @@ public function testSessionDeleteValue() // Verify decoy value isn't in session array (no decoy cookie in use) $session_dump = $session->dump(2); - $this->assertFalse(array_key_exists('my_variable', $session_dump['asdfValues'])); + $this->assertFalse(array_key_exists('my_variable', $session_dump['asdfdotdev.session'])); } /** @@ -234,11 +234,23 @@ public function testSessionFingerprint() $session->start(); // Verify fingerprint matches expected recipe - $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); + $this->assertEquals( + $session->getValue('fingerprint'), + hash( + 'sha256', + $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id() + ) + ); // Verify regenerating session id maintains valid fingerprint $session->regenerate(); - $this->assertEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); + $this->assertEquals( + $session->getValue('fingerprint'), + hash( + 'sha256', + $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id() + ) + ); // Randomly change either the user agent or ip address if (rand(0, 1) == 1) { @@ -263,21 +275,7 @@ public function testExceptionThrow() $session = new Session(); $session->start(); - $missing_value = 'this should not change'; - - // Try to retrieve a session value that doesn't exist - try { - $missing_value = $session->getValue('there_is_no_spoon'); - } - - // Catch the exception we should have thrown and verify the message is correct - catch (\Exception $e) { - $this->assertEquals($e->getMessage(), 'Invalid Session Value Name'); - } - - // make sure our value hasn't changed - finally { - $this->assertEquals($missing_value, 'this should not change'); - } + $missing_value = $session->getValue('there_is_no_spoon'); + $this->assertEquals($missing_value, null); } } From 39702294b56f868181b1d065ab68ca6d4c242ce2 Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 20:47:52 -0700 Subject: [PATCH 4/8] Add tests --- _tests/TestSession.php | 168 ++++++++++++++++++++++++++++++++++++----- composer.json | 2 +- src/Session.php | 66 +++++++--------- 3 files changed, 178 insertions(+), 58 deletions(-) diff --git a/_tests/TestSession.php b/_tests/TestSession.php index 290658d..ce9c739 100644 --- a/_tests/TestSession.php +++ b/_tests/TestSession.php @@ -61,18 +61,18 @@ public function testSessionCustomSettings() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - $session_settings = array( + + require '../src/Session.php'; + $session = new Session([ 'name' => 'TestSession', 'path' => '/subdirectory', 'domain' => '.testing.edu', 'secure' => true, 'decoy' => false, + 'hash' => 'sha512', 'min' => 150, 'max' => 200, - ); - - require '../src/Session.php'; - $session = new Session($session_settings); + ]); $session->start(); // Verify session lifespan falls between default times range of 60 & 600 @@ -93,6 +93,9 @@ public function testSessionCustomSettings() $this->assertEquals($array_details['secure'], true); $this->assertEquals($array_details['httponly'], true); + // Verify hash has changed + $this->assertEquals($session->getHash(), 'sha512'); + // Verify decoy value isn't in session array (no decoy cookie in use) $session_dump = $session->dump(2); $this->assertFalse(array_key_exists('decoy_value', $session_dump['asdfdotdev.session'])); @@ -110,11 +113,19 @@ public function testSessionValue() $session = new Session(); $session->start(); - // Create a new session string value + // Create a new session string values $session->setValue('my_variable', 'this is the value'); + $session->setValue('my_hashed_variable', 'this is the other value', true); - // Verifiy creation of session value + // Verifiy creation of session values $this->assertEquals($session->getValue('my_variable'), 'this is the value'); + $this->assertEquals( + $session->getValue('my_hashed_variable'), + hash( + 'sha256', + 'this is the other value' + ) + ); // Change session value $session->setValue('my_variable', 50); @@ -233,17 +244,8 @@ public function testSessionFingerprint() $session = new Session(); $session->start(); - // Verify fingerprint matches expected recipe - $this->assertEquals( - $session->getValue('fingerprint'), - hash( - 'sha256', - $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id() - ) - ); + $session->setValue('my_variable', 'this is the value'); - // Verify regenerating session id maintains valid fingerprint - $session->regenerate(); $this->assertEquals( $session->getValue('fingerprint'), hash( @@ -252,15 +254,19 @@ public function testSessionFingerprint() ) ); - // Randomly change either the user agent or ip address if (rand(0, 1) == 1) { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)'; } else { $_SERVER['REMOTE_ADDR'] = '10.0.10.1'; } - // Verify that whatever we changed causes the fingerprint comparison to fail - $this->assertNOTEquals($session->getValue('fingerprint'), sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id())); + $this->assertNOTEquals( + $session->getValue('fingerprint'), + hash( + 'sha256', + $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'] . session_id() + ) + ); } /** @@ -278,4 +284,126 @@ public function testExceptionThrow() $missing_value = $session->getValue('there_is_no_spoon'); $this->assertEquals($missing_value, null); } + + /** + * @runInSeparateProcess + */ + public function testInvalidHash() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + try { + require '../src/Session.php'; + $session = new Session([ + 'hash' => 'doesnotexist' + ]); + $session->start(); + } catch (\Exception $e) { + $this->assertEquals($e->getMessage(), 'Server does not support selected hash algorithm selected.'); + } + } + + /** + * @runInSeparateProcess + */ + public function testInvalidIdLength() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + try { + require '../src/Session.php'; + $session = new Session([ + 'length' => '21' + ]); + $session->start(); + } catch (\Exception $e) { + $this->assertEquals($e->getMessage(), 'Session ID length invalid. Length must be between 22 to 256.'); + } + } + + /** + * @runInSeparateProcess + */ + public function testInvalidIdBits() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + try { + require '../src/Session.php'; + $session = new Session([ + 'bits' => '3' + ]); + $session->start(); + } catch (\Exception $e) { + $this->assertEquals($e->getMessage(), 'Session ID bits per character invalid. Options are 4, 5, or 6.'); + } + } + + /** + * @runInSeparateProcess + */ + public function testInvalidIncrement() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + try { + require '../src/Session.php'; + $session = new Session(); + $session->start(); + + $session->incValue('should-not-work', 'this is not a numeric value'); + } catch (\Exception $e) { + $this->assertEquals($e->getMessage(), 'Only numeric values can be passed to Asdfdotdev\Session::incValue'); + } + } + + /** + * @runInSeparateProcess + */ + public function testInvalidFingerprint() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + require '../src/Session.php'; + $session = new Session(); + $session->start(); + $session->setValue('my_variable', 'this is the value'); + + $original_print = $session->getValue('fingerprint'); + $session->setValue('fingerprint', 'hey do not set this directly'); + $invalid_print = $session->getValue('fingerprint'); + + $this->assertNotEquals($original_print, $invalid_print); + + $this->assertTrue(!empty($_SESSION)); + + $session->regenerate(); + + $this->assertTrue(empty($_SESSION)); + } + + /** + * @runInSeparateProcess + */ + public function testInvalidMinMax() + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + require '../src/Session.php'; + $session = new Session([ + 'min' => 600, + 'max' => 60, + ]); + $session->start(); + + $ttl = $session->getValue('ttl'); + + $this->assertGreaterThan(0, $ttl); + } } diff --git a/composer.json b/composer.json index 9ab7462..6549e39 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "asdfdotdev/session", - "version": "1.0.0", + "version": "0.4.0", "description": "endeavors to make it easy to use basic session best practices in PHP scripts.", "type": "library", "license": "LGPL-2.1-only", diff --git a/src/Session.php b/src/Session.php index 7632037..ed8cdb9 100644 --- a/src/Session.php +++ b/src/Session.php @@ -15,7 +15,7 @@ * @author Chris Carlevato * @copyright 2015-2019 Chris Carlevato * @license http://www.gnu.org/licenses/lgpl-2.1.html - * @version 1.0.0 + * @version 0.4.0 * @link https://github.com/asdfdotdev/session */ @@ -69,8 +69,9 @@ class Session * - secure: Only transmit the cookie over https (Default: false) * - hash: 0 = MD5, 1 = SHA1, or supported hash name (Default: 1) * - decoy: True/False to generate fake PHPSESSID cookie (Default: true) - * - min: Min time, in seconds, to regenerate session (Default: 60) - * - max: Max time, in seconds, to regenerate session (Default: 600). + * - min: Min time, in seconds, to regenerate session (Default: 60) + * - max: Max time, in seconds, to regenerate session (Default: 600) + * * @param array $config Session Configuration * * @throws \Exception @@ -295,18 +296,6 @@ protected function generateDecoyCookie() } } - /** - * Destroy PHPSESSID decoy cookie. - * - * @return void - */ - protected function killDecoyCookie() - { - if (isset($_COOKIE['PHPSESSID'])) { - unset($_COOKIE['PHPSESSID']); - } - } - /** * Generate sha256 fingerprint hash from current settings. * @@ -339,7 +328,7 @@ protected function setFingerprint() * * @throws \Exception * - * @return void + * @return bool Valid fingerprint */ protected function validateFingerprint() { @@ -347,8 +336,10 @@ protected function validateFingerprint() if ($this->getValue('fingerprint') == '') { $this->setFingerprint(); + return true; } elseif ($this->getValue('fingerprint') != $valid) { $this->end(); + return false; } } @@ -393,13 +384,9 @@ protected function checkLifespan() */ public function start($restart = false) { - $foo = 'bar'; - if ($restart) { $this->regenerateId(); - } - - if (function_exists('ini_set') && !$restart) { + } else { $this->configureSystemSessionSettings(); } @@ -418,11 +405,13 @@ public function start($restart = false) $_SESSION[$this->valuesKey] = []; } - $valuesEmpty = (count($_SESSION[$this->valuesKey]) == 0); - - if ($restart || $valuesEmpty) { - $this->setFingerprint(); + if ($restart) { $this->resetLifespan(); + $valuesEmpty = (count($_SESSION[$this->valuesKey]) === 0); + + if ($valuesEmpty) { + $this->setFingerprint(); + } } if ($this->decoy) { @@ -431,13 +420,14 @@ public function start($restart = false) $this->dropValue('decoy_value'); } - $this->validateFingerprint(); - $this->checkLifespan(); - $this->setValue('session_loaded', date('U')); - $this->setValue( - 'ttl', - ($this->getValue('lifespan') - $this->getValue('session_loaded')) - ); + if ($this->validateFingerprint()) { + $this->checkLifespan(); + $this->setValue('session_loaded', date('U')); + $this->setValue( + 'ttl', + ($this->getValue('lifespan') - $this->getValue('session_loaded')) + ); + } } /** @@ -582,11 +572,13 @@ private function regenerateId() */ private function configureSystemSessionSettings() { - ini_set('session.sid_length', $this->getIdLength()); - ini_set('session.sid_bits_per_character', $this->getIdBits()); - ini_set('session.cookie_secure', $this->getSecure()); - ini_set('session.use_strict_mode', 1); - ini_set('session.use_only_cookies', 1); + if (function_exists('ini_set')) { + ini_set('session.sid_length', $this->getIdLength()); + ini_set('session.sid_bits_per_character', $this->getIdBits()); + ini_set('session.cookie_secure', $this->getSecure()); + ini_set('session.use_strict_mode', 1); + ini_set('session.use_only_cookies', 1); + } } /** From bb6b64a4f058acdcd94a638ee18e0a7353eec60c Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 21:09:42 -0700 Subject: [PATCH 5/8] Update README files --- _examples/README.md | 32 +++++++++++++++++++++++--------- _tests/README.md | 30 +++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/_examples/README.md b/_examples/README.md index d5b434b..0d546b7 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -1,18 +1,23 @@ -todo: fix it +# Examples + +The following outlines a few usage options. + +### Creating a session -Creating a session: ``` -include('/path/to/cl_session.php'); -$session = new ChristopherL\Session(); +include('/path/to/Session.php'); +$session = new Asdfdotdev\Session(); $session->start(); ``` -Creating a Session Variable +### Creating a Session Variable + ``` $session->setValue('my_variable','value'); ``` -Changing a Session Variable Value +### Changing a Session Variable Value + ``` // Set New Value $session->setValue('my_variable','new value'); @@ -23,11 +28,20 @@ $session->incValue('my_variable', 1); // Append to Value $session->appValue('my_variable','appended to current value'); -// Hash Stored Value (SHA1) -$session->setValue('my_variable','value_to_hash', 1); +// Hash Stored Value +$session->setValue('my_variable','value_to_hash', true); ``` -Force Session ID Regeneration +### Force Session ID Regeneration + ``` $session->regenerate(); ``` + +## Included Examples + +- [Basic Session Usage](./basic.php) +- [Append a Session Value](./append.php) +- [Hash a Session Value](./hashed) +- [Increment a Session Value](./increment) +- [Regenerate Session ID](./regenerate.php) diff --git a/_tests/README.md b/_tests/README.md index 8f33a8b..bcd6e4d 100644 --- a/_tests/README.md +++ b/_tests/README.md @@ -1 +1,29 @@ -todo: write it +# Tests + +A number of unit tests are included in this repo and you can [browse our code voerage at Codecov](https://codecov.io/gh/asdfdotdev/session/). + +## Compatibility + +### PHPUnit Tests + +PHPUnit tests are written for v7.5+ + +### PHP Code Sniffer + +PHP Code Sniffer v3.4+ is recommended. + +## Running PHPUnit Tests + +From the `_tests/` directory run `$ ../vendor/bin/phpunit` to run the tests. This should result in output similar to: + +``` +PHPUnit 7.5.14 by Sebastian Bergmann and contributors. + +............... 15 / 15 (100%) + +Time: 1.97 seconds, Memory: 6.00 MB + +OK (15 tests, 42 assertions) + +Generating code coverage report in Clover XML format ... done +``` From 6a7978545d5a8a5fe8fe6b8f96350377e57fd544 Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Sun, 18 Aug 2019 21:17:13 -0700 Subject: [PATCH 6/8] Fix docblocks --- src/Session.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Session.php b/src/Session.php index ed8cdb9..24577f9 100644 --- a/src/Session.php +++ b/src/Session.php @@ -157,7 +157,6 @@ protected function getPath() /** * Set cookie domain. To make cookie visible on all subdomains prefixed with a dot - * Ex) .christopherl.com. * * @param string $domain Cookie Domain */ @@ -246,11 +245,25 @@ protected function setIdLength(int $length) } } + /** + * Get session id length. + * + * @return int + */ protected function getIdLength() { return $this->idLength; } + /** + * Set session id bits. + * + * @param int $bits + * + * @throws \Exception + * + * @return void + */ protected function setIdBits(int $bits) { if (in_array($bits, range(4, 6))) { @@ -262,6 +275,11 @@ protected function setIdBits(int $bits) } } + /** + * Get session id bits. + * + * @return int + */ protected function getIdBits() { return $this->idBits; @@ -666,6 +684,13 @@ private function validateSessionDir() } } + /** + * Confirm that request domain matches cookie domain. + * + * @throws \Exception + * + * @return void + */ private function validateSessionDomain() { if ($_SERVER['HTTP_HOST'] != $this->getDomain()) { @@ -679,6 +704,13 @@ private function validateSessionDomain() } } + /** + * Confirm PHP version is at least 7.1.0 + * + * @throws \Exception + * + * @return void + */ private function validatePHPVersion() { if (version_compare(phpversion(), '7.1.0', '<')) { From 79c94161583535e4e3d1724f12bc304aefe0cb3e Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Mon, 19 Aug 2019 21:03:14 -0700 Subject: [PATCH 7/8] Add release prep --- README.md | 2 -- _examples/README.md | 6 ++++ _tests/TestSession.php | 27 -------------- src/Session.php | 82 +++++++++++++++++++++++------------------- 4 files changed, 51 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 7464a77..8e6c60c 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,6 @@ Session class is developed for and tested with recent PHP Version: composer require asdfdotdev/session ``` -If you don't use composer just clone/download the [Session](./src/Session.php) class file and include it in your project. - ## Use A number of usage examples are included in `_examples/`. Check out the examples [README](./_examples/README.md) for further details. diff --git a/_examples/README.md b/_examples/README.md index 0d546b7..12e948a 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -5,6 +5,12 @@ The following outlines a few usage options. ### Creating a session ``` +// Use Composer +require __DIR__ . '/vendor/autoload.php'; +$session = new Asdfdotdev\Session(); +$session->start(); + +// Direct include('/path/to/Session.php'); $session = new Asdfdotdev\Session(); $session->start(); diff --git a/_tests/TestSession.php b/_tests/TestSession.php index ce9c739..f1287b4 100644 --- a/_tests/TestSession.php +++ b/_tests/TestSession.php @@ -243,7 +243,6 @@ public function testSessionFingerprint() require '../src/Session.php'; $session = new Session(); $session->start(); - $session->setValue('my_variable', 'this is the value'); $this->assertEquals( @@ -361,32 +360,6 @@ public function testInvalidIncrement() } } - /** - * @runInSeparateProcess - */ - public function testInvalidFingerprint() - { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'; - $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - - require '../src/Session.php'; - $session = new Session(); - $session->start(); - $session->setValue('my_variable', 'this is the value'); - - $original_print = $session->getValue('fingerprint'); - $session->setValue('fingerprint', 'hey do not set this directly'); - $invalid_print = $session->getValue('fingerprint'); - - $this->assertNotEquals($original_print, $invalid_print); - - $this->assertTrue(!empty($_SESSION)); - - $session->regenerate(); - - $this->assertTrue(empty($_SESSION)); - } - /** * @runInSeparateProcess */ diff --git a/src/Session.php b/src/Session.php index 24577f9..ca89036 100644 --- a/src/Session.php +++ b/src/Session.php @@ -63,14 +63,14 @@ class Session /** * Config settings can include: - * - name: Name of the session (Default: clsession) - * - path: Server path the cookie is available on (Default: /) - * - domain: Domain the cookie is available to (Default: localhost) - * - secure: Only transmit the cookie over https (Default: false) - * - hash: 0 = MD5, 1 = SHA1, or supported hash name (Default: 1) - * - decoy: True/False to generate fake PHPSESSID cookie (Default: true) - * - min: Min time, in seconds, to regenerate session (Default: 60) - * - max: Max time, in seconds, to regenerate session (Default: 600) + * - name: Name of the session (Default: asdfdotdev) + * - path: Server path the cookie is available on (Default: /) + * - domain: Domain the session cookie is available on (Default: localhost) + * - secure: Only transmit the cookie over https (Default: false) + * - hash: Name of algorithm to use for hashed values (Default: sha256) + * - decoy: Generate fake PHPSESSID cookie (Default: true) + * - min: Min time in seconds to regenerate session (Default: 60) + * - max: Max time in seconds to regenerate session (Default: 600) * * @param array $config Session Configuration * @@ -95,7 +95,6 @@ public function __construct(array $config = []) $config ); - /** Configure settings */ $this->setName($settings['name']); $this->setPath($settings['path']); $this->setDomain($settings['domain']); @@ -300,7 +299,9 @@ protected function getIdBits() */ protected function generateDecoyCookie() { - if (!isset($_COOKIE['PHPSESSID'])) { + $has_decoy = isset($_COOKIE['PHPSESSID']); + + if ($this->decoy && !$has_decoy) { $this->setValue('decoy_value', md5(mt_rand())); setcookie( 'PHPSESSID', @@ -350,15 +351,21 @@ protected function setFingerprint() */ protected function validateFingerprint() { + $print = $this->getValue('fingerprint'); $valid = $this->generateFingerprint(); - if ($this->getValue('fingerprint') == '') { + if (!isset($print)) { + $this->setFingerprint(); - return true; - } elseif ($this->getValue('fingerprint') != $valid) { + + } elseif ($print != $valid) { + $this->end(); return false; + } + + return true; } /** @@ -408,6 +415,31 @@ public function start($restart = false) $this->configureSystemSessionSettings(); } + $this->prepareSession(); + + if ($restart) { + $this->setFingerprint(); + $this->resetLifespan(); + } + + if ($this->validateFingerprint()) { + $this->generateDecoyCookie(); + $this->checkLifespan(); + $this->setValue('session_loaded', date('U')); + $this->setValue( + 'ttl', + ($this->getValue('lifespan') - $this->getValue('session_loaded')) + ); + } + } + + /** + * Generate generate system session, maybe scaffold value array + * + * @return void + */ + private function prepareSession() + { session_set_cookie_params( 0, $this->getPath(), @@ -422,30 +454,6 @@ public function start($restart = false) if (!isset($_SESSION[$this->valuesKey])) { $_SESSION[$this->valuesKey] = []; } - - if ($restart) { - $this->resetLifespan(); - $valuesEmpty = (count($_SESSION[$this->valuesKey]) === 0); - - if ($valuesEmpty) { - $this->setFingerprint(); - } - } - - if ($this->decoy) { - $this->generateDecoyCookie(); - } else { - $this->dropValue('decoy_value'); - } - - if ($this->validateFingerprint()) { - $this->checkLifespan(); - $this->setValue('session_loaded', date('U')); - $this->setValue( - 'ttl', - ($this->getValue('lifespan') - $this->getValue('session_loaded')) - ); - } } /** From 06d10bb5a19c3a853f8af7551589662598b1442c Mon Sep 17 00:00:00 2001 From: Chris Carlevato Date: Tue, 20 Aug 2019 20:09:48 -0700 Subject: [PATCH 8/8] Add minor tweaks --- _examples/README.md | 2 +- _tests/README.md | 6 +++--- src/Session.php | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/_examples/README.md b/_examples/README.md index 12e948a..e1ead5c 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -5,7 +5,7 @@ The following outlines a few usage options. ### Creating a session ``` -// Use Composer +// Using Composer require __DIR__ . '/vendor/autoload.php'; $session = new Asdfdotdev\Session(); $session->start(); diff --git a/_tests/README.md b/_tests/README.md index bcd6e4d..ca8cbf5 100644 --- a/_tests/README.md +++ b/_tests/README.md @@ -19,11 +19,11 @@ From the `_tests/` directory run `$ ../vendor/bin/phpunit` to run the tests. Thi ``` PHPUnit 7.5.14 by Sebastian Bergmann and contributors. -............... 15 / 15 (100%) +.............. 14 / 14 (100%) -Time: 1.97 seconds, Memory: 6.00 MB +Time: 1.76 seconds, Memory: 6.00 MB -OK (15 tests, 42 assertions) +OK (14 tests, 39 assertions) Generating code coverage report in Clover XML format ... done ``` diff --git a/src/Session.php b/src/Session.php index ca89036..4930152 100644 --- a/src/Session.php +++ b/src/Session.php @@ -355,14 +355,10 @@ protected function validateFingerprint() $valid = $this->generateFingerprint(); if (!isset($print)) { - $this->setFingerprint(); - } elseif ($print != $valid) { - $this->end(); return false; - } return true;