This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace App\Controller; |
||||||
6 | |||||||
7 | use App\Model\Edit; |
||||||
8 | use App\Repository\ProjectRepository; |
||||||
9 | use MediaWiki\OAuthClient\Client; |
||||||
10 | use MediaWiki\OAuthClient\ClientConfig; |
||||||
11 | use MediaWiki\OAuthClient\Consumer; |
||||||
12 | use MediaWiki\OAuthClient\Exception; |
||||||
13 | use MediaWiki\OAuthClient\Token; |
||||||
14 | use OpenApi\Annotations as OA; |
||||||
15 | use Symfony\Component\HttpFoundation\JsonResponse; |
||||||
16 | use Symfony\Component\HttpFoundation\RedirectResponse; |
||||||
17 | use Symfony\Component\HttpFoundation\Request; |
||||||
18 | use Symfony\Component\HttpFoundation\RequestStack; |
||||||
19 | use Symfony\Component\HttpFoundation\Response; |
||||||
20 | use Symfony\Component\Routing\Annotation\Route; |
||||||
21 | |||||||
22 | /** |
||||||
23 | * The DefaultController handles the homepage, about pages, and user authentication. |
||||||
24 | */ |
||||||
25 | class DefaultController extends XtoolsController |
||||||
26 | { |
||||||
27 | /** @var Client The Oauth HTTP client. */ |
||||||
28 | protected Client $oauthClient; |
||||||
29 | |||||||
30 | /** |
||||||
31 | * Required to be defined by XtoolsController, though here it is unused. |
||||||
32 | * @inheritDoc |
||||||
33 | * @codeCoverageIgnore |
||||||
34 | */ |
||||||
35 | public function getIndexRoute(): string |
||||||
36 | { |
||||||
37 | return 'homepage'; |
||||||
38 | } |
||||||
39 | |||||||
40 | /** |
||||||
41 | * Display the homepage. |
||||||
42 | * @Route("/", name="homepage") |
||||||
43 | * @Route("/index.php", name="homepageIndexPhp") |
||||||
44 | * @return Response |
||||||
45 | */ |
||||||
46 | public function indexAction(): Response |
||||||
47 | { |
||||||
48 | return $this->render('default/index.html.twig', [ |
||||||
49 | 'xtPage' => 'home', |
||||||
50 | ]); |
||||||
51 | } |
||||||
52 | |||||||
53 | /** |
||||||
54 | * Redirect to the default project (or Meta) for Oauth authentication. |
||||||
55 | * @Route("/login", name="login") |
||||||
56 | * @param Request $request |
||||||
57 | * @param RequestStack $requestStack |
||||||
58 | * @param ProjectRepository $projectRepo |
||||||
59 | * @param string $centralAuthProject |
||||||
60 | * @return RedirectResponse |
||||||
61 | * @codeCoverageIgnore |
||||||
62 | */ |
||||||
63 | public function loginAction( |
||||||
64 | Request $request, |
||||||
65 | RequestStack $requestStack, |
||||||
66 | ProjectRepository $projectRepo, |
||||||
67 | string $centralAuthProject |
||||||
68 | ): RedirectResponse { |
||||||
69 | try { |
||||||
70 | [ $next, $token ] = $this->getOauthClient($request, $projectRepo, $centralAuthProject)->initiate(); |
||||||
71 | } catch (Exception $oauthException) { |
||||||
72 | $this->addFlashMessage('notice', 'error-login'); |
||||||
73 | return $this->redirectToRoute('homepage'); |
||||||
74 | } |
||||||
75 | |||||||
76 | // Save the request token to the session. |
||||||
77 | $requestStack->getSession()->set('oauth_request_token', $token); |
||||||
78 | return new RedirectResponse($next); |
||||||
79 | } |
||||||
80 | |||||||
81 | /** |
||||||
82 | * Receive authentication credentials back from the Oauth wiki. |
||||||
83 | * @Route("/oauth_callback", name="oauth_callback") |
||||||
84 | * @Route("/oauthredirector.php", name="old_oauth_callback") |
||||||
85 | * @param RequestStack $requestStack |
||||||
86 | * @param ProjectRepository $projectRepo |
||||||
87 | * @param string $centralAuthProject |
||||||
88 | * @return RedirectResponse |
||||||
89 | */ |
||||||
90 | public function oauthCallbackAction( |
||||||
91 | RequestStack $requestStack, |
||||||
92 | ProjectRepository $projectRepo, |
||||||
93 | string $centralAuthProject |
||||||
94 | ): RedirectResponse { |
||||||
95 | $request = $requestStack->getCurrentRequest(); |
||||||
96 | $session = $requestStack->getSession(); |
||||||
97 | // Give up if the required GET params don't exist. |
||||||
98 | if (!$request->get('oauth_verifier')) { |
||||||
99 | throw $this->createNotFoundException('No OAuth verifier given.'); |
||||||
100 | } |
||||||
101 | |||||||
102 | // Complete authentication. |
||||||
103 | $client = $this->getOauthClient($request, $projectRepo, $centralAuthProject); |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
104 | $token = $requestStack->getSession()->get('oauth_request_token'); |
||||||
105 | |||||||
106 | if (!is_a($token, Token::class)) { |
||||||
107 | $this->addFlashMessage('notice', 'error-login'); |
||||||
108 | return $this->redirectToRoute('homepage'); |
||||||
109 | } |
||||||
110 | |||||||
111 | $verifier = $request->get('oauth_verifier'); |
||||||
112 | $accessToken = $client->complete($token, $verifier); |
||||||
113 | |||||||
114 | // Store access token, and remove request token. |
||||||
115 | $session->set('oauth_access_token', $accessToken); |
||||||
116 | $session->remove('oauth_request_token'); |
||||||
117 | |||||||
118 | // Store user identity. |
||||||
119 | $ident = $client->identify($accessToken); |
||||||
120 | $session->set('logged_in_user', $ident); |
||||||
121 | |||||||
122 | // Store reference to the client. |
||||||
123 | $session->set('oauth_client', $this->oauthClient); |
||||||
124 | |||||||
125 | // Redirect to callback, if given. |
||||||
126 | if ($request->query->get('redirect')) { |
||||||
127 | return $this->redirect($request->query->get('redirect')); |
||||||
128 | } |
||||||
129 | |||||||
130 | // Send back to homepage. |
||||||
131 | return $this->redirectToRoute('homepage'); |
||||||
132 | } |
||||||
133 | |||||||
134 | /** |
||||||
135 | * Get an OAuth client, configured to the default project. |
||||||
136 | * (This shouldn't really be in this class, but oh well.) |
||||||
137 | * @param Request $request |
||||||
138 | * @param ProjectRepository $projectRepo |
||||||
139 | * @param string $centralAuthProject |
||||||
140 | * @return Client |
||||||
141 | * @codeCoverageIgnore |
||||||
142 | */ |
||||||
143 | protected function getOauthClient( |
||||||
144 | Request $request, |
||||||
145 | ProjectRepository $projectRepo, |
||||||
146 | string $centralAuthProject |
||||||
147 | ): Client { |
||||||
148 | if (isset($this->oauthClient)) { |
||||||
149 | return $this->oauthClient; |
||||||
150 | } |
||||||
151 | $defaultProject = $projectRepo->getProject($centralAuthProject); |
||||||
152 | $endpoint = $defaultProject->getUrl(false) |
||||||
153 | . $defaultProject->getScript() |
||||||
154 | . '?title=Special:OAuth'; |
||||||
155 | $conf = new ClientConfig($endpoint); |
||||||
156 | $consumerKey = $this->getParameter('oauth_key'); |
||||||
157 | $consumerSecret = $this->getParameter('oauth_secret'); |
||||||
158 | $conf->setConsumer(new Consumer($consumerKey, $consumerSecret)); |
||||||
0 ignored issues
–
show
It seems like
$consumerSecret can also be of type array ; however, parameter $secret of MediaWiki\OAuthClient\Consumer::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$consumerKey can also be of type array ; however, parameter $key of MediaWiki\OAuthClient\Consumer::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
159 | $this->oauthClient = new Client($conf); |
||||||
160 | |||||||
161 | // Set the callback URL if given. Used to redirect back to target page after logging in. |
||||||
162 | if ($request->query->get('callback')) { |
||||||
163 | $this->oauthClient->setCallback($request->query->get('callback')); |
||||||
0 ignored issues
–
show
$request->query->get('callback') can contain request data and is used in request header context(s) leading to a potential security vulnerability.
1 path for user data to reach this point
Used in request-header context
General Strategies to prevent injectionIn general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:
if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
throw new \InvalidArgumentException('This input is not allowed.');
}
For numeric data, we recommend to explicitly cast the data: $sanitized = (integer) $tainted;
![]() Security
Variable Injection
introduced
by
$request->query->get('callback') can contain request data and is used in variable name context(s) leading to a potential security vulnerability.
1 path for user data to reach this point
Used in variable context
General Strategies to prevent injectionIn general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:
if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
throw new \InvalidArgumentException('This input is not allowed.');
}
For numeric data, we recommend to explicitly cast the data: $sanitized = (integer) $tainted;
![]() |
|||||||
164 | } |
||||||
165 | |||||||
166 | return $this->oauthClient; |
||||||
167 | } |
||||||
168 | |||||||
169 | /** |
||||||
170 | * Log out the user and return to the homepage. |
||||||
171 | * @Route("/logout", name="logout") |
||||||
172 | * @param RequestStack $requestStack |
||||||
173 | * @return RedirectResponse |
||||||
174 | */ |
||||||
175 | public function logoutAction(RequestStack $requestStack): RedirectResponse |
||||||
176 | { |
||||||
177 | $requestStack->getSession()->invalidate(); |
||||||
178 | return $this->redirectToRoute('homepage'); |
||||||
179 | } |
||||||
180 | |||||||
181 | /************************ API endpoints ************************/ |
||||||
182 | |||||||
183 | /** |
||||||
184 | * Get domain name, URL, API path and database name for the given project. |
||||||
185 | * @Route("/api/project/normalize/{project}", name="ProjectApiNormalize", methods={"GET"}) |
||||||
186 | * @OA\Tag(name="Project API") |
||||||
187 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||||
188 | * @OA\Response( |
||||||
189 | * response=200, |
||||||
190 | * description="The domain, URL, API path and database name.", |
||||||
191 | * @OA\JsonContent( |
||||||
192 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||||
193 | * @OA\Property(property="domain", type="string", example="en.wikipedia.org"), |
||||||
194 | * @OA\Property(property="url", type="string", example="https://en.wikipedia.org"), |
||||||
195 | * @OA\Property(property="api", type="string", example="https://en.wikipedia.org/w/api.php"), |
||||||
196 | * @OA\Property(property="database", type="string", example="enwiki"), |
||||||
197 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||||
198 | * ) |
||||||
199 | * ) |
||||||
200 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||||
201 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||||
202 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||||
203 | * @return JsonResponse |
||||||
204 | */ |
||||||
205 | public function normalizeProjectApiAction(): JsonResponse |
||||||
206 | { |
||||||
207 | return $this->getFormattedApiResponse([ |
||||||
208 | 'domain' => $this->project->getDomain(), |
||||||
209 | 'url' => $this->project->getUrl(), |
||||||
210 | 'api' => $this->project->getApiUrl(), |
||||||
211 | 'database' => $this->project->getDatabaseName(), |
||||||
212 | ]); |
||||||
213 | } |
||||||
214 | |||||||
215 | /** |
||||||
216 | * Get the localized names for each namespaces of the given project. |
||||||
217 | * @Route("/api/project/namespaces/{project}", name="ProjectApiNamespaces", methods={"GET"}) |
||||||
218 | * @OA\Tag(name="Project API") |
||||||
219 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||||
220 | * @OA\Response( |
||||||
221 | * response=200, |
||||||
222 | * description="List of localized namespaces keyed by their ID.", |
||||||
223 | * @OA\JsonContent( |
||||||
224 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||||
225 | * @OA\Property(property="url", type="string", example="https://en.wikipedia.org"), |
||||||
226 | * @OA\Property(property="api", type="string", example="https://en.wikipedia.org/w/api.php"), |
||||||
227 | * @OA\Property(property="database", type="string", example="enwiki"), |
||||||
228 | * @OA\Property(property="namespaces", type="object", example={"0": "", "3": "User talk"}), |
||||||
229 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||||
230 | * ) |
||||||
231 | * ) |
||||||
232 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||||
233 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||||
234 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||||
235 | * @return JsonResponse |
||||||
236 | */ |
||||||
237 | public function namespacesApiAction(): JsonResponse |
||||||
238 | { |
||||||
239 | return $this->getFormattedApiResponse([ |
||||||
240 | 'domain' => $this->project->getDomain(), |
||||||
241 | 'url' => $this->project->getUrl(), |
||||||
242 | 'api' => $this->project->getApiUrl(), |
||||||
243 | 'database' => $this->project->getDatabaseName(), |
||||||
244 | 'namespaces' => $this->project->getNamespaces(), |
||||||
245 | ]); |
||||||
246 | } |
||||||
247 | |||||||
248 | /** |
||||||
249 | * Get page assessment metadata for a project. |
||||||
250 | * @Route("/api/project/assessments/{project}", name="ProjectApiAssessments", methods={"GET"}) |
||||||
251 | * @OA\Tag(name="Project API") |
||||||
252 | * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageAssessments") |
||||||
253 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||||
254 | * @OA\Response( |
||||||
255 | * response=200, |
||||||
256 | * description="List of classifications and importance levels, along with their associated colours and badges.", |
||||||
257 | * @OA\JsonContent( |
||||||
258 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||||
259 | * @OA\Property(property="assessments", type="object", example={ |
||||||
260 | * "wikiproject_prefix": "Wikipedia:WikiProject ", |
||||||
261 | * "class": { |
||||||
262 | * "FA": { |
||||||
263 | * "badge": "b/bc/Featured_article_star.svg", |
||||||
264 | * "color": "#9CBDFF", |
||||||
265 | * "category": "Category:FA-Class articles" |
||||||
266 | * } |
||||||
267 | * }, |
||||||
268 | * "importance": { |
||||||
269 | * "Top": { |
||||||
270 | * "color": "#FF97FF", |
||||||
271 | * "category": "Category:Top-importance articles", |
||||||
272 | * "weight": 5 |
||||||
273 | * } |
||||||
274 | * } |
||||||
275 | * }), |
||||||
276 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||||
277 | * ) |
||||||
278 | * ) |
||||||
279 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||||
280 | * @return JsonResponse |
||||||
281 | */ |
||||||
282 | public function projectAssessmentsApiAction(): JsonResponse |
||||||
283 | { |
||||||
284 | return $this->getFormattedApiResponse([ |
||||||
285 | 'project' => $this->project->getDomain(), |
||||||
286 | 'assessments' => $this->project->getPageAssessments()->getConfig(), |
||||||
287 | ]); |
||||||
288 | } |
||||||
289 | |||||||
290 | /** |
||||||
291 | * Get assessment metadata for all projects. |
||||||
292 | * @Route("/api/project/assessments", name="ApiAssessmentsConfig", methods={"GET"}) |
||||||
293 | * @OA\Tag(name="Project API") |
||||||
294 | * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageAssessments") |
||||||
295 | * @OA\Response( |
||||||
296 | * response=200, |
||||||
297 | * description="Page assessment metadata for all projects that have |
||||||
298 | <a href='https://w.wiki/6o9c'>PageAssessments</a> installed.", |
||||||
299 | * @OA\JsonContent( |
||||||
300 | * @OA\Property(property="projects", type="array", @OA\Items(type="string"), |
||||||
301 | * example={"en.wikipedia.org", "fr.wikipedia.org"} |
||||||
302 | * ), |
||||||
303 | * @OA\Property(property="config", type="object", example={ |
||||||
304 | * "en.wikipedia.org": { |
||||||
305 | * "wikiproject_prefix": "Wikipedia:WikiProject ", |
||||||
306 | * "class": { |
||||||
307 | * "FA": { |
||||||
308 | * "badge": "b/bc/Featured_article_star.svg", |
||||||
309 | * "color": "#9CBDFF", |
||||||
310 | * "category": "Category:FA-Class articles" |
||||||
311 | * } |
||||||
312 | * }, |
||||||
313 | * "importance": { |
||||||
314 | * "Top": { |
||||||
315 | * "color": "#FF97FF", |
||||||
316 | * "category": "Category:Top-importance articles", |
||||||
317 | * "weight": 5 |
||||||
318 | * } |
||||||
319 | * } |
||||||
320 | * } |
||||||
321 | * }), |
||||||
322 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||||
323 | * ) |
||||||
324 | * ) |
||||||
325 | * @return JsonResponse |
||||||
326 | */ |
||||||
327 | public function assessmentsConfigApiAction(): JsonResponse |
||||||
328 | { |
||||||
329 | // Here there is no Project, so we don't use XtoolsController::getFormattedApiResponse(). |
||||||
330 | $response = new JsonResponse(); |
||||||
331 | $response->setEncodingOptions(JSON_NUMERIC_CHECK); |
||||||
332 | $response->setStatusCode(Response::HTTP_OK); |
||||||
333 | $response->setData([ |
||||||
334 | 'projects' => array_keys($this->getParameter('assessments')), |
||||||
335 | 'config' => $this->getParameter('assessments'), |
||||||
336 | ]); |
||||||
337 | |||||||
338 | return $response; |
||||||
339 | } |
||||||
340 | |||||||
341 | /** |
||||||
342 | * Transform given wikitext to HTML using the XTools parser. Wikitext must be passed in as the query 'wikitext'. |
||||||
343 | * @Route("/api/project/parser/{project}") |
||||||
344 | * @return JsonResponse Safe HTML. |
||||||
345 | */ |
||||||
346 | public function wikifyApiAction(): JsonResponse |
||||||
347 | { |
||||||
348 | return new JsonResponse( |
||||||
349 | Edit::wikifyString($this->request->query->get('wikitext', ''), $this->project) |
||||||
350 | ); |
||||||
351 | } |
||||||
352 | } |
||||||
353 |