From 4d2965cf72fb9975e010ae68537ae01c62e97047 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 18 Aug 2019 16:21:40 +0200 Subject: [PATCH] api: Update QueryFilearchive to provide information to everyone Wikimedia Commons needs the ability to quickly detect, given a SHA-1, if a file has been previously uploaded but was deleted later. This is currently not possible in an efficient manner because the fa_sha1 field of the public database replica is not indexed, and this API requires the 'deletedhistory' user right. Effectively removes the 'deletedhistory' requirement, as this API does not expose more information than the public toolforge database replica. Bug : T60993 Change-Id: I2e9e1d50b6db9fa17acaf14d0975b6e9145a411e --- RELEASE-NOTES-1.34 | 2 + includes/api/ApiQueryFilearchive.php | 55 ++++++++++++++++------------ includes/api/i18n/en.json | 2 + includes/api/i18n/qqq.json | 2 + 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/RELEASE-NOTES-1.34 b/RELEASE-NOTES-1.34 index 229fda407c..3ba9537f0a 100644 --- a/RELEASE-NOTES-1.34 +++ b/RELEASE-NOTES-1.34 @@ -153,6 +153,8 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false; === Action API changes in 1.34 === * The 'recenteditcount' response property from action=query list=allusers, deprecated in 1.25, has been removed. +* (T60993) action=query list=filearchive no longer requires the 'deletedhistory' + user right. === Action API internal changes in 1.34 === * … diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php index f9087eb143..c84f457918 100644 --- a/includes/api/ApiQueryFilearchive.php +++ b/includes/api/ApiQueryFilearchive.php @@ -38,9 +38,6 @@ class ApiQueryFilearchive extends ApiQueryBase { } public function execute() { - // Before doing anything at all, let's check permissions - $this->checkUserRightsAny( 'deletedhistory' ); - $user = $this->getUser(); $db = $this->getDB(); $commentStore = CommentStore::getStore(); @@ -60,6 +57,17 @@ class ApiQueryFilearchive extends ApiQueryBase { $fld_bitdepth = isset( $prop['bitdepth'] ); $fld_archivename = isset( $prop['archivename'] ); + if ( $fld_description && + !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) + ) { + $this->dieWithError( 'apierror-cantview-deleted-description', 'permissiondenied' ); + } + if ( $fld_metadata && + !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' ) + ) { + $this->dieWithError( 'apierror-cantview-deleted-metadata', 'permissiondenied' ); + } + $fileQuery = ArchivedFile::getQueryInfo(); $this->addTables( $fileQuery['tables'] ); $this->addFields( $fileQuery['fields'] ); @@ -110,23 +118,22 @@ class ApiQueryFilearchive extends ApiQueryBase { } if ( $sha1 ) { $this->addWhereFld( 'fa_sha1', $sha1 ); + // Paranoia: avoid brute force searches (T19342) + if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) { + $bitmask = File::DELETED_FILE; + } elseif ( !$this->getPermissionManager() + ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) + ) { + $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED; + } else { + $bitmask = 0; + } + if ( $bitmask ) { + $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" ); + } } } - // Exclude files this user can't view. - if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) { - $bitmask = File::DELETED_FILE; - } elseif ( !$this->getPermissionManager() - ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) - ) { - $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED; - } else { - $bitmask = 0; - } - if ( $bitmask ) { - $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" ); - } - $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' ); @@ -150,6 +157,8 @@ class ApiQueryFilearchive extends ApiQueryBase { break; } + $canViewFile = RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_FILE, $user ); + $file = []; $file['id'] = (int)$row->fa_id; $file['name'] = $row->fa_name; @@ -171,13 +180,13 @@ class ApiQueryFilearchive extends ApiQueryBase { $file['userid'] = (int)$row->fa_user; $file['user'] = $row->fa_user_text; } - if ( $fld_sha1 ) { + if ( $fld_sha1 && $canViewFile ) { $file['sha1'] = Wikimedia\base_convert( $row->fa_sha1, 36, 16, 40 ); } if ( $fld_timestamp ) { $file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp ); } - if ( $fld_size || $fld_dimensions ) { + if ( ( $fld_size || $fld_dimensions ) && $canViewFile ) { $file['size'] = $row->fa_size; $pageCount = ArchivedFile::newFromRow( $row )->pageCount(); @@ -188,18 +197,18 @@ class ApiQueryFilearchive extends ApiQueryBase { $file['height'] = $row->fa_height; $file['width'] = $row->fa_width; } - if ( $fld_mediatype ) { + if ( $fld_mediatype && $canViewFile ) { $file['mediatype'] = $row->fa_media_type; } - if ( $fld_metadata ) { + if ( $fld_metadata && $canViewFile ) { $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result ) : null; } - if ( $fld_bitdepth ) { + if ( $fld_bitdepth && $canViewFile ) { $file['bitdepth'] = $row->fa_bits; } - if ( $fld_mime ) { + if ( $fld_mime && $canViewFile ) { $file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime"; } if ( $fld_archivename && !is_null( $row->fa_archive_name ) ) { diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 8b42a07e8e..35e45f1005 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -1727,6 +1727,8 @@ "apierror-cantoverwrite-sharedfile": "The target file exists on a shared repository and you do not have permission to override it.", "apierror-cantsend": "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email.", "apierror-cantundelete": "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already.", + "apierror-cantview-deleted-description": "You don't have permission to view descriptions of deleted files.", + "apierror-cantview-deleted-metadata": "You don't have permission to view metadata of deleted files.", "apierror-changeauth-norequest": "Failed to create change request.", "apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.", "apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 87f056bb04..e796d25016 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -1615,6 +1615,8 @@ "apierror-cantoverwrite-sharedfile": "{{doc-apierror}}", "apierror-cantsend": "{{doc-apierror}}", "apierror-cantundelete": "{{doc-apierror}}", + "apierror-cantview-deleted-description": "{{doc-apierror}}", + "apierror-cantview-deleted-metadata": "{{doc-apierror}}", "apierror-changeauth-norequest": "{{doc-apierror}}", "apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.", "apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.", -- 2.20.1