Passed
Push — v3 ( 836575...105154 )
by
unknown
02:40
created

Eos   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Test Coverage

Coverage 78.08%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 18
eloc 76
c 2
b 0
f 0
dl 0
loc 247
ccs 57
cts 73
cp 0.7808
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttribute() 0 3 2
A httpRequest() 0 19 2
A authenticate() 0 26 4
A beforeSend() 0 14 2
A deviceAuthentication() 0 12 1
A queryServers() 0 23 2
A processResponse() 0 13 5
1
<?php
2
3
/**
4
 * This file is part of GameQ.
5
 *
6
 * GameQ is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GameQ is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace GameQ\Protocols;
21
22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Server;
24
25
/**
26
 * Epic Online Services Protocol Class
27
 *
28
 * Serves as a base class for EOS-powered games.
29
 *
30
 * @package GameQ\Protocols
31
 * @author  H.Rouatbi
32
 */
33
class Eos extends Http
34
{
35
    /**
36
     * The protocol being used
37
     *
38
     * @var string
39
     */
40
    protected $protocol = 'eos';
41
42
    /**
43
     * Longer string name of this protocol class
44
     *
45
     * @var string
46
     */
47
    protected $name_long = 'Epic Online Services';
48
49
    /**
50
     * String name of this protocol class
51
     *
52
     * @var string
53
     */
54
    protected $name = 'eos';
55
56
    /**
57
     * Grant type used for authentication
58
     *
59
     * @var string
60
     */
61
    protected $grant_type = 'client_credentials';
62
63
    /**
64
     * Deployment ID for the game or application
65
     *
66
     * @var string
67
     */
68
    protected $deployment_id = null;
69
70
    /**
71
     * User ID for authentication
72
     *
73
     * @var string
74
     */
75
    protected $user_id = null;
76
77
    /**
78
     * User secret key for authentication
79
     *
80
     * @var string
81
     */
82
    protected $user_secret = null;
83
84
    /**
85
     * Holds the server ip so we can overwrite it back
86
     *
87
     * @var string
88
     */
89
    protected $serverIp = null;
90
91
    /**
92
     * Holds the server port query so we can overwrite it back
93
     *
94
     * @var string
95
     */
96
    protected $serverPortQuery = null;
97
98
    /**
99
     * Normalize some items
100
     *
101
     * @var array
102
     */
103
    protected $normalize = [
104
        // General
105
        'general' => [
106
            // target       => source
107
            'hostname'   => 'hostname',
108
            'mapname'    => 'mapname',
109
            'maxplayers' => 'maxplayers',
110
            'numplayers' => 'numplayers',
111
            'password'   => 'password',
112
        ]
113
    ];
114
115
    /**
116
     * Process the response from the EOS API
117
     *
118
     * @return array
119
     * @throws Exception
120
     */
121 24
    public function processResponse()
122
    {
123 24
        $index = ($this->grant_type === 'external_auth') ? 2 : 1;
124 24
        $server_data = isset($this->packets_response[$index]) ? json_decode($this->packets_response[$index], true) : null;
125
126 24
        $server_data = isset($server_data['sessions']) ? $server_data['sessions'] : null;
127
128
        // If no server data, throw an exception
129 24
        if (empty($server_data)) {
130 6
            throw new Exception('No server data found. Server might be offline.');
131
        }
132
133 18
        return $server_data;
134
    }
135
136
    /**
137
     * Called before sending the request
138
     *
139
     * @param Server $server
140
     */
141 24
    public function beforeSend($server)
142
    {
143 24
        $this->serverIp = $server->ip();
144 24
        $this->serverPortQuery = $server->portQuery();
145
146
        // Authenticate and get the access token
147 24
        $auth_token = $this->authenticate();
148
149 24
        if (!$auth_token) {
150 6
            return;
151
        }
152
153
        // Query for server data
154 18
        $this->queryServers($auth_token);
155
    }
156
157
    /**
158
     * Authenticate to get the access token
159
     *
160
     * @return string|null
161
     */
162 24
    protected function authenticate()
163
    {
164 24
        $auth_url = "https://api.epicgames.dev/auth/v1/oauth/token";
165 24
        $auth_headers = [
166 24
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
167 24
            'Accept-Encoding: deflate, gzip',
168 24
            'Content-Type: application/x-www-form-urlencoded',
169 24
        ];
170
171 24
        $auth_postfields = "grant_type={$this->grant_type}&deployment_id={$this->deployment_id}";
172
173 24
        if ($this->grant_type === 'external_auth') {
174
            // Perform device authentication if necessary
175
            $device_auth = $this->deviceAuthentication();
176
            if (!$device_auth) {
177
                return null;
178
            }
179
            $auth_postfields .= "&external_auth_type=deviceid_access_token"
180
                            . "&external_auth_token={$device_auth['access_token']}"
181
                            . "&nonce=ABCHFA3qgUCJ1XTPAoGDEF&display_name=User";
182
        }
183
184
        // Make the request to get the access token
185 24
        $response = $this->httpRequest($auth_url, $auth_headers, $auth_postfields);
186
187 24
        return isset($response['access_token']) ? $response['access_token'] : null;
188
    }
189
190
    /**
191
     * Query the EOS server for matchmaking data
192
     *
193
     * @param string $auth_token
194
     * @return array|null
195
     */
196 18
    protected function queryServers($auth_token)
197
    {
198 18
        $server_query_url = "https://api.epicgames.dev/matchmaking/v1/{$this->deployment_id}/filter";
199 18
        $query_headers = [
200 18
            "Authorization: Bearer {$auth_token}",
201 18
            'Accept: application/json',
202 18
            'Content-Type: application/json',
203 18
        ];
204
205 18
        $query_body = json_encode([
206 18
            'criteria' => [
207 18
                [
208 18
                    'key' => 'attributes.ADDRESS_s',
209 18
                    'op' => 'EQUAL',
210 18
                    'value' => $this->serverIp,
211 18
                ],
212 18
            ],
213 18
            'maxResults' => 200,
214 18
        ]);
215
216 18
        $response = $this->httpRequest($server_query_url, $query_headers, $query_body);
217
218 18
        return isset($response['sessions']) ? $response['sessions'] : null;
219
    }
220
221
    /**
222
     * Handle device authentication for external auth type
223
     *
224
     * @return array|null
225
     */
226
    protected function deviceAuthentication()
227
    {
228
        $device_auth_url = "https://api.epicgames.dev/auth/v1/accounts/deviceid";
229
        $device_auth_headers = [
230
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
231
            'Accept-Encoding: deflate, gzip',
232
            'Content-Type: application/x-www-form-urlencoded',
233
        ];
234
235
        $device_auth_postfields = "deviceModel=PC";
236
237
        return $this->httpRequest($device_auth_url, $device_auth_headers, $device_auth_postfields);
238
    }
239
240
    /**
241
     * Execute an HTTP request
242
     *
243
     * @param string $url
244
     * @param array $headers
245
     * @param string $postfields
246
     * @return array|null
247
     */
248 24
    protected function httpRequest($url, $headers, $postfields)
249
    {
250 24
        $ch = curl_init();
251
252 24
        curl_setopt($ch, CURLOPT_URL, $url);
253 24
        curl_setopt($ch, CURLOPT_POST, 1);
254 24
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
255 24
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
256 24
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
257
258 24
        $response = curl_exec($ch);
259
260 24
        if (!$response) {
261
            return null;
262
        }
263
264 24
        $this->packets_response[] = $response;
265
266 24
        return json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() 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 ignore-type  annotation

266
        return json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
267
    }
268
269
    /**
270
     * Safely retrieves an attribute from an array or returns a default value.
271
     *
272
     * @param array $attributes
273
     * @param string $key
274
     * @param mixed $default
275
     * @return mixed
276
     */
277 18
    protected function getAttribute(array $attributes, string $key, $default = null)
278
    {
279 18
        return isset($attributes[$key]) ? $attributes[$key] : $default;
280
    }
281
}
282