PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/submit-token.php +39 -11 2.11.1 → 2.12.8 View file →
@@ -54,8 +54,31 @@
54 54 */
55 55 public const DEFAULT_ACCEPTED_WINDOWS = 4;
56 56
57 57 /**
58 + * Namespace for tokens that authorise a form submission.
59 + *
60 + * The value is the historical payload prefix, so existing tokens keep verifying
61 + * across an upgrade — changing it would reject every token already embedded in
62 + * cached HTML.
63 + *
64 + * @since 2.12.6
65 + */
66 + public const NAMESPACE_SUBMIT = 'srfm_submit';
67 +
68 + /**
69 + * Namespace for tokens that authorise the page-view beacon.
70 + *
71 + * Separate from NAMESPACE_SUBMIT so the two cannot stand in for each other: a
72 + * view token scraped from the page must not authorise a submission, and the
73 + * view endpoint must not double as an oracle for whether a submit token is
74 + * still inside an accepted window.
75 + *
76 + * @since 2.12.6
77 + */
78 + public const NAMESPACE_VIEW = 'srfm_view';
79 +
80 + /**
58 81 * Generate a submission token for a given form.
59 82 *
60 83 * The token encodes the form ID and the current half-day window, signed
61 84 * with the site's auth salt. It is safe to embed in cached HTML because
@@ -61,13 +84,15 @@
61 84 * with the site's auth salt. It is safe to embed in cached HTML because
62 85 * `verify()` accepts several consecutive past windows.
63 86 *
64 87 * @since 2.6.0
65 - * @param int $form_id The form post ID.
88 + * @since 2.12.6 Added the $namespace parameter.
89 + * @param int $form_id The form post ID.
90 + * @param string $namespace Purpose the token is minted for. Defaults to form submission.
66 91 * @return string 64-character lowercase hex HMAC-SHA256 token.
67 92 */
68 - public static function generate( int $form_id ): string {
69 - return self::sign( $form_id, self::current_window() );
93 + public static function generate( int $form_id, string $namespace = self::NAMESPACE_SUBMIT ): string {
94 + return self::sign( $form_id, self::current_window(), $namespace );
70 95 }
71 96
72 97 /**
73 98 * Verify a token submitted with a form.
@@ -75,13 +100,15 @@
75 100 * Checks the token against every accepted window, from newest to oldest,
76 101 * using constant-time comparison throughout.
77 102 *
78 103 * @since 2.6.0
79 - * @param string $token Token value received from the client.
80 - * @param int $form_id Form post ID extracted from the request body.
104 + * @since 2.12.6 Added the $namespace parameter.
105 + * @param string $token Token value received from the client.
106 + * @param int $form_id Form post ID extracted from the request body.
107 + * @param string $namespace Purpose the token must have been minted for.
81 108 * @return bool True if the token is valid for the given form, false otherwise.
82 109 */
83 - public static function verify( string $token, int $form_id ): bool {
110 + public static function verify( string $token, int $form_id, string $namespace = self::NAMESPACE_SUBMIT ): bool {
84 111 if ( '' === $token || $form_id <= 0 ) {
85 112 return false;
86 113 }
87 114
@@ -90,9 +117,9 @@
90 117 $accepted = max( 1, min( 14, (int) apply_filters( 'srfm_submit_token_accepted_windows', self::DEFAULT_ACCEPTED_WINDOWS ) ) );
91 118
92 119 // Walk backwards through accepted windows; current window first.
93 120 for ( $offset = 0; $offset < $accepted; $offset++ ) {
94 - if ( hash_equals( self::sign( $form_id, self::current_window() - $offset ), $token ) ) {
121 + if ( hash_equals( self::sign( $form_id, self::current_window() - $offset, $namespace ), $token ) ) {
95 122 return true;
96 123 }
97 124 }
98 125
@@ -119,18 +146,19 @@
119 146 * the window index so that tokens cannot be repurposed across forms or
120 147 * replayed across time windows.
121 148 *
122 149 * @since 2.6.0
123 - * @param int $form_id Post ID of the form.
124 - * @param int $window Half-day window index.
150 + * @param int $form_id Post ID of the form.
151 + * @param int $window Half-day window index.
152 + * @param string $namespace Purpose prefix; keeps tokens for one action from authorising another.
125 153 * @return string 64-character lowercase hex digest.
126 154 */
127 - private static function sign( int $form_id, int $window ): string {
155 + private static function sign( int $form_id, int $window, string $namespace = self::NAMESPACE_SUBMIT ): string {
128 156 // Derive a plugin-specific sub-key from the site's auth salt so this
129 157 // system has an independent key surface from WordPress session cookies.
130 158 // Rotating wp-config.php secrets invalidates all outstanding tokens, which
131 159 // is intentional — a cache purge should follow any secret key rotation.
132 160 $signing_key = hash_hmac( 'sha256', 'srfm-submit-token-v1', wp_salt( 'auth' ) );
133 - $payload = implode( '|', [ 'srfm_submit', $form_id, $window ] );
161 + $payload = implode( '|', [ $namespace, $form_id, $window ] );
134 162 return hash_hmac( 'sha256', $payload, $signing_key );
135 163 }
136 164 }