"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "mrbs-1.9.4/web/lib/MRBS/Form/Form.php" between
mrbs-1.9.4.tar.gz and mrbs-1.10.0.tar.gz

About: MRBS is a web application for booking meeting rooms or other resources (using PHP and MySQL/pgsql).

Form.php  (mrbs-1.9.4):Form.php  (mrbs-1.10.0)
<?php <?php
namespace MRBS\Form; namespace MRBS\Form;
use MRBS\Exception;
use MRBS\JFactory; use MRBS\JFactory;
use MRBS\Session\SessionCookie; use MRBS\Session\SessionCookie;
use function MRBS\fatal_error;
use function MRBS\generate_token;
use function MRBS\get_form_var;
use function MRBS\get_vocab;
use function MRBS\session;
class Form extends Element class Form extends Element
{ {
private static $token = null; private static $token = null;
private static $token_name = 'csrf_token'; // As of PHP 7.1 this would be a p rivate const private static $token_name = 'csrf_token'; // As of PHP 7.1 this would be a p rivate const
private static $cookie_set = false; private static $cookie_set = false;
public function __construct() public function __construct()
{ {
parent::__construct('form'); parent::__construct('form');
$this->addCSRFToken(); $this->addCSRFToken();
} }
// Adds a hidden input to the form // Adds a hidden input to the form
public function addHiddenInput($name, $value) public function addHiddenInput($name, $value) : Form
{ {
$element = new ElementInputHidden(); $element = new ElementInputHidden();
$element->setAttributes(array('name' => $name, $element->setAttributes(array('name' => $name,
'value' => $value)); 'value' => $value));
$this->addElement($element); $this->addElement($element);
return $this; return $this;
} }
// Adds an array of hidden inputs to the form // Adds an array of hidden inputs to the form
public function addHiddenInputs(array $hidden_inputs) public function addHiddenInputs(array $hidden_inputs) : Form
{ {
foreach ($hidden_inputs as $key => $value) foreach ($hidden_inputs as $key => $value)
{ {
$this->addHiddenInput($key, $value); $this->addHiddenInput($key, $value);
} }
return $this; return $this;
} }
// Returns the HTML for a hidden field containing a CSRF token // Returns the HTML for a hidden field containing a CSRF token
public static function getTokenHTML() public static function getTokenHTML() : string
{ {
$element = new ElementInputHidden(); $element = new ElementInputHidden();
$element->setAttributes(array('name' => self::$token_name, $element->setAttributes(array('name' => self::$token_name,
'value' => self::getToken())); 'value' => self::getToken()));
return $element->toHTML(); return $element->toHTML();
} }
// Checks the CSRF token against the stored value and dies with a fatal error // Checks the CSRF token against the stored value and dies with a fatal error
// if they do not match. Note that: // if they do not match. Note that:
// (1) The CSRF token is always looked for in the POST data, never anywhere else. // (1) The CSRF token is always looked for in the POST data, never anywhere else.
skipping to change at line 63 skipping to change at line 69
// grant access. // grant access.
// (2) Forms should normally use a POST method. // (2) Forms should normally use a POST method.
// (3) Actions should normally be taken by handler pages which are not desi gned to be // (3) Actions should normally be taken by handler pages which are not desi gned to be
// accessed directly by the user and are only expecting POST requests. These pages // accessed directly by the user and are only expecting POST requests. These pages
// will look for the CSRF token however they are requested. If they ar e requested via // will look for the CSRF token however they are requested. If they ar e requested via
// GET then they will still look for the token in the POST data and so fail. // GET then they will still look for the token in the POST data and so fail.
// (4) There are some MRBS pages that can be accessed either via a URL with query string, // (4) There are some MRBS pages that can be accessed either via a URL with query string,
// or via a POST request. These pages should not take any action, but as a matter of // or via a POST request. These pages should not take any action, but as a matter of
// good practice should check the token anyway if they have been reques ted by a POST. // good practice should check the token anyway if they have been reques ted by a POST.
// To cater for these pages the $post_only parameter should be set to T RUE. // To cater for these pages the $post_only parameter should be set to T RUE.
public static function checkToken($post_only=false) public static function checkToken($post_only=false) : void
{ {
global $server; global $server;
if ($post_only && ($server['REQUEST_METHOD'] != 'POST')) if ($post_only && ($server['REQUEST_METHOD'] != 'POST'))
{ {
return; return;
} }
$token = \MRBS\get_form_var(self::$token_name, 'string', null, INPUT_POST); $token = get_form_var(self::$token_name, 'string', null, INPUT_POST);
$stored_token = self::getStoredToken(); $stored_token = self::getStoredToken();
if (!self::compareTokens($stored_token, $token)) if (!self::compareTokens($stored_token, $token))
{ {
if (isset($stored_token)) if (isset($stored_token))
{ {
// Only report a possible CSRF attack if the stored token exists. If i t doesn't // Only report a possible CSRF attack if the stored token exists. If i t doesn't
// it's normally because the user session has expired in between the for m being // it's normally because the user session has expired in between the for m being
// displayed and submitted. // displayed and submitted.
trigger_error('Possible CSRF attack from IP address ' . $server['REMOTE_ ADDR'], E_USER_NOTICE); trigger_error('Possible CSRF attack from IP address ' . $server['REMOTE_ ADDR'], E_USER_NOTICE);
} }
if (method_exists(\MRBS\session(), 'logoffUser')) if (method_exists(session(), 'logoffUser'))
{ {
\MRBS\session()->logoffUser(); session()->logoffUser();
} }
\MRBS\fatal_error(\MRBS\get_vocab("session_expired")); fatal_error(get_vocab("session_expired"));
} }
} }
// $max_unit can be set to 'seconds', 'minutes', 'hours', etc. and // $max_unit can be set to 'seconds', 'minutes', 'hours', etc. and
// can be used to specify the maximum unit to return. // can be used to specify the maximum unit to return.
public static function getTimeUnitOptions($max_unit=null) public static function getTimeUnitOptions($max_unit=null) : array
{ {
$options = array(); $options = array();
$units = array('seconds', 'minutes', 'hours', 'days', 'weeks'); $units = array('seconds', 'minutes', 'hours', 'days', 'weeks');
foreach ($units as $unit) foreach ($units as $unit)
{ {
$options[$unit] = \MRBS\get_vocab($unit); $options[$unit] = get_vocab($unit);
if (isset($max_unit) && ($max_unit == $unit)) if (isset($max_unit) && ($max_unit == $unit))
{ {
break; break;
} }
} }
return $options; return $options;
} }
private function addCSRFToken() private function addCSRFToken() : Form
{ {
$this->addHiddenInput(self::$token_name, self::getToken()); $this->addHiddenInput(self::$token_name, self::getToken());
return $this; return $this;
} }
// Get a CSRF token // Get a CSRF token
public static function getToken() public static function getToken() : string
{ {
$token_length = 32; $token_length = 32;
if (!isset(self::$token)) if (!isset(self::$token))
{ {
$stored_token = self::getStoredToken(); $stored_token = self::getStoredToken();
if (isset($stored_token)) // The test below should really be isset() rather than !empty(). However
occasionally MRBS has the
// value 0 stored in the session variable. It's not clear how or why this
is happening. Until the
// root cause is found we test for empty() and if the token is set but emp
ty we generate a new token.
if (!empty($stored_token))
{ {
self::$token = $stored_token; self::$token = $stored_token;
} }
else else
{ {
self::$token = \MRBS\generate_token($token_length); if (isset($stored_token))
{
// The token is set but empty
$message = "Stored token is '$stored_token'. This should not be possi
ble. " .
"Generating a new token.";
trigger_error($message,E_USER_WARNING);
}
self::$token = generate_token($token_length);
self::storeToken(self::$token); self::storeToken(self::$token);
} }
} }
return self::$token; return self::$token;
} }
// Compare two tokens in a timing attack safe manner. // Compare two tokens in a timing attack safe manner.
// Returns true if they are equal, otherwise false. // Returns true if they are equal, otherwise false.
// Note: it is important to provide the user-supplied string as the // Note: it is important to provide the user-supplied string as the
// second parameter, rather than the first. // second parameter, rather than the first.
private static function compareTokens($known_token, $user_token) private static function compareTokens($known_token, $user_token) : bool
{ {
if (is_null($known_token) || is_null($user_token)) if (is_null($known_token) || is_null($user_token))
{ {
return false; return false;
} }
if (function_exists('hash_equals')) if (function_exists('hash_equals'))
{ {
return hash_equals($known_token, $user_token); return hash_equals($known_token, $user_token);
} }
// Could do fancier things here to give a timing attack safe comparison, // Could do fancier things here to give a timing attack safe comparison,
// For example https://github.com/indigophp/hash-compat // For example https://github.com/indigophp/hash-compat
return ($known_token === $user_token); return ($known_token === $user_token);
} }
private static function storeToken($token) private static function storeToken($token) : void
{ {
global $auth, $csrf_cookie; global $auth, $csrf_cookie;
if ($auth['session'] == 'joomla') if ($auth['session'] == 'joomla')
{ {
// Joomla has its own session handling and will clear the $_SESSION variab le, // Joomla has its own session handling and will clear the $_SESSION variab le,
// so if we are using Joomla authentication we need to do sessions the Joo mla // so if we are using Joomla authentication we need to do sessions the Joo mla
// way. (Maybe MRBS should abstract session handling into a separate Ses sion // way. (Maybe MRBS should abstract session handling into a separate Ses sion
// class in due course? Note also that Joomla's JSession class has metho ds for // class in due course? Note also that Joomla's JSession class has metho ds for
// getting and checking form tokens, so maybe that's another way of doing it?) // getting and checking form tokens, so maybe that's another way of doing it?)
skipping to change at line 186 skipping to change at line 202
$session_status = session_status(); $session_status = session_status();
// Use PHP sessions if we can // Use PHP sessions if we can
if ($session_status !== PHP_SESSION_DISABLED) if ($session_status !== PHP_SESSION_DISABLED)
{ {
if ($session_status === PHP_SESSION_NONE) if ($session_status === PHP_SESSION_NONE)
{ {
if (false === session_start()) if (false === session_start())
{ {
throw new \Exception("Could not start session"); throw new Exception("Could not start session");
} }
} }
$_SESSION[self::$token_name] = $token; $_SESSION[self::$token_name] = $token;
return; return;
} }
// Otherwise use cookies // Otherwise use cookies
if (!self::$cookie_set) if (!self::$cookie_set)
{ {
SessionCookie::setCookie('MRBS_CSRF', SessionCookie::setCookie('MRBS_CSRF',
$csrf_cookie['hash_algorithm'], $csrf_cookie['hash_algorithm'],
$csrf_cookie['secret'], $csrf_cookie['secret'],
array(self::$token_name => $token), array(self::$token_name => $token),
0); //Always a session cookie 0); //Always a session cookie
self::$cookie_set = true; self::$cookie_set = true;
} }
} }
private static function getStoredToken() private static function getStoredToken() : ?string
{ {
global $auth, $csrf_cookie; global $auth, $csrf_cookie;
if ($auth['session'] == 'joomla') if ($auth['session'] == 'joomla')
{ {
$session = JFactory::getSession(); $session = JFactory::getSession();
return $session->get(self::$token_name); return $session->get(self::$token_name);
} }
$session_status = session_status(); $session_status = session_status();
// Use PHP sessions if we can // Use PHP sessions if we can
if ($session_status !== PHP_SESSION_DISABLED) if ($session_status !== PHP_SESSION_DISABLED)
{ {
if ($session_status === PHP_SESSION_NONE) if ($session_status === PHP_SESSION_NONE)
{ {
if (false === session_start()) if (false === session_start())
{ {
throw new \Exception("Could not start session"); throw new Exception("Could not start session");
} }
} }
return (isset($_SESSION[self::$token_name])) ? $_SESSION[self::$token_name ] : null; return (isset($_SESSION[self::$token_name])) ? $_SESSION[self::$token_name ] : null;
} }
// Otherwise use cookies // Otherwise use cookies
$data = SessionCookie::getCookie('MRBS_CSRF', $data = SessionCookie::getCookie('MRBS_CSRF',
$csrf_cookie['hash_algorithm'], $csrf_cookie['hash_algorithm'],
$csrf_cookie['secret']); $csrf_cookie['secret']);
 End of changes. 21 change blocks. 
19 lines changed or deleted 39 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)