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
=== Action API changes in 1.34 ===
* The 'recenteditcount' response property from action=query list=allusers,
deprecated in 1.25, has been removed.
=== 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 ===
* …
=== Action API internal changes in 1.34 ===
* …
}
public function execute() {
}
public function execute() {
- // Before doing anything at all, let's check permissions
- $this->checkUserRightsAny( 'deletedhistory' );
-
$user = $this->getUser();
$db = $this->getDB();
$commentStore = CommentStore::getStore();
$user = $this->getUser();
$db = $this->getDB();
$commentStore = CommentStore::getStore();
$fld_bitdepth = isset( $prop['bitdepth'] );
$fld_archivename = isset( $prop['archivename'] );
$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'] );
$fileQuery = ArchivedFile::getQueryInfo();
$this->addTables( $fileQuery['tables'] );
$this->addFields( $fileQuery['fields'] );
}
if ( $sha1 ) {
$this->addWhereFld( 'fa_sha1', $sha1 );
}
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' : '' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
+ $canViewFile = RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_FILE, $user );
+
$file = [];
$file['id'] = (int)$row->fa_id;
$file['name'] = $row->fa_name;
$file = [];
$file['id'] = (int)$row->fa_id;
$file['name'] = $row->fa_name;
$file['userid'] = (int)$row->fa_user;
$file['user'] = $row->fa_user_text;
}
$file['userid'] = (int)$row->fa_user;
$file['user'] = $row->fa_user_text;
}
+ 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 );
}
$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();
$file['size'] = $row->fa_size;
$pageCount = ArchivedFile::newFromRow( $row )->pageCount();
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
- if ( $fld_mediatype ) {
+ if ( $fld_mediatype && $canViewFile ) {
$file['mediatype'] = $row->fa_media_type;
}
$file['mediatype'] = $row->fa_media_type;
}
+ if ( $fld_metadata && $canViewFile ) {
$file['metadata'] = $row->fa_metadata
? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
: null;
}
$file['metadata'] = $row->fa_metadata
? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
: null;
}
+ if ( $fld_bitdepth && $canViewFile ) {
$file['bitdepth'] = $row->fa_bits;
}
$file['bitdepth'] = $row->fa_bits;
}
+ if ( $fld_mime && $canViewFile ) {
$file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
}
if ( $fld_archivename && !is_null( $row->fa_archive_name ) ) {
$file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
}
if ( $fld_archivename && !is_null( $row->fa_archive_name ) ) {
"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-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.",
"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.",
"apierror-cantoverwrite-sharedfile": "{{doc-apierror}}",
"apierror-cantsend": "{{doc-apierror}}",
"apierror-cantundelete": "{{doc-apierror}}",
"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.",
"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.",