Completed
Push — kraftbj-patch-2 ( f1a4bb...ba1272 )
by
unknown
25:04 queued 15:38
created

Nonce_Handler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * The nonce handler.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
namespace Automattic\Jetpack\Connection;
9
10
/**
11
 * The nonce handler.
12
 */
13
class Nonce_Handler {
14
15
	/**
16
	 * How long the scheduled cleanup can run (in seconds).
17
	 * Can be modified using the filter `jetpack_connection_nonce_scheduled_cleanup_limit`.
18
	 */
19
	const SCHEDULED_CLEANUP_TIME_LIMIT = 5;
20
21
	/**
22
	 * How many nonces should be removed per batch during the `clean_all()` run.
23
	 */
24
	const CLEAN_ALL_LIMIT_PER_BATCH = 1000;
25
26
	/**
27
	 * Nonce lifetime in seconds.
28
	 */
29
	const LIFETIME = HOUR_IN_SECONDS;
30
31
	/**
32
	 * The nonces used during the request are stored here to keep them valid.
33
	 * The property is static to keep the nonces accessible between the `Nonce_Handler` instances.
34
	 *
35
	 * @var array
36
	 */
37
	private static $nonces_used_this_request = array();
38
39
	/**
40
	 * The database object.
41
	 *
42
	 * @var \wpdb
43
	 */
44
	private $db;
45
46
	/**
47
	 * Initializing the object.
48
	 */
49
	public function __construct() {
50
		global $wpdb;
51
52
		$this->db = $wpdb;
53
	}
54
55
	/**
56
	 * Scheduling the WP-cron cleanup event.
57
	 */
58
	public function init_schedule() {
59
		add_action( 'jetpack_clean_nonces', array( __CLASS__, 'clean_scheduled' ) );
60
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
61
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
62
		}
63
	}
64
65
	/**
66
	 * Reschedule the WP-cron cleanup event to make it start sooner.
67
	 */
68
	public function reschedule() {
69
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
70
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
71
	}
72
73
	/**
74
	 * Adds a used nonce to a list of known nonces.
75
	 *
76
	 * @param int    $timestamp the current request timestamp.
77
	 * @param string $nonce the nonce value.
78
	 *
79
	 * @return bool whether the nonce is unique or not.
80
	 */
81
	public function add( $timestamp, $nonce ) {
82
		if ( isset( static::$nonces_used_this_request[ "$timestamp:$nonce" ] ) ) {
0 ignored issues
show
Bug introduced by
Since $nonces_used_this_request is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $nonces_used_this_request to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
83
			return static::$nonces_used_this_request[ "$timestamp:$nonce" ];
0 ignored issues
show
Bug introduced by
Since $nonces_used_this_request is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $nonces_used_this_request to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
84
		}
85
86
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp and $nonce.
87
		$timestamp = (int) $timestamp;
88
		$nonce     = esc_sql( $nonce );
89
90
		// Raw query so we can avoid races: add_option will also update.
91
		$show_errors = $this->db->hide_errors();
92
93
		// Running `try...finally` to make sure that we re-enable errors in case of an exception.
94
		try {
95
			$old_nonce = $this->db->get_row(
96
				$this->db->prepare( "SELECT 1 FROM `{$this->db->options}` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
97
			);
98
99
			if ( is_null( $old_nonce ) ) {
100
				$return = (bool) $this->db->query(
101
					$this->db->prepare(
102
						"INSERT INTO `{$this->db->options}` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
103
						"jetpack_nonce_{$timestamp}_{$nonce}",
104
						time(),
105
						'no'
106
					)
107
				);
108
			} else {
109
				$return = false;
110
			}
111
		} finally {
112
			$this->db->show_errors( $show_errors );
113
		}
114
115
		static::$nonces_used_this_request[ "$timestamp:$nonce" ] = $return;
0 ignored issues
show
Bug introduced by
Since $nonces_used_this_request is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $nonces_used_this_request to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
116
117
		return $return;
118
	}
119
120
	/**
121
	 * Removing all existing nonces, or at least as many as possible.
122
	 * Capped at 20 seconds to avoid breaking the site.
123
	 *
124
	 * @param int $cutoff_timestamp All nonces added before this timestamp will be removed.
125
	 * @param int $time_limit How long the cleanup can run (in seconds).
126
	 *
127
	 * @return true
128
	 */
129
	public function clean_all( $cutoff_timestamp = PHP_INT_MAX, $time_limit = 20 ) {
130
		// phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
131
		for ( $end_time = time() + $time_limit; time() < $end_time; ) {
132
			$result = $this->delete( static::CLEAN_ALL_LIMIT_PER_BATCH, $cutoff_timestamp );
133
134
			if ( ! $result ) {
135
				break;
136
			}
137
		}
138
139
		return true;
140
	}
141
142
	/**
143
	 * Scheduled clean up of the expired nonces.
144
	 */
145
	public static function clean_scheduled() {
146
		/**
147
		 * Adjust the time limit for the scheduled cleanup.
148
		 *
149
		 * @since 9.5.0
150
		 *
151
		 * @param int $time_limit How long the cleanup can run (in seconds).
152
		 */
153
		$time_limit = apply_filters( 'jetpack_connection_nonce_cleanup_runtime_limit', static::SCHEDULED_CLEANUP_TIME_LIMIT );
154
155
		( new static() )->clean_all( time() - static::LIFETIME, $time_limit );
156
	}
157
158
	/**
159
	 * Delete the nonces.
160
	 *
161
	 * @param int      $limit How many nonces to delete.
162
	 * @param null|int $cutoff_timestamp All nonces added before this timestamp will be removed.
163
	 *
164
	 * @return int|false Number of removed nonces, or `false` if nothing to remove (or in case of a database error).
165
	 */
166
	public function delete( $limit = 10, $cutoff_timestamp = null ) {
167
		global $wpdb;
168
169
		$ids = $wpdb->get_col(
170
			$wpdb->prepare(
171
				"SELECT option_id FROM `{$wpdb->options}`"
172
				. " WHERE `option_name` >= 'jetpack_nonce_' AND `option_name` < %s"
173
				. ' LIMIT %d',
174
				'jetpack_nonce_' . $cutoff_timestamp,
175
				$limit
176
			)
177
		);
178
179
		if ( ! is_array( $ids ) || ! count( $ids ) ) {
180
			// There's either nothing to remove, or there's an error and we can't proceed.
181
			return false;
182
		}
183
184
		$ids_fill = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
185
186
		// The Code Sniffer is unable to understand what's going on...
187
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
188
		return $wpdb->query( $wpdb->prepare( "DELETE FROM `{$wpdb->options}` WHERE `option_id` IN ( {$ids_fill} )", $ids ) );
189
	}
190
191
	/**
192
	 * Clean the cached nonces valid during the current request, therefore making them invalid.
193
	 *
194
	 * @return bool
195
	 */
196
	public static function invalidate_request_nonces() {
197
		static::$nonces_used_this_request = array();
0 ignored issues
show
Bug introduced by
Since $nonces_used_this_request is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $nonces_used_this_request to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
198
199
		return true;
200
	}
201
202
}
203