Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
EventBodyValidator
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 4
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validateEvent
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
210
 getJobFromParams
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 throwJobErrors
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\EventBus\Rest;
4
5use Exception;
6use MediaWiki\Extension\EventBus\EventBus;
7use MediaWiki\Extension\EventBus\EventFactory;
8use MediaWiki\JobQueue\Job;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Rest\HttpException;
11use Psr\Log\LoggerInterface;
12
13/**
14 * Validates the body
15 */
16class EventBodyValidator {
17
18    public function __construct(
19        private readonly string $secretKey,
20        private readonly LoggerInterface $logger,
21    ) {
22    }
23
24    public function validateEvent( array $event ): Job {
25        // check that we have the needed components of the event
26        if ( !isset( $event['database'] ) ||
27            !isset( $event['type'] ) ||
28            !isset( $event['params'] )
29        ) {
30            $missingParams = [];
31            if ( !isset( $event['database'] ) ) {
32                $missingParams[] = 'database';
33            }
34            if ( !isset( $event['type'] ) ) {
35                $missingParams[] = 'type';
36            }
37            if ( !isset( $event['params'] ) ) {
38                $missingParams[] = 'params';
39            }
40            throw new HttpException( 'Invalid event received', 400, [ 'missing_params' => $missingParams ] );
41        }
42
43        if ( !isset( $event['mediawiki_signature'] ) ) {
44            throw new HttpException( 'Missing mediawiki signature', 403 );
45        }
46
47        $signature = $event['mediawiki_signature'];
48        unset( $event['mediawiki_signature'] );
49
50        $serialized_event = EventBus::serializeEvents( $event );
51        $expected_signature = EventFactory::getEventSignature(
52            $serialized_event,
53            $this->secretKey
54        );
55
56        $verified = is_string( $signature )
57            && hash_equals( $expected_signature, $signature );
58
59        if ( !$verified ) {
60            throw new HttpException( 'Invalid mediawiki signature', 403 );
61        }
62
63        // check if there are any base64-encoded parameters and if so decode them
64        foreach ( $event['params'] as $key => &$value ) {
65            if ( !is_string( $value ) ) {
66                continue;
67            }
68            if ( preg_match( '/^data:application\/octet-stream;base64,([\s\S]+)$/', $value, $match ) ) {
69                $value = base64_decode( $match[1], true );
70                if ( $value === false ) {
71                    throw new HttpException(
72                        'Parameter base64_decode() failed',
73                        500,
74                        [
75                            'param_name'  => $key,
76                            'param_value' => $match[1]
77                        ]
78                    );
79                }
80            }
81        }
82        unset( $value );
83
84        return $this->getJobFromParams( $event );
85    }
86
87    /**
88     * @param array $jobEvent containing the job EventBus event
89     * @return Job
90     * @throws HttpException
91     */
92    private function getJobFromParams( array $jobEvent ) {
93        try {
94            $jobFactory = MediaWikiServices::getInstance()->getJobFactory();
95            return $jobFactory->newJob( $jobEvent['type'], $jobEvent['params'] );
96        } catch ( Exception $e ) {
97            $this->throwJobErrors( [
98                'status'  => false,
99                'error' => $e->getMessage(),
100                'type' => $jobEvent['type']
101            ] );
102        }
103    }
104
105    /**
106     * @param array $jobResults
107     * @throws HttpException
108     * @return never
109     */
110    private function throwJobErrors( $jobResults ) {
111        $this->logger->error( 'Failed creating job from description', [
112            'job_type' => $jobResults['type'],
113            'error' => $jobResults['error']
114        ] );
115
116        throw new HttpException( "Failed creating job from description",
117            400,
118            [ 'error' => $jobResults['error'] ]
119        );
120    }
121}