Plugin Directory


Ignore:
Timestamp:
10/10/2026 01:03:33 AM (7 days ago)
Author:
TigrouMeow
Message:

Version 6.5.7.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • wplr-sync/trunk/classes/api.php

    r3714439 r3737477  
    4040                // Upload
    4141                else if ( isset( $_POST['action'] ) && $_POST['action'] === 'sync' ) {
    42                         if ( !$user = $this->auth( $_POST['token'] ) ) {
     42                        if ( !$user = $this->auth( isset( $_POST['token'] ) ? wp_unslash( $_POST['token'] ) : null ) ) {
    4343                                $this->response( null, false, $this->error );
    4444                                $this->finalize();
    … …  
    5151                        $body = file_get_contents('php://input');
    5252                        $args = json_decode( $body );
    53                         if ( !$user = $this->auth( $args->token ) ) {
     53                        if ( !is_object( $args ) || !isset( $args->action ) ) {
     54                                $this->response( null, false, "Invalid request." );
     55                                $this->finalize();
     56                        }
     57                        if ( !$user = $this->auth( isset( $args->token ) ? $args->token : null ) ) {
    5458                                $this->response( null, false, $this->error );
    5559                                $this->finalize();
    5660                        }
    5761                        $wplr->log( "Action: " . $args->action );
    58                         if ( !$user = $this->auth( $args->token ) )
    59                                 $this->response( null, false, $this->error );
    60                         else if ( $args->action === 'presync' )
     62                        if ( $args->action === 'presync' )
    6163                                $this->presync( $args );
    6264                        else if ( $args->action === 'sync_collection' )
    … …  
    117119                global $wplr, $wpdb;
    118120
    119 
    120                 if ( empty( $token ) ) {
     121                // Failed attempts are counted per IP: a token can't be guessed by hammering the endpoint.
     122                $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
     123                $fail_key = 'wplr_api_fail_' . md5( $ip );
     124                $fails = (int) get_transient( $fail_key );
     125                if ( $fails >= 20 ) {
     126                        $this->error = "Too many failed attempts. Please try again in 15 minutes.";
     127                        $wplr->log( $this->error );
     128                        return false;
     129                }
     130
     131                if ( !is_string( $token ) || strlen( $token ) < 12 || strlen( $token ) > 128 ) {
    121132                        $this->error = "Authentification is missing.";
    122133                        $wplr->log( $this->error );
     134                        set_transient( $fail_key, $fails + 1, 15 * MINUTE_IN_SECONDS );
    123135                        return false;
    124136                }
    125                
    126                 //* DEBUG: allow to authenticate as admin with a hardcoded token (for testing purposes only)
    127                 // if ( $token == "MEOW_TOKEN" ) {
    128 
    129                 //      $this->user = get_userdata( 1 );
    130                 //      if ( empty( $this->user ) ) {
    131                 //              $this->error = "User admin does not exist.";
    132                 //              $wplr->log( $this->error );
    133                 //              return false;
    134                 //      }
    135 
    136                 //      $wplr->log( "Authenticated as admin" );
    137                 //      return $this->user;
    138                 // }
    139137
    140138                $prefix = defined( 'BLOG_ID_CURRENT_SITE' ) ? $wpdb->get_blog_prefix( BLOG_ID_CURRENT_SITE ) : $wpdb->prefix;
    141139                $tbl_meta = defined('CUSTOM_USER_META_TABLE' ) ? CUSTOM_USER_META_TABLE : $prefix . "usermeta";
    142                 $userId = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $tbl_meta
     140                $row = $wpdb->get_row( $wpdb->prepare( "SELECT user_id, meta_value FROM $tbl_meta
    143141                        WHERE meta_value = %s AND meta_key = %s", $token, 'wplr_auth_token' ) );
    144                 if ( empty( $userId ) ) {
    145                         $this->error = "Token '$token' seems to be invalid.";
     142                // The SQL match can be case-insensitive (collation): the token must match exactly.
     143                if ( empty( $row ) || !is_string( $row->meta_value ) || !hash_equals( $row->meta_value, $token ) ) {
     144                        $this->error = "This token seems to be invalid.";
    146145                        $wplr->log( $this->error );
     146                        set_transient( $fail_key, $fails + 1, 15 * MINUTE_IN_SECONDS );
    147147                        return false;
    148148                }
    149                 $this->user = get_userdata( $userId );
     149                $this->user = get_userdata( $row->user_id );
    150150                if ( empty( $this->user ) ) {
    151151                        $this->error = 'User does not exist.';
    … …  
    153153                        return false;
    154154                }
     155
     156                // Act as this user, so every capability check below applies to them.
     157                wp_set_current_user( $this->user->ID );
     158                if ( !current_user_can( 'upload_files' ) ) {
     159                        $this->error = 'This user is not allowed to upload files.';
     160                        $wplr->log( $this->error );
     161                        $this->user = null;
     162                        return false;
     163                }
    155164                $wplr->log( "Authenticated as " . $this->user->user_login );
    156165                return $this->user;
     166        }
     167
     168        // The attachment(s) behind a Lightroom ID must be editable (or deletable) by the authenticated user.
     169        function can_on_lr_id( $lr_id, $cap ) {
     170                global $wplr;
     171                foreach ( (array) $wplr->list_wpids( (int) $lr_id ) as $row ) {
     172                        if ( !empty( $row->wp_id ) && !current_user_can( $cap, (int) $row->wp_id ) ) {
     173                                return false;
     174                        }
     175                }
     176                return true;
     177        }
     178
     179        function can_on_wp_id( $wp_id, $cap ) {
     180                $wp_id = (int) $wp_id;
     181                return $wp_id > 0 && get_post_type( $wp_id ) === 'attachment' && current_user_can( $cap, $wp_id );
    157182        }
    158183
    … …  
    237262                $file = $_FILES['file']['tmp_name'];
    238263
     264                // Syncing over an existing photo replaces its file: the user must be allowed to edit it.
     265                if ( !$this->can_on_lr_id( $args["id"], 'edit_post' ) ) {
     266                        return $this->response( null, false, 'You are not allowed to edit this media.' );
     267                }
     268
    239269                if ( !isset( $args["wp_col_id"] ) || $args["wp_col_id"] == null )
    240270                        $args["wp_col_id"] = -1;
    … …  
    301331                global $wplr;
    302332                $res = false;
     333                // Deleting a collection also deletes its photos, which may belong to anyone.
     334                if ( !current_user_can( 'delete_others_posts' ) ) {
     335                        return $this->response( null, false, 'You are not allowed to delete collections.' );
     336                }
    303337                if ( property_exists( $args, 'remoteId' ) )
    304338                        $res = $wplr->delete_collection( $args->remoteId );
    … …  
    312346                if ( property_exists( $args, 'collection_id' ) )
    313347                        $collectionId = $args->collection_id;
    314                 $list = $wplr->delete_media( $args->lr_id, $collectionId );
     348                if ( !property_exists( $args, 'lr_id' ) || !$this->can_on_lr_id( $args->lr_id, 'delete_post' ) ) {
     349                        return $this->response( null, false, 'You are not allowed to delete this media.' );
     350                }
     351                $list = $wplr->delete_media( (int) $args->lr_id, $collectionId );
    315352                return $this->response( $list );
    316353        }
    … …  
    335372        function unlink( $args ) {
    336373                global $wplr;
    337                 $res = $wplr->unlink_media( $args->lr_id, $args->wp_id );
     374                $wp_id = property_exists( $args, 'wp_id' ) ? (int) $args->wp_id : 0;
     375                $lr_id = property_exists( $args, 'lr_id' ) ? (int) $args->lr_id : 0;
     376                if ( $wp_id ? !$this->can_on_wp_id( $wp_id, 'edit_post' ) : !$this->can_on_lr_id( $lr_id, 'edit_post' ) ) {
     377                        return $this->response( null, false, 'You are not allowed to edit this media.' );
     378                }
     379                $res = $wplr->unlink_media( $lr_id, $wp_id );
    338380                return $this->response( $res );
    339381        }
    … …  
    342384        function link( $args ) {
    343385                global $wplr;
    344                 $res = $wplr->link_media( $args->lr_id, $args->wp_id );
     386                // Linking decides which attachment a later sync or delete will touch.
     387                $wp_id = property_exists( $args, 'wp_id' ) ? (int) $args->wp_id : 0;
     388                if ( !$this->can_on_wp_id( $wp_id, 'edit_post' ) ) {
     389                        return $this->response( null, false, 'You are not allowed to link this media.' );
     390                }
     391                $res = $wplr->link_media( (int) $args->lr_id, $wp_id );
    345392                return $this->response( $res );
    346393        }
    … …  
    357404        function linkinfo( $args ) {
    358405                global $wplr;
    359                 $linkinfo = $wplr->linkinfo_media( $args->wp_id );
     406                if ( !$this->can_on_wp_id( property_exists( $args, 'wp_id' ) ? $args->wp_id : 0, 'edit_post' ) ) {
     407                        return $this->response( null, false, 'You are not allowed to access this media.' );
     408                }
     409                $linkinfo = $wplr->linkinfo_media( (int) $args->wp_id );
    360410                return $this->response( $linkinfo );
    361411        }
    … …  
    365415                global $wplr;
    366416                $wp_ids = isset( $args->wp_ids ) ? (array)$args->wp_ids : array();
     417                $wp_ids = array_values( array_filter( array_map( 'intval', $wp_ids ), function ( $id ) {
     418                        return $this->can_on_wp_id( $id, 'edit_post' );
     419                } ) );
    367420                $list = $wplr->linkinfo_bulk( $wp_ids );
    368421                return $this->response( $list );
Note: See TracChangeset for help on using the changeset viewer.