diff --git a/libs/README.md b/libs/README.md index 44301f3..e5f0777 100644 --- a/libs/README.md +++ b/libs/README.md @@ -2,8 +2,6 @@ CSRFProtector configuration ========================================== - `CSRFP_TOKEN`: name of the csrf nonce, used for cookie or posting as argument. default: `CSRFP-Token` (if left blank) - - `logDirectory`: location of the directory at which log files will be saved, either **relative** to the default `config.php` file location or an **absolute** path. This is required for file based logging (default), Not needed, in case you override logging function to implement your logging logic. (View [Overriding logging function](https://github.com/mebjas/CSRF-Protector-PHP/wiki/Overriding-logging-function)) -
**Default value:** `../log/` - `failedAuthAction`: Action code (integer) for action to be taken in case of failed validation. Has two different values for bot `GET` and `POST`. Different action codes are specified as follows, (
**Default:** `0` for both `GET` & `POST`): * `0` Send **403, Forbidden** Header * `1` **Strip the POST/GET query** and forward the request! unset($_POST) diff --git a/libs/config.sample.php b/libs/config.sample.php index ebb526a..b9054f7 100755 --- a/libs/config.sample.php +++ b/libs/config.sample.php @@ -2,14 +2,12 @@ /** * Configuration file for CSRF Protector * Necessary configurations are (library would throw exception otherwise) - * ---- logDirectory * ---- failedAuthAction * ---- jsUrl * ---- tokenLength */ return array( - "CSRFP_TOKEN" => "", - "logDirectory" => "../log", + "CSRFP_TOKEN" => "", "failedAuthAction" => array( "GET" => 0, "POST" => 0), @@ -26,5 +24,5 @@ "disabledJavascriptMessage" => "This site attempts to protect users against Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. See details of your web browser for how to enable JavaScript.", - "verifyGetFor" => array() + "verifyGetFor" => array() ); \ No newline at end of file diff --git a/libs/csrf/LoggerInterface.php b/libs/csrf/LoggerInterface.php index 6e898fd..b0b6463 100644 --- a/libs/csrf/LoggerInterface.php +++ b/libs/csrf/LoggerInterface.php @@ -1,6 +1,6 @@ path = $cfg['path']; - if (isset($cfg['domain'])) $this->domain = $cfg['domain']; - if (isset($cfg['secure'])) $this->secure = (bool) $cfg['secure']; - if (isset($cfg['expire']) && $cfg['expire']) $this->expire = (int)$cfg['expire']; + if (isset($cfg['path'])) { + $this->path = $cfg['path']; + } + + if (isset($cfg['domain'])) { + $this->domain = $cfg['domain']; + } + + if (isset($cfg['secure'])) { + $this->secure = (bool) $cfg['secure']; + } + + if (isset($cfg['expire']) && $cfg['expire']) { + $this->expire = (int)$cfg['expire']; + } } } } diff --git a/libs/csrf/csrfpDefaultLogger.php b/libs/csrf/csrfpDefaultLogger.php index 6f74a37..dffb667 100644 --- a/libs/csrf/csrfpDefaultLogger.php +++ b/libs/csrf/csrfpDefaultLogger.php @@ -8,51 +8,16 @@ // to avoid multiple declaration errors define('__CSRF_PROTECTOR_DEFAULT_LOGGER_', true); - class logDirectoryNotFoundException extends \exception {}; - class logFileWriteError extends \exception {}; - /** * Default logger class for CSRF Protector. * - * This is a file based logger class. + * This implementation is based on PHP's default error_log implementation. */ class csrfpDefaultLogger implements LoggerInterface { - /** - * Variable: $logDirectory - * directory for file based logging - */ - private $logDirectory; - - /** - * Constructor - * - * Parameters: - * $path - the path for logs to be stored (relative or absolute) + * Sends error message to the defined error_handling routines. * - * Returns: - * void - * - * Throws: - * logDirectoryNotFoundException - if log directory is not found - */ - function __construct($path) { - // Check for relative path - $this->logDirectory = __DIR__ . "/../" . $path; - - - // If the relative log directory path does not exist try as an absolute path. - if (!is_dir($this->logDirectory)) { - $this->logDirectory = $path; - } - - if (!is_dir($this->logDirectory)) { - throw new logDirectoryNotFoundException("OWASP CSRFProtector: Log Directory Not Found!"); - } - } - - /** - * logging method + * Based on PHP's default error_log method implementation. * * Parameters: * $message - the log message @@ -60,30 +25,16 @@ function __construct($path) { * * Return: * void - * - * Throws: - * logFileWriteError - if unable to log an attack */ public function log($message, $context = array()) { - // Append to the log file, or create it if it does not exist create - $logFile = fopen($this->logDirectory ."/" . date("m-20y") . ".log", "a+"); - - // Throw exception if above fopen fails - if (!$logFile) { - throw new logFileWriteError("OWASP CSRFProtector: Unable to write to the log file"); - } - $context['timestamp'] = time(); $context['message'] = $message; // Convert log array to JSON format to be logged - $context = json_encode($context) .PHP_EOL; - - // Append log to the file - fwrite($logFile, $context); - - // Close the file handler - fclose($logFile); + $contextString = "OWASP CSRF Protector PHP " + .json_encode($context) + .PHP_EOL; + error_log($contextString, /* message_type= */ 0); } } -} \ No newline at end of file +} diff --git a/libs/csrf/csrfprotector.php b/libs/csrf/csrfprotector.php index 32e057f..ad424f9 100755 --- a/libs/csrf/csrfprotector.php +++ b/libs/csrf/csrfprotector.php @@ -10,7 +10,7 @@ if (!defined('__CSRF_PROTECTOR__')) { define('__CSRF_PROTECTOR__', true); // to avoid multiple declaration errors - // name of HTTP POST variable for authentication + // Name of HTTP POST variable for authentication define("CSRFP_TOKEN","CSRFP-Token"); // We insert token name and list of url patterns for which @@ -19,13 +19,13 @@ define("CSRFP_FIELD_TOKEN_NAME", "csrfp_hidden_data_token"); define("CSRFP_FIELD_URLS", "csrfp_hidden_data_urls"); - /** - * child exception classes - */ + /** Indicates configuration file was not found. */ class configFileNotFoundException extends \exception {}; - class jsFileNotFoundException extends \exception {}; - class baseJSFileNotFoundExceptio extends \exception {}; + + /** Indicates that configuration file is incomplete. */ class incompleteConfigurationException extends \exception {}; + + /** Indicates that CSRF Protector is already initialized. */ class alreadyInitializedException extends \exception {}; class csrfProtector @@ -76,13 +76,17 @@ class csrfProtector * Variable: $config * config file for CSRFProtector * @var int Array, length = 6 - * Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails - * Property: #2: logDirectory (string) => directory in which log will be saved - * Property: #3: customErrorMessage (string) => custom error message to be sent in case - * of failed authentication - * Property: #4: jsFile (string) => location of the CSRFProtector js file - * Property: #5: tokenLength (int) => default length of hash - * Property: #6: disabledJavascriptMessage (string) => error message if client's js is disabled + * Property: #1: failedAuthAction (int) => action to be taken in case + * autherisation fails. + * Property: #3: customErrorMessage (string) => custom error message to + * be sent in case of failed authentication. + * Property: #4: jsFile (string) => location of the CSRFProtector js + * file. + * Property: #5: tokenLength (int) => default length of hash. + * Property: #6: disabledJavascriptMessage (string) => error message if + * client's js is disabled. + * + * TODO(mebjas): this field should be private */ public static $config = array(); @@ -90,16 +94,20 @@ class csrfProtector * Variable: $requiredConfigurations * Contains list of those parameters that are required to be there * in config file for csrfp to work + * + * TODO(mebjas): this field should be private */ - public static $requiredConfigurations = array('logDirectory', 'failedAuthAction', 'jsUrl', 'tokenLength'); + public static $requiredConfigurations = array( + 'failedAuthAction', 'jsUrl', 'tokenLength'); /* * Function: function to initialise the csrfProtector work flow * * Parameters: - * $length - length of CSRF_AUTH_TOKEN to be generated - * $action - int array, for different actions to be taken in case of failed validation - * $logger - custom logger class object + * $length - (int) length of CSRF_AUTH_TOKEN to be generated. + * $action - (int array), for different actions to be taken in case of + * failed validation. + * $logger - (LoggerInterface) custom logger class object. * * Returns: * void @@ -179,11 +187,11 @@ public static function init($length = null, $action = null, $logger = null) implode(', ', $missingConfiguration) . ' value(s)'); } - // Iniialize the logger class + // Initialize the logger class if ($logger !== null) { self::$logger = $logger; } else { - self::$logger = new csrfpDefaultLogger(self::$config['logDirectory']); + self::$logger = new csrfpDefaultLogger(); } // Authorise the incoming request @@ -212,9 +220,8 @@ public static function init($length = null, $action = null, $logger = null) * * Returns: * void - * - * Throws: - * logDirectoryNotFoundException - if log directory is not found + * + * TODO(mebjas): this method should be private. */ public static function authorizePost() { @@ -539,7 +546,8 @@ protected static function logCSRFattack() $context['REQUEST_URI'] = $_SERVER['REQUEST_URI']; $context['requestType'] = self::$requestType; $context['cookie'] = $_COOKIE; - self::$logger->log("OWASP CSRF PROTECTOR VALIDATION FAILURE", $context); + self::$logger->log( + "OWASP CSRF PROTECTOR VALIDATION FAILURE", $context); } /* diff --git a/log/.htaccess b/log/.htaccess deleted file mode 100644 index 3418e55..0000000 --- a/log/.htaccess +++ /dev/null @@ -1 +0,0 @@ -deny from all \ No newline at end of file diff --git a/log/index.php b/log/index.php deleted file mode 100644 index c512e09..0000000 --- a/log/index.php +++ /dev/null @@ -1,7 +0,0 @@ -CSRF protector php, a standalone php library for csrf mitigation in web applications. Easy to integrate in any php web app. -Add to your project using packagist -========== +# Add to your project using packagist Add a `composer.json` file to your project directory ```json { @@ -16,61 +15,60 @@ Add to your project using packagist Then open terminal (or command prompt), move to project directory and run ```shell composer install -``` -OR -``` + +## Or alternatively + php composer.phar install ``` -This will add CSRFP (library will be downloaded at ./vendor/owasp/csrf-protector-php) to your project directory. View [packagist.org](https://packagist.org/) for more help with composer! +This will add CSRFP (library will be downloaded at `./vendor/owasp/csrf-protector-php`) to your project directory. View [packagist.org](https://packagist.org/) for more help with composer! -Configuration -========== +# Configuration For composer installations: Copy the config.sample.php file into your root folder at config/csrf_config.php For non-composer installations: Copy the `libs/csrf/config.sample.php` file into `libs/csrf/config.php` Edit config accordingly. See Detailed Information link below. [Link to wiki - Editing Configurations & Mandatory requirements before using this library](https://github.com/mebjas/CSRF-Protector-PHP/wiki/Configurations) -How to use -========== +# How to use ```php fork > send a pull request to `master branch`. + - The best way to start is by picking up one of the existing [issues with `Up For Grab` label](https://github.com/mebjas/CSRF-Protector-PHP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22). + - Leave a comment, that you intend to help on this > then fork > and then send a pull request to `master branch`. -### FAQ: +## FAQ: 1. What happens if token expires? - https://github.com/mebjas/CSRF-Protector-PHP/wiki/what-if-token-expires -2. Secure flag in cookie? - https://github.com/mebjas/CSRF-Protector-PHP/issues/54 +2. Secure flag in a cookie? - https://github.com/mebjas/CSRF-Protector-PHP/issues/54 3. NoJS support? - https://github.com/mebjas/CSRF-Protector-PHP/tree/nojs-support + +## Appendix + +### JS not supported? +This version (in `master` branch) requires the clients to have Javascript enabled. However if your application can work without javascript & you require a nojs version of this library, check our nojs version \ No newline at end of file diff --git a/test/config.test.php b/test/config.test.php index 180a526..0936284 100644 --- a/test/config.test.php +++ b/test/config.test.php @@ -2,14 +2,12 @@ /** * Configuration file for CSRF Protector * Necessary configurations are (library would throw exception otherwise) - * ---- logDirectory * ---- failedAuthAction * ---- jsUrl * ---- tokenLength */ return array( "CSRFP_TOKEN" => "CSRFP-Token", - "logDirectory" => "../log", "failedAuthAction" => array( "GET" => 0, "POST" => 0), @@ -23,8 +21,6 @@ "secure" => false, "expire" => '', ), - "disabledJavascriptMessage" => "This site attempts to protect users against - Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. - See details of your web browser for how to enable JavaScript.", - "verifyGetFor" => array() -); \ No newline at end of file + "disabledJavascriptMessage" => "sample error message", + "verifyGetFor" => array() +); diff --git a/test/config.testInit_incompleteConfigurationException.php b/test/config.testInit_incompleteConfigurationException.php index a21083d..86fd8a4 100644 --- a/test/config.testInit_incompleteConfigurationException.php +++ b/test/config.testInit_incompleteConfigurationException.php @@ -2,29 +2,20 @@ /** * Configuration file for CSRF Protector * Necessary configurations are (library would throw exception otherwise) - * ---- logDirectory * ---- failedAuthAction * ---- jsUrl * ---- tokenLength */ return array( "CSRFP_TOKEN" => "CSRFP-Token", -// "logDirectory" => "../log", -// "failedAuthAction" => array( -// "GET" => 0, -// "POST" => 0), "errorRedirectionPage" => "", "customErrorMessage" => "", -// "jsUrl" => "http://localhost/csrfp/js/csrfprotector.js", -// "tokenLength" => 10, "cookieConfig" => array( "path" => '', "domain" => '', "secure" => false, "expire" => '', ), - "disabledJavascriptMessage" => "This site attempts to protect users against - Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. - See details of your web browser for how to enable JavaScript.", - "verifyGetFor" => array() -); \ No newline at end of file + "disabledJavascriptMessage" => "sample error message", + "verifyGetFor" => array() +); diff --git a/test/config.testInit_withoutInjectedCSRFGuardScript.php b/test/config.testInit_withoutInjectedCSRFGuardScript.php index 045fea7..e531895 100644 --- a/test/config.testInit_withoutInjectedCSRFGuardScript.php +++ b/test/config.testInit_withoutInjectedCSRFGuardScript.php @@ -2,14 +2,12 @@ /** * Configuration file for CSRF Protector * Necessary configurations are (library would throw exception otherwise) - * ---- logDirectory * ---- failedAuthAction * ---- jsUrl * ---- tokenLength */ return array( "CSRFP_TOKEN" => "CSRFP-Token", - "logDirectory" => "../log", "failedAuthAction" => array( "GET" => 0, "POST" => 0), @@ -23,8 +21,6 @@ "secure" => false, "expire" => '', ), - "disabledJavascriptMessage" => "This site attempts to protect users against - Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. - See details of your web browser for how to enable JavaScript.", - "verifyGetFor" => array() -); \ No newline at end of file + "disabledJavascriptMessage" => "sample error message", + "verifyGetFor" => array() +); diff --git a/test/csrfprotector_test.php b/test/csrfprotector_test.php index 5461141..61d3b73 100644 --- a/test/csrfprotector_test.php +++ b/test/csrfprotector_test.php @@ -1,104 +1,32 @@ = 7 && !class_exists('\PHPUnit_Framework_TestCase', true)) { +if (intval(phpversion('tidy')) >= 7 + && !class_exists('\PHPUnit_Framework_TestCase', true)) { class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase'); } -/** - * Wrapper class for testing purpose - */ -class csrfp_wrapper extends csrfprotector -{ - /** - * Function to provide wrapper method to set the protected var, requestType - * @param string $type - */ - public static function changeRequestType($type) - { - self::$requestType = $type; - } - - /** - * Function to check for a string value anywhere within HTTP response headers - * Returns true on first match of $needle in header names or values - * @param string $needle - * @return bool - */ - public static function checkHeader($needle) - { - $haystack = xdebug_get_headers(); - foreach ($haystack as $key => $value) { - if (strpos($value, $needle) !== false) - return true; - } - return false; - } - - /** - * Function to return the string value of the last response header - * identified by name $needle - * @param string $needle - * @return string - */ - public static function getHeaderValue($needle) - { - $haystack = xdebug_get_headers(); - foreach ($haystack as $key => $value) { - if (strpos($value, $needle) === 0) { - // Deliberately overwrite to accept the last rather than first match - // as xdebug_get_headers() will accumulate all set headers - list(,$hvalue) = explode(':', $value, 2); - } - } - return $hvalue; - } -} - -/** - * helper methods - */ -class Helper { - /** - * Function to recursively delete a dir - */ - public static function delTree($dir) { - $files = array_diff(scandir($dir), array('.','..')); - foreach ($files as $file) { - (is_dir("$dir/$file")) ? self::delTree("$dir/$file") : unlink("$dir/$file"); - } - return rmdir($dir); - } -} - - /** * main test class */ -class csrfp_test extends PHPUnit_Framework_TestCase -{ +class csrfp_test extends PHPUnit_Framework_TestCase { /** * @var array to hold current configurations */ protected $config = array(); - /** - * @var string log directory for testing - */ - private $logDir; - /** * Function to be run before every test*() functions. */ - public function setUp() - { - $this->logDir = __DIR__ .'/logs'; - + public function setUp() { csrfprotector::$config['CSRFP_TOKEN'] = 'CSRFP-Token'; csrfprotector::$config['cookieConfig'] = array('secure' => false); - csrfprotector::$config['logDirectory'] = '../test/logs'; $_SERVER['REQUEST_URI'] = 'temp'; // For logging $_SERVER['REQUEST_SCHEME'] = 'http'; // For authorizePost @@ -124,28 +52,27 @@ public function setUp() /** * tearDown() */ - public function tearDown() - { + public function tearDown() { unlink(__DIR__ .'/../libs/config.php'); - if (is_dir(__DIR__ .'/logs')) - Helper::delTree(__DIR__ .'/logs'); } /** * Function to check refreshToken() functionality */ - public function testRefreshToken() - { + public function testRefreshToken() { $val = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = '123abcd'; $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); csrfProtector::$config['tokenLength'] = 20; csrfProtector::refreshToken(); - $this->assertTrue(strcmp($val, $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1]) != 0); + $this->assertTrue( + strcmp($val, $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1]) != 0); - $this->assertTrue(csrfP_wrapper::checkHeader('Set-Cookie')); - $this->assertTrue(csrfP_wrapper::checkHeader('CSRFP-Token')); - $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1])); + $this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie')); + $this->assertTrue(csrfp_wrapper::checkHeader('CSRFP-Token')); + $this->assertTrue( + csrfp_wrapper::checkHeader( + $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1])); } /** @@ -189,8 +116,7 @@ public function testCookieConfigClass() { /** * test secure flag is set in the token cookie when requested */ - public function testSecureCookie() - { + public function testSecureCookie() { $_SERVER['REQUEST_METHOD'] = 'POST'; $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); csrfProtector::$config['tokenLength'] = 20; @@ -216,8 +142,7 @@ public function testSecureCookie() /** * test secure flag is set in the token cookie when requested */ - public function testCookieExpireTime() - { + public function testCookieExpireTime() { $_SERVER['REQUEST_METHOD'] = 'POST'; $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); csrfProtector::$config['tokenLength'] = 20; @@ -242,11 +167,9 @@ public function testCookieExpireTime() /** * test authorise post -> action = 403, forbidden */ - public function testAuthorisePost_failedAction_1() - { + public function testAuthorisePost_failedAction_1() { $_SERVER['REQUEST_METHOD'] = 'POST'; csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); - csrfprotector::$config['logDirectory'] = '../log'; csrfprotector::$config['failedAuthAction']['POST'] = 0; csrfprotector::$config['failedAuthAction']['GET'] = 0; @@ -263,15 +186,10 @@ public function testAuthorisePost_failedAction_1() /** * test authorise post -> strip $_GET, $_POST */ - public function testAuthorisePost_failedAction_2() - { + public function testAuthorisePost_failedAction_2() { $_SERVER['REQUEST_METHOD'] = 'POST'; $csrfp = new csrfProtector; - $reflection = new \ReflectionClass(get_class($csrfp)); - $property = $reflection->getProperty('logger'); - $property->setAccessible(true); - // change value to false - $property->setValue($csrfp, new csrfpDefaultLogger('../log')); + $fakeLogger = $this->setFakeLogger($csrfp); csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); csrfprotector::$config['failedAuthAction']['POST'] = 1; @@ -292,11 +210,9 @@ public function testAuthorisePost_failedAction_2() /** * test authorise post -> redirect */ - public function testAuthorisePost_failedAction_3() - { + public function testAuthorisePost_failedAction_3() { $_SERVER['REQUEST_METHOD'] = 'POST'; - csrfprotector::$config['logDirectory'] = '../log'; csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); csrfprotector::$config['errorRedirectionPage'] = 'http://test'; csrfprotector::$config['failedAuthAction']['POST'] = 2; @@ -314,11 +230,9 @@ public function testAuthorisePost_failedAction_3() /** * test authorise post -> error message & exit */ - public function testAuthorisePost_failedAction_4() - { + public function testAuthorisePost_failedAction_4() { $_SERVER['REQUEST_METHOD'] = 'POST'; - csrfprotector::$config['logDirectory'] = '../log'; csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); csrfprotector::$config['customErrorMessage'] = 'custom error message'; csrfprotector::$config['failedAuthAction']['POST'] = 3; @@ -336,11 +250,9 @@ public function testAuthorisePost_failedAction_4() /** * test authorise post -> 500 internal server error */ - public function testAuthorisePost_failedAction_5() - { + public function testAuthorisePost_failedAction_5() { $_SERVER['REQUEST_METHOD'] = 'POST'; - csrfprotector::$config['logDirectory'] = '../log'; csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); csrfprotector::$config['failedAuthAction']['POST'] = 4; csrfprotector::$config['failedAuthAction']['GET'] = 4; @@ -358,15 +270,10 @@ public function testAuthorisePost_failedAction_5() /** * test authorise post -> default action: strip $_GET, $_POST */ - public function testAuthorisePost_failedAction_6() - { + public function testAuthorisePost_failedAction_6() { $_SERVER['REQUEST_METHOD'] = 'POST'; $csrfp = new csrfProtector; - $reflection = new \ReflectionClass(get_class($csrfp)); - $property = $reflection->getProperty('logger'); - $property->setAccessible(true); - // change value to false - $property->setValue($csrfp, new csrfpDefaultLogger('../log')); + $fakeLogger = $this->setFakeLogger($csrfp); csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); csrfprotector::$config['failedAuthAction']['POST'] = 10; @@ -387,8 +294,7 @@ public function testAuthorisePost_failedAction_6() /** * test authorise success with token in $_POST */ - public function testAuthorisePost_success() - { + public function testAuthorisePost_success() { $_SERVER['REQUEST_METHOD'] = 'POST'; $_POST[csrfprotector::$config['CSRFP_TOKEN']] = $_GET[csrfprotector::$config['CSRFP_TOKEN']] @@ -419,8 +325,7 @@ public function testAuthorisePost_success() /** * test authorise success with token in header */ - public function testAuthorisePost_success_2() - { + public function testAuthorisePost_success_2() { unset($_POST[csrfprotector::$config['CSRFP_TOKEN']]); $_SERVER['REQUEST_METHOD'] = 'POST'; $serverKey = 'HTTP_' .strtoupper(csrfprotector::$config['CSRFP_TOKEN']); @@ -447,8 +352,7 @@ public function testAuthorisePost_success_2() /** * test for generateAuthToken() */ - public function testGenerateAuthToken() - { + public function testGenerateAuthToken() { csrfprotector::$config['tokenLength'] = 20; $token1 = csrfprotector::generateAuthToken(); $token2 = csrfprotector::generateAuthToken(); @@ -466,8 +370,7 @@ public function testGenerateAuthToken() /** * test ob_handler_function */ - public function testob_handler() - { + public function testob_handler() { csrfprotector::$config['verifyGetFor'] = array(); csrfprotector::$config['disabledJavascriptMessage'] = 'test message'; csrfprotector::$config['jsUrl'] = 'http://localhost/test/csrf/js/csrfprotector.js'; @@ -495,8 +398,7 @@ public function testob_handler() /** * test ob_handler_function */ - public function testob_handler_withoutClosedBodyTag() - { + public function testob_handler_withoutClosedBodyTag() { csrfprotector::$config['verifyGetFor'] = array(); csrfprotector::$config['disabledJavascriptMessage'] = 'test message'; csrfprotector::$config['jsUrl'] = 'http://localhost/test/csrf/js/csrfprotector.js'; @@ -517,8 +419,7 @@ public function testob_handler_withoutClosedBodyTag() /** * test ob_handler_function for output filter */ - public function testob_handler_positioning() - { + public function testob_handler_positioning() { csrfprotector::$config['verifyGetFor'] = array(); csrfprotector::$config['disabledJavascriptMessage'] = 'test message'; csrfprotector::$config['jsUrl'] = 'http://localhost/test/csrf/js/csrfprotector.js'; @@ -540,8 +441,7 @@ public function testob_handler_positioning() /** * test ob_handler_function for output filter */ - public function testob_handler_withoutInjectedCSRFGuardScript() - { + public function testob_handler_withoutInjectedCSRFGuardScript() { csrfprotector::$config['verifyGetFor'] = array(); csrfprotector::$config['disabledJavascriptMessage'] = 'test message'; csrfprotector::$config['jsUrl'] = false; @@ -566,8 +466,7 @@ public function testob_handler_withoutInjectedCSRFGuardScript() /** * testing exception in logging function */ - public function testgetCurrentUrl() - { + public function testgetCurrentUrl() { $stub = new ReflectionClass('csrfprotector'); $method = $stub->getMethod('getCurrentUrl'); $method->setAccessible(true); @@ -589,94 +488,28 @@ public function testgetCurrentUrl() $_SERVER['REQUEST_SCHEME'] = $tmp_request_scheme; } - /** - * test log CSRF attack -> log directory exception - * @expectedException logDirectoryNotFoundException - */ - public function testlogCSRFattack_logDirException() - { - new csrfpDefaultLogger('unknown_location'); - } - - /** - * test log CSRF attack -> log file write error - * @expectedException logFileWriteError - */ - public function testlogCSRFattack_logFileError() - { - // Setting error reporting to E_ERROR and creating a directory with the same name as the log file will force - // fopen to return FALSE - $errorReportingLevel = error_reporting(E_ERROR); - - $logFilename = $this->logDir . "/" . date("m-20y") . ".log"; - if (!is_dir($logFilename)) mkdir($logFilename, 0777, true); - - try { - $logger = new csrfpDefaultLogger($this->logDir); - $logger->log("test"); - } catch (Exception $e) { - // Reset the error reporting level - error_reporting($errorReportingLevel); - throw $e; - } - } - - /** - * testing logging function - */ - public function testlogCSRFattack() - { - //// TODO: create log directory if not exists - if (!is_dir($this->logDir)) mkdir($this->logDir); - - $csrfp = new csrfProtector; - $reflection = new \ReflectionClass(get_class($csrfp)); - $property = $reflection->getProperty('logger'); - $property->setAccessible(true); - // change value to false - $property->setValue($csrfp, new csrfpDefaultLogger($this->logDir)); - - $stub = new ReflectionClass('csrfprotector'); - $method = $stub->getMethod('logCSRFattack'); - $method->setAccessible(true); - - - $method->invoke(null); - $this->assertFileExists($this->logDir . "/" . date("m-20y") . ".log"); - } - /** * testing logging function */ - public function testlogCSRFattack_withAbsoluteLogDirectory() - { - //// TODO: create log directory if not exists - if (!is_dir($this->logDir)) mkdir($this->logDir); - + public function testlogCSRFattack() { $csrfp = new csrfProtector; - $reflection = new \ReflectionClass(get_class($csrfp)); - $property = $reflection->getProperty('logger'); - $property->setAccessible(true); - // change value to false - $property->setValue($csrfp, new csrfpDefaultLogger($this->logDir)); + $fakeLogger = $this->setFakeLogger($csrfp); $stub = new ReflectionClass('csrfprotector'); $method = $stub->getMethod('logCSRFattack'); $method->setAccessible(true); - - csrfprotector::$config['logDirectory'] = realpath($this->logDir); - + $this->assertNull($fakeLogger->getLastMessageLogged()); $method->invoke(null); - $this->assertFileExists($this->logDir . "/" . date("m-20y") . ".log"); + $this->assertNotNull($fakeLogger->getLastMessageLogged()); } /** * Tests isUrlAllowed() function for various urls and configuration */ - public function testisURLallowed() - { - csrfprotector::$config['verifyGetFor'] = array('http://test/delete*', 'https://test/*'); + public function testisURLallowed() { + csrfprotector::$config['verifyGetFor'] + = array('http://test/delete*', 'https://test/*'); $_SERVER['PHP_SELF'] = '/nodelete.php'; $this->assertTrue(csrfprotector::isURLallowed()); @@ -706,8 +539,7 @@ public function testisURLallowed() /** * Test for exception thrown when env variable is set by mod_csrfprotector */ - public function testModCSRFPEnabledException() - { + public function testModCSRFPEnabledException() { putenv('mod_csrfp_enabled=true'); $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = 'abc'; $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('abc'); @@ -725,15 +557,14 @@ public function testModCSRFPEnabledException() /** * Test for exception thrown when init() method is called multiple times */ - public function testMultipleInitializeException() - { + public function testMultipleInitializeException() { csrfProtector::$config = array(); $this->assertTrue(count(csrfProtector::$config) == 0); $_SERVER['REQUEST_METHOD'] = 'GET'; csrfProtector::init(); - $this->assertTrue(count(csrfProtector::$config) == 10); + $this->assertTrue(count(csrfProtector::$config) == 9); try { csrfProtector::init(); $this->fail("alreadyInitializedException not raised"); @@ -743,15 +574,17 @@ public function testMultipleInitializeException() } catch (Exception $ex) { $this->fail("exception other than alreadyInitializedException failed"); } + + // cleanup + ob_end_clean(); } /** * Test for exception thrown when init() method is called with missing config items * @expectedException incompleteConfigurationException - * @expectedExceptionMessage OWASP CSRFProtector: Incomplete configuration file: missing logDirectory, failedAuthAction, jsUrl, tokenLength value(s) + * @expectedExceptionMessage OWASP CSRFProtector: Incomplete configuration file: missing failedAuthAction, jsUrl, tokenLength value(s) */ - public function testInit_incompleteConfigurationException() - { + public function testInit_incompleteConfigurationException() { // Create an instance of config file -- for testing $data = file_get_contents(__DIR__ .'/config.testInit_incompleteConfigurationException.php'); file_put_contents(__DIR__ .'/../libs/config.php', $data); @@ -765,8 +598,7 @@ public function testInit_incompleteConfigurationException() /** * Test for exception thrown when init() method is called multiple times */ - public function testInit_withoutInjectedCSRFGuardScript() - { + public function testInit_withoutInjectedCSRFGuardScript() { // Create an instance of config file -- for testing $data = file_get_contents(__DIR__ .'/config.testInit_withoutInjectedCSRFGuardScript.php'); file_put_contents(__DIR__ .'/../libs/config.php', $data); @@ -775,5 +607,19 @@ public function testInit_withoutInjectedCSRFGuardScript() $_SERVER['REQUEST_METHOD'] = 'GET'; csrfProtector::init(); + + // cleanup + ob_end_clean(); + } + + private function setFakeLogger($csrfp) { + $fakeLogger = new fakeLogger(); + + $reflection = new \ReflectionClass(get_class($csrfp)); + $property = $reflection->getProperty('logger'); + $property->setAccessible(true); + $property->setValue($csrfp, $fakeLogger); + + return $fakeLogger; } } diff --git a/test/csrfprotector_test_customlogger.php b/test/csrfprotector_test_customlogger.php index e82207b..2178f01 100644 --- a/test/csrfprotector_test_customlogger.php +++ b/test/csrfprotector_test_customlogger.php @@ -2,20 +2,12 @@ date_default_timezone_set('UTC'); require_once __DIR__ .'/../libs/csrf/csrfprotector.php'; require_once __DIR__ .'/../libs/csrf/LoggerInterface.php'; +require_once __DIR__ .'/fakeLogger.php'; if (intval(phpversion('tidy')) >= 7 && !class_exists('\PHPUnit_Framework_TestCase', true)) { class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase'); } -/** - * Custom logger class, prints to screen - */ -class testConsoleLogger implements LoggerInterface { - public function log($message, $context = array()) { - echo $message .PHP_EOL; - } -} - class csrfp_test_customLogger extends PHPUnit_Framework_TestCase { /** @@ -51,24 +43,30 @@ public function setUp() { /** * tearDown() */ - public function tearDown() - { + public function tearDown() { unlink(__DIR__ .'/../libs/config.php'); - if (is_dir(__DIR__ .'/logs')) - Helper::delTree(__DIR__ .'/logs'); + ob_end_flush(); } - /** - * To test a custom logger class - */ - public function testCustomLogger() { + public function testCustomLogger_doesntThrowException() { $_SERVER['REQUEST_METHOD'] = 'POST'; $tmp = csrfProtector::$config; csrfProtector::$config = array(); - csrfProtector::init(null, array('POST' => 1), new testConsoleLogger()); + csrfProtector::init(null, array('POST' => 1), new fakeLogger()); $this->assertTrue(true); - csrfProtector::$config = $tmp; } -} \ No newline at end of file + + public function testCustomLogger_onLogAttack_loggerIsCalled() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $fakeLogger = new fakeLogger(); + $this->assertNull($fakeLogger->getLastMessageLogged()); + + $tmp = csrfProtector::$config; + csrfProtector::$config = array(); + csrfProtector::init(null, array('POST' => 1), $fakeLogger); + + $this->assertNotNull($fakeLogger->getLastMessageLogged()); + } +} diff --git a/test/fakeLogger.php b/test/fakeLogger.php new file mode 100644 index 0000000..9d1c080 --- /dev/null +++ b/test/fakeLogger.php @@ -0,0 +1,29 @@ +lastMessage; + } + + public function getLastContextLogged() { + return $this->lastContext; + } + + public function log($message, $context = array()) { + $this->lastMessage = $message; + $this->lastContext = $context; + } + } +} diff --git a/test/testHelpers.php b/test/testHelpers.php new file mode 100644 index 0000000..4686bfe --- /dev/null +++ b/test/testHelpers.php @@ -0,0 +1,52 @@ + $value) { + if (strpos($value, $needle) !== false) + return true; + } + return false; + } + + /** + * Function to return the string value of the last response header + * identified by name $needle + * @param string $needle + * @return string + */ + public static function getHeaderValue($needle) { + $haystack = xdebug_get_headers(); + foreach ($haystack as $key => $value) { + if (strpos($value, $needle) === 0) { + // Deliberately overwrite to accept the last rather than first match + // as xdebug_get_headers() will accumulate all set headers + list(,$hvalue) = explode(':', $value, 2); + } + } + return $hvalue; + } + } +} +?>