Passed
Pull Request — main (#442)
by MusikAnimal
08:15 queued 04:14
created

DefaultController::configAction()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 0
dl 0
loc 21
rs 9.9
c 0
b 0
f 0
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'));
0 ignored issues
show
Security Header Injection introduced by
$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

  1. ParameterBag::get() returns request data
    in vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/ParameterBag.php on line 75

Used in request-header context

  1. Client::setCallback() is called
    in src/Controller/DefaultController.php on line 158
  2. Enters via parameter $url
    in vendor/mediawiki/oauthclient/src/Client.php on line 121
  3. $url is assigned to property Client::$callbackUrl
    in vendor/mediawiki/oauthclient/src/Client.php on line 122
  4. Read from property Client::$callbackUrl
    in vendor/mediawiki/oauthclient/src/Client.php on line 135
  5. Data is passed through urlencode()
    in vendor/mediawiki/oauthclient/src/Client.php on line 135
  6. $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
  7. Client::makeOAuthCall() is called
    in vendor/mediawiki/oauthclient/src/Client.php on line 136
  8. Enters via parameter $url
    in vendor/mediawiki/oauthclient/src/Client.php on line 222
  9. Client::makeCurlCall() is called
    in vendor/mediawiki/oauthclient/src/Client.php on line 249
  10. Enters via parameter $url
    in vendor/mediawiki/oauthclient/src/Client.php on line 265
  11. curl_setopt() is called
    in 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:

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;
Loading history...
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

  1. ParameterBag::get() returns request data
    in vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/ParameterBag.php on line 75

Used in variable context

  1. Client::setCallback() is called
    in src/Controller/DefaultController.php on line 158
  2. Enters via parameter $url
    in vendor/mediawiki/oauthclient/src/Client.php on line 121
  3. $url is assigned to property Client::$callbackUrl
    in vendor/mediawiki/oauthclient/src/Client.php on line 122
  4. Read from property Client::$callbackUrl
    in vendor/mediawiki/oauthclient/src/Client.php on line 135
  5. Data is passed through urlencode()
    in vendor/mediawiki/oauthclient/src/Client.php on line 135
  6. $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
  7. Client::makeOAuthCall() is called
    in vendor/mediawiki/oauthclient/src/Client.php on line 136
  8. Enters via parameter $url
    in vendor/mediawiki/oauthclient/src/Client.php on line 222
  9. Data is passed through parse_url()
    in vendor/mediawiki/oauthclient/src/Client.php on line 227
  10. parse_url($url) is assigned to $parsed
    in vendor/mediawiki/oauthclient/src/Client.php on line 227
  11. parse_str() is called
    in vendor/mediawiki/oauthclient/src/Client.php on line 228

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:

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;
Loading history...
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