Add nonce functionality

This commit is contained in:
Flavio Copes
2015-11-06 15:31:49 +01:00
parent fcf48ed2e5
commit e1fdb6803d
3 changed files with 135 additions and 2 deletions

View File

@@ -94,14 +94,17 @@ class TwigExtension extends \Twig_Extension
new \Twig_simpleFunction('authorize', [$this, 'authorize']),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
];
}
@@ -595,4 +598,24 @@ class TwigExtension extends \Twig_Extension
return false;
}
/**
* Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action.
*
* For maximum protection, ensure that the string representing the action is as specific as possible.
*
* @todo evaluate if adding referrer or not
*
* @param string action the action
* @param string nonceParamName a custom nonce param name
*
* @return string the nonce input field
*/
public function nonceFieldFunc($action, $nonceParamName = 'nonce')
{
$string = '<input type="hidden" id="' . $nonceParamName . '" name="' . $nonceParamName . '" value="' . Utils::getNonce($action) .'" />';
// $string += '<input type="hidden" name="_grav_http_referer" value="/admin/pages/test-page" />';
return $string;
}
}

View File

@@ -572,4 +572,20 @@ class Uri
return $normalized_url;
}
}
/**
* Adds the nonce to a URL for a specific action
*
* @param string $url the url
* @param string $action the action
* @param string $nonceParamName the param name to use
*
* @return string the url with the nonce
*/
public static function addNonce($url, $action, $nonceParamName = 'nonce')
{
$nonce = Utils::getNonce($action);
$urlWithNonce = $url . '/' . $nonceParamName . self::getGrav()['config']->get('system.param_sep', ':') . $nonce;
return $urlWithNonce;
}
}

View File

@@ -384,7 +384,101 @@ abstract class Utils
*
* @return boolean
*/
public static function isPositive($value) {
public static function isPositive($value)
{
return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true);
}
/**
* Generates a nonce string to be hashed. Called by self::getNonce()
*
* @param string $action
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
*
* @return string the nonce string
*/
private static function generateNonceString($action, $plusOneTick)
{
$user = self::getGrav()['user'];
$username = $user->username;
if ( ! $username ) {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
}
return ( $i . '|' . $action . '|' . $username . '|' . $token );
}
/**
* Get the time-dependent variable for nonce creation.
*
* @todo now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way
* to ensure nonces issued near the end of the day do not expire in that small amount of time
*
* @return int the time part of the nonce. Changes once every 24 hours
*/
private static function nonceTick()
{
$secondsInHalfADay = 60 * 60 * 12;
return (int)ceil(time() / ( $secondsInHalfADay ));
}
/**
* Get hash of given string. Uses BCrypt. The salt is taken from system.security.default_hash
*
* @param string $data string to hash
*
* @return string hashed value of $data, cut to 10 characters
*/
private static function hash($data)
{
$hash = password_hash($data, PASSWORD_BCRYPT, ['salt' => self::getGrav()['config']->get('system.security.default_hash')]);
$hash = substr($hash, -12, 10);
return $hash;
}
/**
* Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
* action is the same for 12 hours.
*
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
*
* @return string the nonce, a 10 characters string
*/
public static function getNonce($action, $plusOneTick = false)
{
$nonce = self::hash(self::generateNonceString($action, $plusOneTick));
return $nonce;
}
/**
* Verify the passed nonce for the give action
*
* @param string $nonce the nonce to verify
* @param string $action the action to verify the nonce to
*
* @return boolean verified or not
*/
public static function verifyNonce($nonce, $action)
{
if ($nonce == self::getNonce($action)) {
return true;
}
$plusOneTick = true;
if ($nonce == self::getNonce($action, $plusOneTick)) {
return true;
}
return false;
}
}