| @@ -147,28 +147,80 @@ | ||
| 147 | 147 | return ( ! empty( $this->src() ) && ! is_wp_error( $this->src() ) ); |
| 148 | 148 | } |
| 149 | 149 | |
| 150 | 150 | /** |
| 151 | + * Whether this source allows the `restricted: false` opt-out (see | |
| 152 | + * has_permission()) to skip can_get_preset() at all. A source can override | |
| 153 | + * this to return false when its capability must never be made public by a | |
| 154 | + * form's stored, trusted preset configuration. | |
| 155 | + * | |
| 156 | + * Preset_Source_Options_Page intentionally uses the default: a form author | |
| 157 | + * can explicitly publish the selected Options Page field by switching | |
| 158 | + * "Restrict access" off. Request-provided preset data cannot do so because | |
| 159 | + * it never receives the trusted-origin marker. See issues-tracker #20359. | |
| 160 | + * | |
| 151 | 161 | * @return bool |
| 162 | + */ | |
| 163 | + protected function allows_restriction_bypass(): bool { | |
| 164 | + return true; | |
| 165 | + } | |
| 166 | + | |
| 167 | + /** | |
| 168 | + * The `restricted: false` opt-out below only takes effect when both: | |
| 169 | + * - `_trusts_restriction_flag` is true - a flag set programmatically by | |
| 170 | + * Base_Preset::get_source() (never taken from preset_data itself, so | |
| 171 | + * an attacker can't forge it) from whatever the CALL SITE declared via | |
| 172 | + * Base_Preset::trust_restriction_flag(). Only origins that are | |
| 173 | + * admin-authored declare trust: the form's own preset meta | |
| 174 | + * (General_Preset), a field block's attributes | |
| 175 | + * (Preset_Manager::get_field_value()) and the settings parsed via | |
| 176 | + * jet_fb_parse_dynamic_trusted() (validation rules, date min/max, | |
| 177 | + * conditional blocks, action conditions, dynamic value). Parsing a | |
| 178 | + * submitted field value - jet_fb_parse_dynamic() via Rich_Content - | |
| 179 | + * never does, so a `restricted` flag smuggled through a submitted | |
| 180 | + * value is ignored there. | |
| 181 | + * - allows_restriction_bypass() is true for this source (see above). | |
| 182 | + * | |
| 183 | + * This closes the access-control bypass reported in issues-tracker | |
| 184 | + * #20359 while preserving the documented "Restrict access" editor | |
| 185 | + * toggle for admin-configured presets. | |
| 186 | + * | |
| 187 | + * `jet-form-builder/preset-sanitize` runs on BOTH paths - the opt-out | |
| 188 | + * result as well as can_get_preset() - so an integrator can always | |
| 189 | + * further restrict access, including on presets whose author switched | |
| 190 | + * the toggle off. Returning true from it does not grant access that | |
| 191 | + * can_get_preset() denied on the checked path, because the filter only | |
| 192 | + * ever sees an already-computed decision. | |
| 193 | + * | |
| 194 | + * @return bool | |
| 152 | 195 | * @throws Preset_Exception |
| 153 | 196 | */ |
| 154 | 197 | protected function has_permission(): bool { |
| 155 | - if ( | |
| 156 | - // not enabled programmatically | |
| 157 | - empty( $this->preset_data['_check_restriction'] ) || | |
| 158 | - // disabled in preset-editor | |
| 159 | - ( | |
| 160 | - array_key_exists( 'restricted', $this->preset_data ) && | |
| 161 | - ! $this->preset_data['restricted'] | |
| 162 | - ) | |
| 163 | - ) { | |
| 164 | - return true; | |
| 165 | - } | |
| 166 | 198 | if ( is_null( $this->permission ) ) { |
| 167 | - $this->permission = apply_filters( 'jet-form-builder/preset-sanitize', $this->can_get_preset(), $this ); | |
| 199 | + $this->permission = apply_filters( | |
| 200 | + 'jet-form-builder/preset-sanitize', | |
| 201 | + $this->allows_restriction_bypass_for_data() ? true : $this->can_get_preset(), | |
| 202 | + $this | |
| 203 | + ); | |
| 168 | 204 | } |
| 169 | 205 | |
| 170 | 206 | return $this->permission; |
| 207 | + } | |
| 208 | + | |
| 209 | + /** | |
| 210 | + * Whether the `restricted: false` opt-out applies to this preset data. | |
| 211 | + * | |
| 212 | + * All four conditions must hold, see has_permission() above. | |
| 213 | + * | |
| 214 | + * @return bool | |
| 215 | + */ | |
| 216 | + private function allows_restriction_bypass_for_data(): bool { | |
| 217 | + return ( | |
| 218 | + ! empty( $this->preset_data['_trusts_restriction_flag'] ) && | |
| 219 | + $this->allows_restriction_bypass() && | |
| 220 | + array_key_exists( 'restricted', $this->preset_data ) && | |
| 221 | + ! $this->preset_data['restricted'] | |
| 222 | + ); | |
| 171 | 223 | } |
| 172 | 224 | |
| 173 | 225 | /** |
| 174 | 226 | * @throws Preset_Exception |