Passed
Pull Request — v3 (#731)
by
unknown
33:01
created

Eos::httpRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 17
rs 9.9332
cc 2
nc 2
nop 3
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
    public function processResponse()
122
    {
123
        // Authenticate and get the access token
124
        $auth_token = $this->authenticate();
125
126
        // If authentication failed, throw an exception
127
        if (!$auth_token) {
128
            throw new Exception('Failed to authenticate with EOS API.');
129
        }
130
131
        // Query for server data
132
        $server_data = $this->queryServers($auth_token);
133
134
        // If no server data, throw an exception
135
        if (empty($server_data)) {
136
            throw new Exception('No server data found. Server might be offline.');
137
        }
138
139
        return $server_data;
140
    }
141
142
    /**
143
     * Called before sending the request
144
     *
145
     * @param Server $server
146
     */
147
    public function beforeSend($server)
148
    {
149
        $this->serverIp = $server->ip();
150
        $this->serverPortQuery = $server->portQuery();
151
    }
152
153
    /**
154
     * Authenticate to get the access token
155
     *
156
     * @return string|null
157
     */
158
    protected function authenticate()
159
    {
160
        $auth_url = "https://api.epicgames.dev/auth/v1/oauth/token";
161
        $auth_headers = [
162
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
163
            'Accept-Encoding: deflate, gzip',
164
            'Content-Type: application/x-www-form-urlencoded',
165
        ];
166
167
        $auth_postfields = "grant_type={$this->grant_type}&deployment_id={$this->deployment_id}";
168
169
        if ($this->grant_type === 'external_auth') {
170
            // Perform device authentication if necessary
171
            $device_auth = $this->deviceAuthentication();
172
            if (!$device_auth) {
173
                return null;
174
            }
175
            $auth_postfields .= "&external_auth_type=deviceid_access_token"
176
                            . "&external_auth_token={$device_auth['access_token']}"
177
                            . "&nonce=ABCHFA3qgUCJ1XTPAoGDEF&display_name=User";
178
        }
179
180
        // Make the request to get the access token
181
        $response = $this->httpRequest($auth_url, $auth_headers, $auth_postfields);
182
183
        return isset($response['access_token']) ? $response['access_token'] : null;
184
    }
185
186
    /**
187
     * Query the EOS server for matchmaking data
188
     *
189
     * @param string $auth_token
190
     * @return array|null
191
     */
192
    protected function queryServers($auth_token)
193
    {
194
        $server_query_url = "https://api.epicgames.dev/matchmaking/v1/{$this->deployment_id}/filter";
195
        $query_headers = [
196
            "Authorization: Bearer {$auth_token}",
197
            'Accept: application/json',
198
            'Content-Type: application/json',
199
        ];
200
201
        $query_body = json_encode([
202
            'criteria' => [
203
                [
204
                    'key' => 'attributes.ADDRESS_s',
205
                    'op' => 'EQUAL',
206
                    'value' => $this->serverIp,
207
                ],
208
            ],
209
            'maxResults' => 200,
210
        ]);
211
212
        $response = $this->httpRequest($server_query_url, $query_headers, $query_body);
213
214
        return isset($response['sessions']) ? $response['sessions'] : null;
215
    }
216
217
    /**
218
     * Handle device authentication for external auth type
219
     *
220
     * @return array|null
221
     */
222
    protected function deviceAuthentication()
223
    {
224
        $device_auth_url = "https://api.epicgames.dev/auth/v1/accounts/deviceid";
225
        $device_auth_headers = [
226
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
227
            'Accept-Encoding: deflate, gzip',
228
            'Content-Type: application/x-www-form-urlencoded',
229
        ];
230
231
        $device_auth_postfields = "deviceModel=PC";
232
233
        return $this->httpRequest($device_auth_url, $device_auth_headers, $device_auth_postfields);
234
    }
235
236
    /**
237
     * Execute an HTTP request
238
     *
239
     * @param string $url
240
     * @param array $headers
241
     * @param string $postfields
242
     * @return array|null
243
     */
244
    protected function httpRequest($url, $headers, $postfields)
245
    {
246
        $ch = curl_init();
247
248
        curl_setopt($ch, CURLOPT_URL, $url);
249
        curl_setopt($ch, CURLOPT_POST, 1);
250
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
251
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
252
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
253
254
        $response = curl_exec($ch);
255
256
        if (!$response) {
257
            return null;
258
        }
259
260
        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

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