| @@ -82,8 +82,49 @@ | ||
| 82 | 82 | if ( is_wp_error( $attachment ) ) { |
| 83 | 83 | throw new Upload_Exception( esc_html( $attachment->get_error_message() ) ); |
| 84 | 84 | } |
| 85 | 85 | |
| 86 | + /** | |
| 87 | + * Records who actually uploaded this attachment, so a later request | |
| 88 | + * can't take credit for someone else's upload just because it also | |
| 89 | + * carries the `_jfb_uploaded_by_form` marker. | |
| 90 | + * | |
| 91 | + * A target post ID is deliberately NOT recorded here: at upload time | |
| 92 | + * (field/request parsing) no post has been authorized yet - the Insert | |
| 93 | + * Post action's `ID` property (which would map to a real post) is only | |
| 94 | + * resolved and capability-checked later, when that action itself runs. | |
| 95 | + * Binding to a not-yet-authorized `pid` value from the same request | |
| 96 | + * would just persist the attacker-controlled value the field-level | |
| 97 | + * check exists to distrust. | |
| 98 | + * | |
| 99 | + * Written BEFORE `_jfb_uploaded_by_form` (below), not after: the latter | |
| 100 | + * is the trust gate `Media_Cleanup::is_owned_by_current_actor()` reads | |
| 101 | + * to decide an attachment is plugin-owned at all. If this request were | |
| 102 | + * interrupted between the two writes, an attachment left with the | |
| 103 | + * trust gate set but no uploader identity would fall into that check's | |
| 104 | + * unconditional-allow "legacy, predates the marker system" branch - | |
| 105 | + * recreating the same unrestricted-trust primitive this fix exists to | |
| 106 | + * close. Writing the identity first means an interruption instead | |
| 107 | + * leaves the attachment with neither marker: not yet plugin-owned by | |
| 108 | + * anyone, exactly like an ordinary, unrelated attachment. | |
| 109 | + * | |
| 110 | + * Order alone is not enough, though: `update_post_meta()` is not | |
| 111 | + * guaranteed to persist just because it was called - a plugin hooked | |
| 112 | + * on the `update_post_meta`/`add_post_meta` filters (WP core lets any | |
| 113 | + * of them short-circuit the write by returning non-null) could silently | |
| 114 | + * swallow the identity write while the second call still succeeds. The | |
| 115 | + * explicit `metadata_exists()` check below confirms the identity is | |
| 116 | + * actually there before the trust gate is published; if it is not, | |
| 117 | + * this attachment is left exactly as an ordinary, non-plugin-owned one | |
| 118 | + * (no `_jfb_uploaded_by_form` either), rather than risk a trust gate | |
| 119 | + * with nothing behind it. | |
| 120 | + */ | |
| 121 | + update_post_meta( $attachment, '_jfb_uploaded_by_user', get_current_user_id() ); | |
| 122 | + | |
| 123 | + if ( metadata_exists( 'post', $attachment, '_jfb_uploaded_by_user' ) ) { | |
| 124 | + update_post_meta( $attachment, '_jfb_uploaded_by_form', 1 ); | |
| 125 | + } | |
| 126 | + | |
| 86 | 127 | wp_update_attachment_metadata( |
| 87 | 128 | $attachment, |
| 88 | 129 | wp_generate_attachment_metadata( $attachment, $this->get_file() ) |
| 89 | 130 | ); |