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 Symfony\Component\HttpFoundation\JsonResponse; |
15
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
16
|
|
|
use Symfony\Component\HttpFoundation\Request; |
17
|
|
|
use Symfony\Component\HttpFoundation\Response; |
18
|
|
|
use Symfony\Component\HttpFoundation\Session\SessionInterface; |
19
|
|
|
use Symfony\Component\Routing\Annotation\Route; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* The DefaultController handles the homepage, about pages, and user authentication. |
23
|
|
|
*/ |
24
|
|
|
class DefaultController extends XtoolsController |
25
|
|
|
{ |
26
|
|
|
/** @var Client The Oauth HTTP client. */ |
27
|
|
|
protected Client $oauthClient; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Required to be defined by XtoolsController, though here it is unused. |
31
|
|
|
* @inheritDoc |
32
|
|
|
* @codeCoverageIgnore |
33
|
|
|
*/ |
34
|
|
|
public function getIndexRoute(): string |
35
|
|
|
{ |
36
|
|
|
return 'homepage'; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Display the homepage. |
41
|
|
|
* @Route("/", name="homepage") |
42
|
|
|
* @Route("/index.php", name="homepageIndexPhp") |
43
|
|
|
* @return Response |
44
|
|
|
*/ |
45
|
|
|
public function indexAction(): Response |
46
|
|
|
{ |
47
|
|
|
return $this->render('default/index.html.twig', [ |
48
|
|
|
'xtPage' => 'home', |
49
|
|
|
]); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Redirect to the default project (or Meta) for Oauth authentication. |
54
|
|
|
* @Route("/login", name="login") |
55
|
|
|
* @param Request $request |
56
|
|
|
* @param SessionInterface $session |
57
|
|
|
* @param ProjectRepository $projectRepo |
58
|
|
|
* @return RedirectResponse |
59
|
|
|
* @throws Exception If initialization fails. |
60
|
|
|
*/ |
61
|
|
|
public function loginAction( |
62
|
|
|
Request $request, |
63
|
|
|
SessionInterface $session, |
64
|
|
|
ProjectRepository $projectRepo |
65
|
|
|
): RedirectResponse { |
66
|
|
|
try { |
67
|
|
|
[ $next, $token ] = $this->getOauthClient($request, $projectRepo)->initiate(); |
68
|
|
|
} catch (Exception $oauthException) { |
69
|
|
|
throw $oauthException; |
70
|
|
|
// @TODO Make this work. |
71
|
|
|
//$this->addFlash('error', $oauthException->getMessage()); |
72
|
|
|
//return $this->redirectToRoute('homepage'); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// Save the request token to the session. |
76
|
|
|
$session->set('oauth_request_token', $token); |
77
|
|
|
return new RedirectResponse($next); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Receive authentication credentials back from the Oauth wiki. |
82
|
|
|
* @Route("/oauth_callback", name="oauth_callback") |
83
|
|
|
* @Route("/oauthredirector.php", name="old_oauth_callback") |
84
|
|
|
* @param Request $request The HTTP request. |
85
|
|
|
* @param SessionInterface $session |
86
|
|
|
* @param ProjectRepository $projectRepo |
87
|
|
|
* @return RedirectResponse |
88
|
|
|
*/ |
89
|
|
|
public function oauthCallbackAction( |
90
|
|
|
Request $request, |
91
|
|
|
SessionInterface $session, |
92
|
|
|
ProjectRepository $projectRepo |
93
|
|
|
): RedirectResponse { |
94
|
|
|
// Give up if the required GET params don't exist. |
95
|
|
|
if (!$request->get('oauth_verifier')) { |
96
|
|
|
throw $this->createNotFoundException('No OAuth verifier given.'); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
// Complete authentication. |
100
|
|
|
$client = $this->getOauthClient($request, $projectRepo); |
101
|
|
|
$token = $session->get('oauth_request_token'); |
102
|
|
|
|
103
|
|
|
if (!is_a($token, Token::class)) { |
104
|
|
|
$this->addFlashMessage('notice', 'error-login'); |
105
|
|
|
return $this->redirectToRoute('homepage'); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
$verifier = $request->get('oauth_verifier'); |
109
|
|
|
$accessToken = $client->complete($token, $verifier); |
110
|
|
|
|
111
|
|
|
// Store access token, and remove request token. |
112
|
|
|
$session->set('oauth_access_token', $accessToken); |
113
|
|
|
$session->remove('oauth_request_token'); |
114
|
|
|
|
115
|
|
|
// Store user identity. |
116
|
|
|
$ident = $client->identify($accessToken); |
117
|
|
|
$session->set('logged_in_user', $ident); |
118
|
|
|
|
119
|
|
|
// Store reference to the client. |
120
|
|
|
$session->set('oauth_client', $this->oauthClient); |
121
|
|
|
|
122
|
|
|
// Redirect to callback, if given. |
123
|
|
|
if ($request->query->get('redirect')) { |
124
|
|
|
return $this->redirect($request->query->get('redirect')); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
// Send back to homepage. |
128
|
|
|
return $this->redirectToRoute('homepage'); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Get an OAuth client, configured to the default project. |
133
|
|
|
* (This shouldn't really be in this class, but oh well.) |
134
|
|
|
* @param Request $request |
135
|
|
|
* @param ProjectRepository $projectRepo |
136
|
|
|
* @return Client |
137
|
|
|
* @codeCoverageIgnore |
138
|
|
|
*/ |
139
|
|
|
protected function getOauthClient(Request $request, ProjectRepository $projectRepo): Client |
140
|
|
|
{ |
141
|
|
|
if (isset($this->oauthClient)) { |
142
|
|
|
return $this->oauthClient; |
143
|
|
|
} |
144
|
|
|
$defaultProject = $projectRepo->getProject( |
145
|
|
|
$this->getParameter('central_auth_project') |
146
|
|
|
); |
147
|
|
|
$endpoint = $defaultProject->getUrl(false) |
148
|
|
|
. $defaultProject->getScript() |
149
|
|
|
. '?title=Special:OAuth'; |
150
|
|
|
$conf = new ClientConfig($endpoint); |
151
|
|
|
$consumerKey = $this->getParameter('oauth_key'); |
152
|
|
|
$consumerSecret = $this->getParameter('oauth_secret'); |
153
|
|
|
$conf->setConsumer(new Consumer($consumerKey, $consumerSecret)); |
154
|
|
|
$this->oauthClient = new Client($conf); |
155
|
|
|
|
156
|
|
|
// Set the callback URL if given. Used to redirect back to target page after logging in. |
157
|
|
|
if ($request->query->get('callback')) { |
158
|
|
|
$this->oauthClient->setCallback($request->query->get('callback')); |
|
|
|
|
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return $this->oauthClient; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Log out the user and return to the homepage. |
166
|
|
|
* @Route("/logout", name="logout") |
167
|
|
|
* @param SessionInterface $session |
168
|
|
|
* @return RedirectResponse |
169
|
|
|
*/ |
170
|
|
|
public function logoutAction(SessionInterface $session): RedirectResponse |
171
|
|
|
{ |
172
|
|
|
$session->invalidate(); |
173
|
|
|
return $this->redirectToRoute('homepage'); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/************************ API endpoints ************************/ |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Get domain name, URL, and API URL of the given project. |
180
|
|
|
* @Route("/api/project/normalize/{project}", name="ProjectApiNormalize") |
181
|
|
|
* @return JsonResponse |
182
|
|
|
*/ |
183
|
|
|
public function normalizeProjectApiAction(): JsonResponse |
184
|
|
|
{ |
185
|
|
|
return $this->getFormattedApiResponse([ |
186
|
|
|
'domain' => $this->project->getDomain(), |
187
|
|
|
'url' => $this->project->getUrl(), |
188
|
|
|
'api' => $this->project->getApiUrl(), |
189
|
|
|
'database' => $this->project->getDatabaseName(), |
190
|
|
|
]); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Get all namespaces of the given project. This endpoint also does the same thing |
195
|
|
|
* as the /project/normalize endpoint, returning other basic info about the project. |
196
|
|
|
* @Route("/api/project/namespaces/{project}", name="ProjectApiNamespaces") |
197
|
|
|
* @return JsonResponse |
198
|
|
|
*/ |
199
|
|
|
public function namespacesApiAction(): JsonResponse |
200
|
|
|
{ |
201
|
|
|
return $this->getFormattedApiResponse([ |
202
|
|
|
'domain' => $this->project->getDomain(), |
203
|
|
|
'url' => $this->project->getUrl(), |
204
|
|
|
'api' => $this->project->getApiUrl(), |
205
|
|
|
'database' => $this->project->getDatabaseName(), |
206
|
|
|
'namespaces' => $this->project->getNamespaces(), |
207
|
|
|
]); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Get assessment data for a given project. |
212
|
|
|
* @Route("/api/project/assessments/{project}", name="ProjectApiAssessments") |
213
|
|
|
* @return JsonResponse |
214
|
|
|
*/ |
215
|
|
|
public function projectAssessmentsApiAction(): JsonResponse |
216
|
|
|
{ |
217
|
|
|
return $this->getFormattedApiResponse([ |
218
|
|
|
'project' => $this->project->getDomain(), |
219
|
|
|
'assessments' => $this->project->getPageAssessments()->getConfig(), |
220
|
|
|
]); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Get assessment data for all projects. |
225
|
|
|
* @Route("/api/project/assessments", name="ApiAssessmentsConfig") |
226
|
|
|
* @return JsonResponse |
227
|
|
|
*/ |
228
|
|
|
public function assessmentsConfigApiAction(): JsonResponse |
229
|
|
|
{ |
230
|
|
|
// Here there is no Project, so we don't use XtoolsController::getFormattedApiResponse(). |
231
|
|
|
$response = new JsonResponse(); |
232
|
|
|
$response->setEncodingOptions(JSON_NUMERIC_CHECK); |
233
|
|
|
$response->setStatusCode(Response::HTTP_OK); |
234
|
|
|
$response->setData([ |
235
|
|
|
'projects' => array_keys($this->getParameter('assessments')), |
236
|
|
|
'config' => $this->getParameter('assessments'), |
237
|
|
|
]); |
238
|
|
|
|
239
|
|
|
return $response; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Transform given wikitext to HTML using the XTools parser. Wikitext must be passed in as the query 'wikitext'. |
244
|
|
|
* @Route("/api/project/parser/{project}") |
245
|
|
|
* @return JsonResponse Safe HTML. |
246
|
|
|
*/ |
247
|
|
|
public function wikifyApiAction(): JsonResponse |
248
|
|
|
{ |
249
|
|
|
return new JsonResponse( |
250
|
|
|
Edit::wikifyString($this->request->query->get('wikitext', ''), $this->project) |
251
|
|
|
); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
$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
in vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/ParameterBag.php on line 75
Used in request-header context
in src/Controller/DefaultController.php on line 158
$url
in vendor/mediawiki/oauthclient/src/Client.php on line 121
$url
is assigned to property Client::$callbackUrlin vendor/mediawiki/oauthclient/src/Client.php on line 122
in vendor/mediawiki/oauthclient/src/Client.php on line 135
urlencode()
in vendor/mediawiki/oauthclient/src/Client.php on line 135
$this->config->endpointURL . '/initiate&format=json&oauth_callback=' . urlencode($this->callbackUrl)
is assigned to$initUrl
in vendor/mediawiki/oauthclient/src/Client.php on line 133
in vendor/mediawiki/oauthclient/src/Client.php on line 136
$url
in vendor/mediawiki/oauthclient/src/Client.php on line 222
in vendor/mediawiki/oauthclient/src/Client.php on line 249
$url
in vendor/mediawiki/oauthclient/src/Client.php on line 265
curl_setopt()
is calledin vendor/mediawiki/oauthclient/src/Client.php on line 268
General Strategies to prevent injection
In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:
For numeric data, we recommend to explicitly cast the data: