From 3cd248cc836087592436ffec8c8d62aa6efbf185 Mon Sep 17 00:00:00 2001 From: MusikAnimal Date: Wed, 9 Aug 2023 13:35:29 -0400 Subject: [PATCH] Make iThenticate reports work with new TCA integration --- .copypatrol.ini.dist | 30 +++++++++ .gitignore | 1 + README.md | 18 ++++-- composer.json | 1 + composer.lock | 17 ++--- src/Controller/AppController.php | 85 +++++++++++++++++++++++-- src/Repository/CopyPatrolRepository.php | 4 +- 7 files changed, 134 insertions(+), 22 deletions(-) create mode 100644 .copypatrol.ini.dist diff --git a/.copypatrol.ini.dist b/.copypatrol.ini.dist new file mode 100644 index 00000000..5ac108b7 --- /dev/null +++ b/.copypatrol.ini.dist @@ -0,0 +1,30 @@ +; Used by CopyPatrolBot, which populates the database that gets feed into the frontend. +; For more information, see https://github.com/JJMC89/copypatrol-backend#configuration +[copypatrol] +version = 2023.08.10 +url-ignore-list-title = example-url-title +user-ignore-list-title = example-user-title + +[copypatrol:en.wikipedia.org] +enabled = true +namespaces = 0,2,118 +pagetriage-namespaces = 0,118 + +[copypatrol:es.wikipedia.org] +enabled = true +namespaces = 0,2 + +[copypatrol:fr.wikipedia.org] +enabled = false + +[client] +drivername = mysql+pymysql +username = example-db-user +password = example-db-password +database = example-db-name +host = localhost +port = 3306 + +[tca] +domain = example-tca-domain.com +key = example-tca-key diff --git a/.gitignore b/.gitignore index ca7ab872..776a6de1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.copypatrol.ini ###> symfony/framework-bundle ### /.env.local diff --git a/README.md b/README.md index 1bfe4bab..7343eff7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ A tool that allows you to see recent Wikipedia edits that are flagged as possibl * User documentation: https://meta.wikimedia.org/wiki/Special:MyLanguage/CopyPatrol * Issue tracker: https://phabricator.wikimedia.org/tag/copypatrol/ -* Source code: https://gitlab.wikimedia.org/repos/commtech/copypatrol +* Frontend source code (this repo): https://github.com/wikimedia/CopyPatrol +* Bot source code: https://github.com/JJMC89/copypatrol-backend ## Installing manually @@ -18,7 +19,11 @@ A tool that allows you to see recent Wikipedia edits that are flagged as possibl * [Toolforge access](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Quickstart) This application makes use of the [Symfony framework](https://symfony.com/) and -the [ToolforgeBundle](https://github.com/wikimedia/ToolforgeBundle). +the [ToolforgeBundle](https://github.com/wikimedia/ToolforgeBundle). A [bot](https://meta.wikimedia.org/wiki/User:CopyPatrolBot) is used +to continually query recent changes against the Turnitin Core API (TCA), record possible copyright +violations in a user database, and CopyPatrol then reads from that database. Unless you need to work +on the [bot code](https://github.com/JJMC89/copypatrol-backend), there's no reason to bother with bot +integration, and instead connect to the existing user database on Toolforge (more on this below). ### Instructions @@ -123,10 +128,11 @@ docker run -ti -p 80:80 -v $(pwd)/.env.local:/app/.env.local wikimedia/copypatro Any languages that are not regularly being used should be removed. 3. Make sure the corresponding `-wikipedia` message in [i18n/en.json](i18n/en.json) (and [qqq.json](i18n/qqq.json) exists and is translated in the desired language. -4. Add the language code to `APP_ENABLED_LANGS` in the [.env](.env) files. -5. _We now use https://github.com/JJMC89/copypatrol-backend as the bot backend; technical instructions TBD_ +4. Update the `.copypatrol.ini` file accordingly (which is used by the bot), + and add the new languages to the `APP_ENABLED_LANGS` variable in [.env](.env). ## Removing a language -1. Remove the language from `APP_ENABLED_LANGS`. -2. _TBD_: Remove the job from CopyPatrolBot. +1. In `.copypatrol.ini`, set the `enabled` key for the desired language to `false`, + or remove the definition entirely. +2. Remove the language from the `APP_ENABLED_LANGS` variable in [.env](.env). diff --git a/composer.json b/composer.json index 212935ed..1ef29d1b 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "php": ">=7.4", "ext-ctype": "*", "ext-iconv": "*", + "ext-json": "*", "doctrine/annotations": "^2.0", "doctrine/doctrine-bundle": "^2.2", "phpxmlrpc/phpxmlrpc": "^4.10", diff --git a/composer.lock b/composer.lock index b8a3da2d..39695787 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5abc06cc4d39f45992a4d74704709ef7", + "content-hash": "c48a26aec2d2044d9269fa19cbb43358", "packages": [ { "name": "doctrine/annotations", @@ -1487,16 +1487,16 @@ }, { "name": "symfony/config", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4" + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2a6b1111d038adfa15d52c0871e540f3b352d1e4", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4", + "url": "https://api.github.com/repos/symfony/config/zipball/8109892f27beed9252bd1f1c1880aeb4ad842650", + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650", "shasum": "" }, "require": { @@ -1546,7 +1546,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.21" + "source": "https://github.com/symfony/config/tree/v5.4.26" }, "funding": [ { @@ -1562,7 +1562,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-19T20:21:11+00:00" }, { "name": "symfony/console", @@ -7402,7 +7402,8 @@ "platform": { "php": ">=7.4", "ext-ctype": "*", - "ext-iconv": "*" + "ext-iconv": "*", + "ext-json": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 8672ab67..ecc3d0f1 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -8,6 +8,7 @@ use DateTime; use Doctrine\DBAL\Exception\DriverException; use Exception; +use Krinkle\Intuition\Intuition; use PhpXmlRpc\Client; use PhpXmlRpc\Value; use stdClass; @@ -18,8 +19,10 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; // phpcs:ignore MediaWiki.Classes.UnusedUseStatement.UnusedUse use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\HttpClient\HttpClientInterface; class AppController extends AbstractController { @@ -278,26 +281,39 @@ public function undoReviewAction( } /** - * @Route("/ithenticate/{id}", name="ithenticate", requirements={"id"="\d+|[a-z\d+\-]"}) + * @Route("/ithenticate/{id}", name="ithenticate", requirements={"id"="\d+|[a-z\d+\-]+"}) + * @param HttpClientInterface $httpClient + * @param RequestStack $requestStack * @param CopyPatrolRepository $copyPatrolRepo + * @param Intuition $intuition * @param string $iThenticateUser * @param string $iThenticatePassword - * @param int $id + * @param string $id * @return RedirectResponse * @throws Exception */ public function iThenticateAction( + HttpClientInterface $httpClient, + RequestStack $requestStack, CopyPatrolRepository $copyPatrolRepo, + Intuition $intuition, string $iThenticateUser, string $iThenticatePassword, - int $id + string $id ): RedirectResponse { $record = $copyPatrolRepo->getRecordById( $id ); $v2DateTime = new DateTime( self::ITHENTICATE_V2_TIMESTAMP ); if ( new DateTime( $record['rev_timestamp'] ) > $v2DateTime ) { - // New system - // TODO: make this work - return new RedirectResponse( '' ); + // New system. + $loggedInUser = $requestStack->getSession()->get( 'logged_in_user' )->username; + if ( $loggedInUser === null ) { + return $this->redirectToRoute( 'toolforge_login', [ + 'callback' => $this->generateUrl( 'toolforge_oauth_callback', [ + 'redirect' => $requestStack->getCurrentRequest()->getUri(), + ] ) + ] ); + } + return $this->redirectToTcaViewer( $httpClient, $loggedInUser, $intuition->getLang(), $id ); } $client = new Client( 'https://api.ithenticate.com/rpc' ); @@ -315,6 +331,63 @@ public function iThenticateAction( return $this->redirect( $response['view_only_url']->scalarval() ); } + /** + * @param HttpClientInterface $client + * @param string $loggedInUser + * @param string $locale + * @param string $id + * @param int $retries + * @return RedirectResponse + */ + private function redirectToTcaViewer( + HttpClientInterface $client, + string $loggedInUser, + string $locale, + string $id, + int $retries = 0 + ): RedirectResponse { + // Load config settings. This lives in .copypatrol.ini and is shared with the bot. + $projectDir = $this->getParameter( 'kernel.project_dir' ); + $config = parse_ini_file( "$projectDir/.copypatrol.ini" ); + // Request the viewer URL from Turnitin. + $response = $client->request( + 'POST', + "https://{$config['domain']}/api/v1/submissions/$id/viewer-url", + [ + 'json' => [ + 'viewer_user_id' => $loggedInUser, + 'locale' => $locale, + 'viewer_permissions' => [ + 'may_view_submission_full_source' => true, + 'may_view_match_submission_info' => true, + 'may_view_document_details_panel' => true, + 'may_view_sections_exclusion_panel' => true, + ], + ], + 'headers' => [ + 'Authorization' => "Bearer {$config['key']}", + 'From' => 'copypatrol@toolforge.org', + 'User-Agent' => "copypatrol/{$config['version']}", + 'X-Turnitin-Integration-Name' => 'CopyPatrol', + 'X-Turnitin-Integration-Version' => $config['version'], + ], + ] + ); + + if ( $response->getStatusCode() !== Response::HTTP_OK ) { + if ( $retries > 5 ) { + throw new HttpException( + Response::HTTP_BAD_GATEWAY, + 'Failed to fetch URL from the Turnitin Core API' + ); + } + sleep( $retries + 1 ); + return $this->redirectToTcaViewer( $client, $loggedInUser, $locale, $id, $retries + 1 ); + } + + return $this->redirect( json_decode( $response->getContent() )->viewer_url ); + } + /** * ToolforgeBundle's logout apparently doesn't work :( * diff --git a/src/Repository/CopyPatrolRepository.php b/src/Repository/CopyPatrolRepository.php index 466db40e..6771ee45 100644 --- a/src/Repository/CopyPatrolRepository.php +++ b/src/Repository/CopyPatrolRepository.php @@ -197,10 +197,10 @@ public function updateCopyvioAssessment( /** * Get a particular record by submission ID. * - * @param int $submissionId ID of record. + * @param string $submissionId ID of record. * @return array Query result. */ - public function getRecordById( int $submissionId ) { + public function getRecordById( string $submissionId ) { $sql = "SELECT * FROM diffs WHERE submission_id = :id"; return $this->client->fetchAssociative( $sql, [ 'id' => $submissionId