Passed
Push — v3 ( 286f4e...3249be )
by
unknown
35:12
created

Eos::processResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 0
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 data so we can overwrite it back
86
     *
87
     * @var string
88
     */
89
    protected $server_data = null;
90
91
    /**
92
     * Holds the server ip so we can overwrite it back
93
     *
94
     * @var string
95
     */
96
    protected $serverIp = null;
97
98
    /**
99
     * Holds the server port query so we can overwrite it back
100
     *
101
     * @var string
102
     */
103
    protected $serverPortQuery = null;
104
105
    /**
106
     * Normalize some items
107
     *
108
     * @var array
109
     */
110
    protected $normalize = [
111
        // General
112
        'general' => [
113
            // target       => source
114
            'hostname'   => 'hostname',
115
            'mapname'    => 'mapname',
116
            'maxplayers' => 'maxplayers',
117
            'numplayers' => 'numplayers',
118
            'password'   => 'password',
119
        ]
120
    ];
121
122
    /**
123
     * Process the response from the EOS API
124
     *
125
     * @return array
126
     * @throws Exception
127
     */
128
    public function processResponse()
129
    {
130
        // If no server data, throw an exception
131
        if (empty($this->server_data)) {
132
            throw new Exception('No server data found. Server might be offline.');
133
        }
134
        return $this->server_data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->server_data returns the type string which is incompatible with the documented return type array.
Loading history...
135
    }
136
137
    /**
138
     * Called before sending the request
139
     *
140
     * @param Server $server
141
     */
142
    public function beforeSend($server)
143
    {
144
        $this->serverIp = $server->ip();
145
        $this->serverPortQuery = $server->portQuery();
146
147
        // Authenticate and get the access token
148
        $auth_token = $this->authenticate();
149
150
        if (!$auth_token) {
151
            return;
152
        }
153
154
        // Query for server data
155
        $this->server_data = $this->queryServers($auth_token);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->queryServers($auth_token) can also be of type array. However, the property $server_data is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
156
    }
157
158
    /**
159
     * Authenticate to get the access token
160
     *
161
     * @return string|null
162
     */
163
    protected function authenticate()
164
    {
165
        $auth_url = "https://api.epicgames.dev/auth/v1/oauth/token";
166
        $auth_headers = [
167
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
168
            'Accept-Encoding: deflate, gzip',
169
            'Content-Type: application/x-www-form-urlencoded',
170
        ];
171
172
        $auth_postfields = "grant_type={$this->grant_type}&deployment_id={$this->deployment_id}";
173
174
        if ($this->grant_type === 'external_auth') {
175
            // Perform device authentication if necessary
176
            $device_auth = $this->deviceAuthentication();
177
            if (!$device_auth) {
178
                return null;
179
            }
180
            $auth_postfields .= "&external_auth_type=deviceid_access_token"
181
                            . "&external_auth_token={$device_auth['access_token']}"
182
                            . "&nonce=ABCHFA3qgUCJ1XTPAoGDEF&display_name=User";
183
        }
184
185
        // Make the request to get the access token
186
        $response = $this->httpRequest($auth_url, $auth_headers, $auth_postfields);
187
188
        return isset($response['access_token']) ? $response['access_token'] : null;
189
    }
190
191
    /**
192
     * Query the EOS server for matchmaking data
193
     *
194
     * @param string $auth_token
195
     * @return array|null
196
     */
197
    protected function queryServers($auth_token)
198
    {
199
        $server_query_url = "https://api.epicgames.dev/matchmaking/v1/{$this->deployment_id}/filter";
200
        $query_headers = [
201
            "Authorization: Bearer {$auth_token}",
202
            'Accept: application/json',
203
            'Content-Type: application/json',
204
        ];
205
206
        $query_body = json_encode([
207
            'criteria' => [
208
                [
209
                    'key' => 'attributes.ADDRESS_s',
210
                    'op' => 'EQUAL',
211
                    'value' => $this->serverIp,
212
                ],
213
            ],
214
            'maxResults' => 200,
215
        ]);
216
217
        $response = $this->httpRequest($server_query_url, $query_headers, $query_body);
218
219
        return isset($response['sessions']) ? $response['sessions'] : null;
220
    }
221
222
    /**
223
     * Handle device authentication for external auth type
224
     *
225
     * @return array|null
226
     */
227
    protected function deviceAuthentication()
228
    {
229
        $device_auth_url = "https://api.epicgames.dev/auth/v1/accounts/deviceid";
230
        $device_auth_headers = [
231
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
232
            'Accept-Encoding: deflate, gzip',
233
            'Content-Type: application/x-www-form-urlencoded',
234
        ];
235
236
        $device_auth_postfields = "deviceModel=PC";
237
238
        return $this->httpRequest($device_auth_url, $device_auth_headers, $device_auth_postfields);
239
    }
240
241
    /**
242
     * Execute an HTTP request
243
     *
244
     * @param string $url
245
     * @param array $headers
246
     * @param string $postfields
247
     * @return array|null
248
     */
249
    protected function httpRequest($url, $headers, $postfields)
250
    {
251
        $ch = curl_init();
252
253
        curl_setopt($ch, CURLOPT_URL, $url);
254
        curl_setopt($ch, CURLOPT_POST, 1);
255
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
256
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
257
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
258
259
        $response = curl_exec($ch);
260
261
        if (!$response) {
262
            return null;
263
        }
264
265
        $this->packets_response[] = $response;
266
267
        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

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