PageRenderTime 153ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Nonce.php

https://github.com/CodeYellowBV/piwik
PHP | 175 lines | 78 code | 21 blank | 76 comment | 18 complexity | 51ad98856fbf6bd1ea1624528ed9669d MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Piwik\Session\SessionNamespace;
  11. /**
  12. * Nonce class.
  13. *
  14. * A cryptographic nonce -- "number used only once" -- is often recommended as
  15. * part of a robust defense against cross-site request forgery (CSRF/XSRF). This
  16. * class provides static methods that create and manage nonce values.
  17. *
  18. * Nonces in Piwik are stored as a session variable and have a configurable expiration.
  19. *
  20. * Learn more about nonces [here](http://en.wikipedia.org/wiki/Cryptographic_nonce).
  21. *
  22. * @api
  23. */
  24. class Nonce
  25. {
  26. /**
  27. * Returns an existing nonce by ID. If none exists, a new nonce will be generated.
  28. *
  29. * @param string $id Unique id to avoid namespace conflicts, e.g., `'ModuleName.ActionName'`.
  30. * @param int $ttl Optional time-to-live in seconds; default is 5 minutes. (ie, in 5 minutes,
  31. * the nonce will no longer be valid).
  32. * @return string
  33. */
  34. static public function getNonce($id, $ttl = 600)
  35. {
  36. // save session-dependent nonce
  37. $ns = new SessionNamespace($id);
  38. $nonce = $ns->nonce;
  39. // re-use an unexpired nonce (a small deviation from the "used only once" principle, so long as we do not reset the expiration)
  40. // to handle browser pre-fetch or double fetch caused by some browser add-ons/extensions
  41. if (empty($nonce)) {
  42. // generate a new nonce
  43. $nonce = md5(SettingsPiwik::getSalt() . time() . Common::generateUniqId());
  44. $ns->nonce = $nonce;
  45. }
  46. // extend lifetime if nonce is requested again to prevent from early timeout if nonce is requested again
  47. // a few seconds before timeout
  48. $ns->setExpirationSeconds($ttl, 'nonce');
  49. return $nonce;
  50. }
  51. /**
  52. * Returns if a nonce is valid and comes from a valid request.
  53. *
  54. * A nonce is valid if it matches the current nonce and if the current nonce
  55. * has not expired.
  56. *
  57. * The request is valid if the referrer is a local URL (see {@link Url::isLocalUrl()})
  58. * and if the HTTP origin is valid (see {@link getAcceptableOrigins()}).
  59. *
  60. * @param string $id The nonce's unique ID. See {@link getNonce()}.
  61. * @param string $cnonce Nonce sent from client.
  62. * @return bool `true` if valid; `false` otherwise.
  63. */
  64. static public function verifyNonce($id, $cnonce)
  65. {
  66. $ns = new SessionNamespace($id);
  67. $nonce = $ns->nonce;
  68. // validate token
  69. if (empty($cnonce) || $cnonce !== $nonce) {
  70. return false;
  71. }
  72. // validate referrer
  73. $referrer = Url::getReferrer();
  74. if (!empty($referrer) && !Url::isLocalUrl($referrer)) {
  75. return false;
  76. }
  77. // validate origin
  78. $origin = self::getOrigin();
  79. if (!empty($origin) &&
  80. ($origin == 'null'
  81. || !in_array($origin, self::getAcceptableOrigins()))
  82. ) {
  83. return false;
  84. }
  85. return true;
  86. }
  87. /**
  88. * Force expiration of the current nonce.
  89. *
  90. * @param string $id The unique nonce ID.
  91. */
  92. static public function discardNonce($id)
  93. {
  94. $ns = new SessionNamespace($id);
  95. $ns->unsetAll();
  96. }
  97. /**
  98. * Returns the **Origin** HTTP header or `false` if not found.
  99. *
  100. * @return string|bool
  101. */
  102. static public function getOrigin()
  103. {
  104. if (!empty($_SERVER['HTTP_ORIGIN'])) {
  105. return $_SERVER['HTTP_ORIGIN'];
  106. }
  107. return false;
  108. }
  109. /**
  110. * Returns a list acceptable values for the HTTP **Origin** header.
  111. *
  112. * @return array
  113. */
  114. static public function getAcceptableOrigins()
  115. {
  116. $host = Url::getCurrentHost(null);
  117. $port = '';
  118. // parse host:port
  119. if (preg_match('/^([^:]+):([0-9]+)$/D', $host, $matches)) {
  120. $host = $matches[1];
  121. $port = $matches[2];
  122. }
  123. if (empty($host)) {
  124. return array();
  125. }
  126. // standard ports
  127. $origins[] = 'http://' . $host;
  128. $origins[] = 'https://' . $host;
  129. // non-standard ports
  130. if (!empty($port) && $port != 80 && $port != 443) {
  131. $origins[] = 'http://' . $host . ':' . $port;
  132. $origins[] = 'https://' . $host . ':' . $port;
  133. }
  134. return $origins;
  135. }
  136. /**
  137. * Verifies and discards a nonce.
  138. *
  139. * @param string $nonceName The nonce's unique ID. See {@link getNonce()}.
  140. * @param string|null $nonce The nonce from the client. If `null`, the value from the
  141. * **nonce** query parameter is used.
  142. * @throws Exception if the nonce is invalid. See {@link verifyNonce()}.
  143. */
  144. static public function checkNonce($nonceName, $nonce = null)
  145. {
  146. if ($nonce === null) {
  147. $nonce = Common::getRequestVar('nonce', null, 'string');
  148. }
  149. if (!self::verifyNonce($nonceName, $nonce)) {
  150. throw new \Exception(Piwik::translate('General_ExceptionNonceMismatch'));
  151. }
  152. self::discardNonce($nonceName);
  153. }
  154. }