Passed
Push — master ( 429b06...f0c7c5 )
by Rafael
08:46
created

JWTGraphiQLAuthentication::requireUserData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
/*******************************************************************************
3
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\GraphiQL;
12
13
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
14
use Symfony\Component\Form\FormBuilderInterface;
15
use Symfony\Component\Form\FormInterface;
16
use Symfony\Component\HttpFoundation\Session\Session;
17
use Symfony\Component\Routing\Router;
18
use function JmesPath\search;
0 ignored issues
show
introduced by
The function JmesPath\search was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
19
20
/**
21
 * JWTGraphiQLAuthentication
22
 */
23
class JWTGraphiQLAuthentication implements GraphiQLAuthenticationProviderInterface
24
{
25
    private const SESSION_PATH = 'graphiql_jwt_api_token';
26
27
    /**
28
     * @var Router
29
     */
30
    protected $router;
31
32
    /**
33
     * @var Session
34
     */
35
    protected $session;
36
37
    /**
38
     * @var array
39
     */
40
    protected $config = [];
41
42
    /**
43
     * @param Router  $router
44
     * @param Session $session
45
     * @param array   $config
46
     */
47
    public function __construct(Router $router, Session $session, array $config)
48
    {
49
        $this->config = $config;
50
        $this->router = $router;
51
        $this->session = $session;
52
    }
53
54
    /**
55
     * {@inheritDoc}
56
     */
57
    public function requireUserData(): bool
58
    {
59
        return true;
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65
    public function buildUserForm(FormBuilderInterface $builder)
66
    {
67
        $builder
68
            ->add(
69
                $this->config['login']['username_parameter'] ?? 'username',
70
                null,
71
                [
72
                    'label' => 'Username',
73
                ]
74
            )
75
            ->add(
76
                $this->config['login']['password_parameter'] ?? 'password',
77
                PasswordType::class,
78
                [
79
                    'label' => 'Password',
80
                ]
81
            );
82
    }
83
84
    /**
85
     * @param null|FormInterface $form
86
     *
87
     * @throws \RuntimeException
88
     * @throws AuthenticationFailedException
89
     */
90
    public function login(?FormInterface $form = null)
91
    {
92
        if (!$form) {
93
            throw new \RuntimeException('This provider require a form');
94
        }
95
96
        $url = $this->config['login']['url'] ?? null;
97
        if ($this->router->getRouteCollection()->get($url)) {
98
            $url = $this->router->generate($url, [], Router::ABSOLUTE_URL);
99
        }
100
101
        $opts = [
102
            CURLOPT_CONNECTTIMEOUT => 10,
103
            CURLOPT_RETURNTRANSFER => true,
104
            CURLOPT_HEADER => true,
105
            CURLOPT_TIMEOUT => 60,
106
            CURLOPT_HTTPHEADER => [],
107
        ];
108
109
        $paramsIn = $this->config['login']['parameters_in'] ?? 'form';
110
        if ('header' === $paramsIn) {
111
            $opts[CURLOPT_HEADER] = true;
112
            $headers = [];
113
            foreach ($form->getData() as $key => $value) {
114
                $headers[] = sprintf('%s: %s', $key, $value);
115
            }
116
            $opts[CURLOPT_HTTPHEADER] = array_merge(["Accept: application/json"], $headers);
117
        } elseif ('query' === $paramsIn) {
118
            $url .= '?'.http_build_query($form->getData(), null, '&');
119
        } else {
120
            $opts[CURLOPT_POSTFIELDS] = http_build_query($form->getData(), null, '&');
121
            $opts[CURLOPT_POST] = true;
122
        }
123
124
        $opts[CURLOPT_URL] = $url;
125
126
        $ch = curl_init();
127
        curl_setopt_array($ch, $opts);
128
        $result = curl_exec($ch);
129
130
        // Split the HTTP response into header and body.
131
        try {
132
            list($headers, $body) = explode("\r\n\r\n", $result);
133
            $headers = explode("\r\n", $headers);
0 ignored issues
show
Bug introduced by
$headers of type array|string[] is incompatible with the type string expected by parameter $string of explode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
            $headers = explode("\r\n", /** @scrutinizer ignore-type */ $headers);
Loading history...
134
        } catch (\ErrorException $exception) {
135
            throw new AuthenticationFailedException('Authentication Failed');
136
        }
137
138
        // We catch HTTP/1.1 4xx or HTTP/1.1 5xx error response.
139
        if (strpos($headers[0], 'HTTP/1.1 4') !== false || strpos($headers[0], 'HTTP/1.1 5') !== false) {
140
            $result = [
141
                'code' => 0,
142
                'message' => '',
143
            ];
144
145
            if (preg_match('/^HTTP\/1.1 ([0-9]{3,3}) (.*)$/', $headers[0], $matches)) {
146
                $result['code'] = $matches[1];
147
                $result['message'] = $matches[2];
148
            }
149
150
            // In case retrun with WWW-Authenticate replace the description.
151
            foreach ($headers as $header) {
152
                if (preg_match("/^WWW-Authenticate:.*error='(.*)'/", $header, $matches)) {
153
                    $result['message'] = $matches[1];
154
                }
155
            }
156
157
            throw new AuthenticationFailedException($result['message'] ?: 'Authentication Failed', $result['code'] ?: 401);
158
        }
159
160
        $tokenPath = $this->config['login']['response_token_path'] ?? 'token';
161
        $token = $body;
162
163
        if ($tokenPath) {
164
            $token = search($tokenPath, @json_decode($body));
165
        }
166
167
        if (!$token) {
168
            throw new AuthenticationFailedException('Authentication Failed');
169
        }
170
171
        $this->session->set(self::SESSION_PATH, $token);
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177
    public function logout()
178
    {
179
        $this->session->remove(self::SESSION_PATH);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function isAuthenticated(): bool
186
    {
187
        return $this->session->has(self::SESSION_PATH) && $this->session->get(self::SESSION_PATH);
188
    }
189
190
    /**
191
     * {@inheritDoc}
192
     */
193
    public function prepareRequest(GraphiQLRequest $request)
194
    {
195
        $token = null;
196
        if ($this->isAuthenticated()) {
197
            $token = $this->session->get(self::SESSION_PATH);
198
            if ('header' === $this->config['requests']['token_in']) {
199
                $token = str_replace('{token}', $token, $this->config['requests']['token_template']);
200
            }
201
        }
202
203
        $tokenName = $this->config['requests']['token_name'];
204
        if ('header' === $this->config['requests']['token_in']) {
205
            $request->addHeader($tokenName, $token);
206
        } else {
207
            $request->addParameter($tokenName, $token);
208
        }
209
    }
210
}
211