2022-09-29 13:40:04 +02:00
< ? php
declare ( strict_types = 1 );
/**
* @ copyright Copyright ( c ) 2020 Arthur Schiwon < blizzz @ arthur - schiwon . de >
*
* @ author Arthur Schiwon < blizzz @ arthur - schiwon . de >
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
*
*/
namespace OC\Core\Command\Maintenance ;
use OCP\DB\QueryBuilder\IQueryBuilder ;
use OCP\IDBConnection ;
use OCP\IUser ;
use OCP\IUserManager ;
2023-11-23 10:22:34 +01:00
use Symfony\Component\Console\Command\Command ;
2022-09-29 13:40:04 +02:00
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Output\OutputInterface ;
2022-10-11 15:36:21 +02:00
use Symfony\Component\Console\Question\ConfirmationQuestion ;
2022-09-29 13:40:04 +02:00
class RepairShareOwnership extends Command {
public function __construct (
2023-06-12 16:46:44 +02:00
private IDBConnection $dbConnection ,
private IUserManager $userManager ,
2022-09-29 13:40:04 +02:00
) {
parent :: __construct ();
}
protected function configure () {
$this
-> setName ( 'maintenance:repair-share-owner' )
-> setDescription ( 'repair invalid share-owner entries in the database' )
2022-10-11 15:36:21 +02:00
-> addOption ( 'no-confirm' , 'y' , InputOption :: VALUE_NONE , " Don't ask for confirmation before repairing the shares " )
2022-09-29 13:40:04 +02:00
-> addArgument ( 'user' , InputArgument :: OPTIONAL , " User to fix incoming shares for, if omitted all users will be fixed " );
}
protected function execute ( InputInterface $input , OutputInterface $output ) : int {
2022-10-11 15:36:21 +02:00
$noConfirm = $input -> getOption ( 'no-confirm' );
2022-09-29 13:40:04 +02:00
$userId = $input -> getArgument ( 'user' );
if ( $userId ) {
$user = $this -> userManager -> get ( $userId );
if ( ! $user ) {
$output -> writeln ( " <error>user $userId not found</error> " );
return 1 ;
}
2022-10-11 15:36:21 +02:00
$shares = $this -> getWrongShareOwnershipForUser ( $user );
2022-09-29 13:40:04 +02:00
} else {
2022-10-18 15:42:25 +02:00
$shares = $this -> getWrongShareOwnership ();
2022-09-29 13:40:04 +02:00
}
2022-10-11 15:36:21 +02:00
if ( $shares ) {
$output -> writeln ( " " );
$output -> writeln ( " Found " . count ( $shares ) . " shares with invalid share owner " );
foreach ( $shares as $share ) {
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
2023-01-16 14:13:57 +01:00
$output -> writeln ( " - share { $share [ 'shareId' ] } from \" { $share [ 'initiator' ] } \" to \" { $share [ 'receiver' ] } \" at \" { $share [ 'fileTarget' ] } \" , owned by \" { $share [ 'owner' ] } \" , that should be owned by \" { $share [ 'mountOwner' ] } \" " );
2022-10-11 15:36:21 +02:00
}
$output -> writeln ( " " );
if ( ! $noConfirm ) {
$helper = $this -> getHelper ( 'question' );
$question = new ConfirmationQuestion ( 'Repair these shares? [y/N]' , false );
if ( ! $helper -> ask ( $input , $output , $question )) {
return 0 ;
}
}
$output -> writeln ( " Repairing " . count ( $shares ) . " shares " );
$this -> repairShares ( $shares );
} else {
$output -> writeln ( " Found no shares with invalid share owner " );
2022-09-29 13:40:04 +02:00
}
2022-09-30 15:34:55 +02:00
2022-09-29 13:40:04 +02:00
return 0 ;
}
2022-10-18 15:42:25 +02:00
/**
* @ return array { shareId : int , fileTarget : string , initiator : string , receiver : string , owner : string , mountOwner : string }[]
* @ throws \OCP\DB\Exception
*/
protected function getWrongShareOwnership () : array {
$qb = $this -> dbConnection -> getQueryBuilder ();
$brokenShares = $qb
-> select ( 's.id' , 'm.user_id' , 's.uid_owner' , 's.uid_initiator' , 's.share_with' , 's.file_target' )
-> from ( 'share' , 's' )
-> join ( 's' , 'filecache' , 'f' , $qb -> expr () -> eq ( 's.item_source' , $qb -> expr () -> castColumn ( 'f.fileid' , IQueryBuilder :: PARAM_STR )))
-> join ( 's' , 'mounts' , 'm' , $qb -> expr () -> eq ( 'f.storage' , 'm.storage_id' ))
-> where ( $qb -> expr () -> neq ( 'm.user_id' , 's.uid_owner' ))
-> andWhere ( $qb -> expr () -> eq ( $qb -> func () -> concat ( $qb -> expr () -> literal ( '/' ), 'm.user_id' , $qb -> expr () -> literal ( '/' )), 'm.mount_point' ))
-> executeQuery ()
-> fetchAll ();
$found = [];
foreach ( $brokenShares as $share ) {
$found [] = [
'shareId' => ( int ) $share [ 'id' ],
'fileTarget' => $share [ 'file_target' ],
'initiator' => $share [ 'uid_initiator' ],
'receiver' => $share [ 'share_with' ],
'owner' => $share [ 'uid_owner' ],
'mountOwner' => $share [ 'user_id' ],
];
}
return $found ;
}
2022-10-11 15:36:21 +02:00
/**
* @ param IUser $user
* @ return array { shareId : int , fileTarget : string , initiator : string , receiver : string , owner : string , mountOwner : string }[]
* @ throws \OCP\DB\Exception
*/
protected function getWrongShareOwnershipForUser ( IUser $user ) : array {
2022-09-29 13:40:04 +02:00
$qb = $this -> dbConnection -> getQueryBuilder ();
2022-10-11 15:36:21 +02:00
$brokenShares = $qb
2022-09-30 15:34:55 +02:00
-> select ( 's.id' , 'm.user_id' , 's.uid_owner' , 's.uid_initiator' , 's.share_with' , 's.file_target' )
2022-09-29 13:40:04 +02:00
-> from ( 'share' , 's' )
-> join ( 's' , 'filecache' , 'f' , $qb -> expr () -> eq ( 's.item_source' , $qb -> expr () -> castColumn ( 'f.fileid' , IQueryBuilder :: PARAM_STR )))
-> join ( 's' , 'mounts' , 'm' , $qb -> expr () -> eq ( 'f.storage' , 'm.storage_id' ))
-> where ( $qb -> expr () -> neq ( 'm.user_id' , 's.uid_owner' ))
-> andWhere ( $qb -> expr () -> eq ( $qb -> func () -> concat ( $qb -> expr () -> literal ( '/' ), 'm.user_id' , $qb -> expr () -> literal ( '/' )), 'm.mount_point' ))
-> andWhere ( $qb -> expr () -> eq ( 's.share_with' , $qb -> createNamedParameter ( $user -> getUID ())))
-> executeQuery ()
-> fetchAll ();
$found = [];
2022-10-11 15:36:21 +02:00
foreach ( $brokenShares as $share ) {
$found [] = [
'shareId' => ( int ) $share [ 'id' ],
'fileTarget' => $share [ 'file_target' ],
'initiator' => $share [ 'uid_initiator' ],
'receiver' => $share [ 'share_with' ],
'owner' => $share [ 'uid_owner' ],
'mountOwner' => $share [ 'user_id' ],
];
}
return $found ;
}
/**
* @ param array { shareId : int , fileTarget : string , initiator : string , receiver : string , owner : string , mountOwner : string }[] $shares
* @ return void
*/
protected function repairShares ( array $shares ) {
$this -> dbConnection -> beginTransaction ();
2022-09-30 15:34:55 +02:00
$update = $this -> dbConnection -> getQueryBuilder ();
$update -> update ( 'share' )
-> set ( 'uid_owner' , $update -> createParameter ( 'share_owner' ))
-> set ( 'uid_initiator' , $update -> createParameter ( 'share_initiator' ))
-> where ( $update -> expr () -> eq ( 'id' , $update -> createParameter ( 'share_id' )));
2022-10-11 15:36:21 +02:00
foreach ( $shares as $share ) {
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
$update -> setParameter ( 'share_id' , $share [ 'shareId' ], IQueryBuilder :: PARAM_INT );
$update -> setParameter ( 'share_owner' , $share [ 'mountOwner' ]);
2022-09-29 13:40:04 +02:00
2022-10-11 15:36:21 +02:00
// if the broken owner is also the initiator it's safe to update them both, otherwise we don't touch the initiator
if ( $share [ 'initiator' ] === $share [ 'owner' ]) {
$update -> setParameter ( 'share_initiator' , $share [ 'mountOwner' ]);
2022-09-30 15:34:55 +02:00
} else {
2022-10-11 15:36:21 +02:00
$update -> setParameter ( 'share_initiator' , $share [ 'initiator' ]);
2022-09-29 13:40:04 +02:00
}
2022-09-30 15:34:55 +02:00
$update -> executeStatement ();
2022-09-29 13:40:04 +02:00
}
2022-10-11 15:36:21 +02:00
$this -> dbConnection -> commit ();
2022-09-29 13:40:04 +02:00
}
}