PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | includes/rest-api/controller/version2/class-mainwp-rest-monitors-controller.php +63 -3 6.1.8 → 6.2.2 View file →
@@ -883,8 +883,16 @@
883 883 */
884 884 public function update_global_monitoring_settings( $request ) {
885 885 // Get request body.
886 886 $body = $request->get_json_params();
887 +
888 + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below
889 + // fatals on the scalar, and the list would intersect down to nothing and answer as a
890 + // successful no-op, so both are treated as no body at all.
891 + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) {
892 + $body = array();
893 + }
894 +
887 895 if ( empty( $body ) ) {
888 896 return new WP_Error(
889 897 'empty_body',
890 898 __( 'Request body is empty.', 'mainwp' ),
@@ -900,10 +908,27 @@
900 908 __( 'Invalid content type. Expected application/json.', 'mainwp' ),
901 909 );
902 910 }
903 911
912 + // Reject a key the route does not declare. get_sanitized_settings_params() drops it, so a
913 + // misspelled setting would otherwise reach the processor as an empty settings array and
914 + // answer as a successful no-op. The individual route reports the same mistake through its
915 + // schema, so it gets the same error code here.
916 + $unknown_keys = array_diff( array_keys( $body ), array_keys( $this->get_global_monitor_settings_allowed_fields() ) );
917 + if ( ! empty( $unknown_keys ) ) {
918 + return new WP_Error(
919 + 'rest_additional_properties_forbidden',
920 + sprintf(
921 + /* translators: %s: comma separated list of request body keys the route does not accept. */
922 + __( 'Unknown settings: %s.', 'mainwp' ),
923 + implode( ', ', $unknown_keys )
924 + ),
925 + array( 'status' => 400 )
926 + );
927 + }
928 +
904 929 // Process global monitoring settings update.
905 - $result = $this->process_global_monitoring_settings_update( $body );
930 + $result = $this->process_global_monitoring_settings_update( $this->get_sanitized_settings_params( $request, $body ) );
906 931 if ( is_wp_error( $result ) ) {
907 932 return $result;
908 933 }
909 934
@@ -938,12 +963,20 @@
938 963
939 964 // Get request body.
940 965 $body = $request->get_json_params();
941 966
967 + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below
968 + // fatals on the scalar, and the list would intersect down to nothing and answer as a
969 + // successful no-op, so both are treated as no body at all.
970 + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) {
971 + $body = array();
972 + }
973 +
942 974 if ( empty( $body ) ) {
943 975 return new WP_Error(
944 976 'empty_body',
945 977 __( 'Request body is empty.', 'mainwp' ),
978 + array( 'status' => 400 )
946 979 );
947 980 }
948 981
949 982 // Check content type.
@@ -954,17 +987,22 @@
954 987 __( 'Invalid content type. Expected application/json.', 'mainwp' ),
955 988 );
956 989 }
957 990
958 - // Validate request body against schema.
991 + // Validate request body against schema. The schema has to see the raw body: the sanitized
992 + // params keep only the keys the route registered, so a misspelled key would be filtered out
993 + // before additionalProperties could reject it.
959 994 $schema = $this->get_monitor_settings_schema(); // Get default schema.
960 995 $valid = rest_validate_value_from_schema( $body, $schema, 'body' );
961 996 if ( is_wp_error( $valid ) ) {
997 + // A schema error carries no status of its own and REST falls back to 500 for one that
998 + // has none, which is the wrong answer for a body the caller got wrong.
999 + $valid->add_data( array( 'status' => 400 ) );
962 1000 return $valid;
963 1001 }
964 1002
965 1003 // Sanitize request body.
966 - $data = rest_sanitize_value_from_schema( $body, $schema );
1004 + $data = rest_sanitize_value_from_schema( $this->get_sanitized_settings_params( $request, $body ), $schema );
967 1005
968 1006 // Process individual monitor settings update.
969 1007 $result = $this->process_individual_monitor_settings_update( $monitor, $data );
970 1008
@@ -1054,8 +1092,30 @@
1054 1092 'uptime_status' => $check_result ? 'up' : 'down',
1055 1093 ),
1056 1094 )
1057 1095 );
1096 + }
1097 +
1098 + /**
1099 + * Get the settings the request carries, as the registered callbacks sanitized them.
1100 + *
1101 + * @param WP_REST_Request $request Full details about the request.
1102 + * @param array $body Raw request body.
1103 + *
1104 + * @return array Sanitized settings, keyed by the keys the body carried.
1105 + */
1106 + private function get_sanitized_settings_params( $request, $body ) {
1107 + // The registered sanitize_callbacks produce the request params, so the persisted value is
1108 + // read from there rather than from the raw body; WordPress currently writes the sanitized
1109 + // value back into the JSON body too, but that is internal to sanitize_params(), not a
1110 + // contract. get_params() also carries auth, query and route params, so keep only the
1111 + // settings keys the route declares and only the ones this body actually sent. It merges in
1112 + // the reverse of get_parameter_order(), so a JSON body value beats a query parameter of the
1113 + // same name rather than the other way round.
1114 + $allowed = $this->get_global_monitor_settings_allowed_fields();
1115 + $params = array_intersect_key( $request->get_params(), $allowed );
1116 +
1117 + return array_intersect_key( $params, $body );
1058 1118 }
1059 1119
1060 1120 /**
1061 1121 * Process global monitoring settings update with all business logic.