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
This commit is contained in:
Vincent Privat 2019-08-18 16:21:40 +02:00
parent bae0adcd72
commit 4d2965cf72
4 changed files with 38 additions and 23 deletions

View file

@ -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 ===
* …

View file

@ -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 ) ) {

View file

@ -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.",

View file

@ -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.",