diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php
index 2da411210..97723ce40 100644
--- a/system/src/Grav/Common/Twig/TwigExtension.php
+++ b/system/src/Grav/Common/Twig/TwigExtension.php
@@ -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 = '';
+ // $string += '';
+ return $string;
+
+ }
}
diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php
index 0473d3938..cf9ec4793 100644
--- a/system/src/Grav/Common/Uri.php
+++ b/system/src/Grav/Common/Uri.php
@@ -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;
+ }
}
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index 8c55669a2..ae4479903 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -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;
+ }
}