Server::checkAllowedIp()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
nc 2
nop 2
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
/*
3
 +------------------------------------------------------------------------+
4
 | Plinker-RPC PHP                                                        |
5
 +------------------------------------------------------------------------+
6
 | Copyright (c)2017-2018 (https://github.com/plinker-rpc/core)           |
7
 +------------------------------------------------------------------------+
8
 | This source file is subject to MIT License                             |
9
 | that is bundled with this package in the file LICENSE.                 |
10
 |                                                                        |
11
 | If you did not receive a copy of the license and are unable to         |
12
 | obtain it through the world-wide-web, please send an email             |
13
 | to [email protected] so we can send you a copy immediately.        |
14
 +------------------------------------------------------------------------+
15
 | Authors: Lawrence Cherone <[email protected]>                     |
16
 +------------------------------------------------------------------------+
17
 */
18
19
namespace Plinker\Core;
20
21
/**
22
 * Plinker\Core\Server
23
 *
24
 * @codeCoverageIgnore
25
 */
26
final class Server
27
{
28
    /**
29
     * @var
30
     */
31
    protected $input;
32
33
    /**
34
     * @var
35
     */
36
    protected $config;
37
38
    /**
39
     * @var
40
     */
41
    protected $signer;
42
    
43
    /**
44
     * @var
45
     */
46
    protected $response;
47
    
48
49
    /**
50
     * @const - error strings
51
     */
52
    const ERROR_IP            = "IP not in allowed list (%s)";
53
    const ERROR_TOKEN         = "Plinker token mismatch";
54
    const ERROR_DECODE        = "Failed to decode payload, check secret";
55
    const ERROR_USR_COMPONENT = "User component class (%s) not found";
56
    const ERROR_EXT_COMPONENT = "Component (%s) not implemented";
57
    const ERROR_ACTION        = "Component action (%s) not implemented in: %s";
58
59
    /**
60
     * Class construct
61
     *
62
     * @param  array $config - config array which holds object configuration
63
     * @return void
64
     */
65
    public function __construct($config = [])
66
    {
67
        $this->config = array_merge([
68
            "secret"      => null,
69
            "allowed_ips" => [],
70
            "classes"     => []
71
        ], $config);
72
    }
73
    
74
    /**
75
     * Listen method
76
     *
77
     * <code>
78
     *  $server->listen();
79
     * </code>
80
     *
81
     * @return string
82
     */
83
    public function listen()
84
    {
85
        $this->setInput();
86
        
87
        // load signer
88
        if (!$this->signer) {
89
            $this->signer = new Lib\Signer($this->config);
90
        }
91
92
        // check allowed ips
93
        if (!$this->checkAllowedIp($_SERVER["REMOTE_ADDR"], $this->config["allowed_ips"])) {
94
            return json_encode($this->signer->encode([
95
                "error" => sprintf(Server::ERROR_IP, $_SERVER["REMOTE_ADDR"]),
96
                "code" => 403
97
            ]), JSON_PRETTY_PRINT);
98
        }
99
100
        // check header token matches data token
101
        if (empty($this->input["token"]) || $_SERVER["HTTP_PLINKER"] != $this->input["token"]) {
102
            return json_encode($this->signer->encode([
103
                "error" => Server::ERROR_TOKEN,
104
                "code" => 422
105
            ]), JSON_PRETTY_PRINT);
106
        }
107
108
        // decode input payload
109
        $this->input = $this->signer->decode($this->input);
110
111
        // could not decode payload
112
        if ($this->input === null) {
113
            return json_encode($this->signer->encode([
114
                "error" => Server::ERROR_DECODE,
115
                "code" => 422
116
            ]), JSON_PRETTY_PRINT);
117
        }
118
119
        // import user config
120
        $this->config = array_merge(
121
            $this->config,
122
            $this->input
123
        );
124
125
        // user component
126
        if (array_key_exists($this->input["component"], $this->config["classes"])) {
127
            //
128
            $response = $this->executeUserComponent($this->input["component"], $this->input["action"]);
129
        }
130
        // core component
131
        else {
132
            $response = $this->executeCoreComponent($this->input["component"], $this->input["action"]);
133
        }
134
135
        // sign response and return
136
        return json_encode($this->signer->encode($response), JSON_PRETTY_PRINT);
137
    }
138
139
    /**
140
     * Return info about available classes
141
     *
142
     * <code>
143
     *  $client->info();
144
     * </code>
145
     *
146
     * @return array
147
     */
148
    private function info()
149
    {
150
        $response = [
151
            'class' => []
152
        ];
153
        
154
        foreach ($this->config["classes"] as $key => $val) {
155
            
156
            // addtional config
157
            $response["class"][$key]["config"] = !empty($val[1]) ? $val[1] : [];
158
            
159
            // check class file exists
160
            if (!file_exists($val[0])) {
161
                $response["class"][$key]["methods"] = [];
162
                continue;
163
            }
164
            
165
            //
166
            require($val[0]);
167
168
            $reflection = new \ReflectionClass($key);
169
170
            foreach ($reflection->getMethods() as $method) {
171
                if (!in_array($method->getName(), ["__construct"])) {
172
                    $param = [];
173
                    foreach ($method->getParameters() as $parameter) {
174
                        $param[] = $parameter->getName();
175
                    }
176
                    $response["class"][$key]["methods"][$method->getName()] = $param;
177
                }
178
            }
179
        }
180
181
        return $response;
182
    }
183
    
184
    /**
185
     * Sets inbound input value into scope
186
     *
187
     * @return void
188
     */
189
    private function setInput()
190
    {
191
        $this->input = file_get_contents("php://input");
192
        if (!empty($this->input)) {
193
            $this->input = gzinflate($this->input);
194
            $this->input = json_decode($this->input, true);
195
        }
196
    }
197
    
198
    /**
199
     * Check allowed IPs
200
     *
201
     * @return bool
202
     */
203
    private function checkAllowedIp($ip, $allowed_ips = [])
204
    {
205
        return !(!empty($allowed_ips) && !in_array($ip, $allowed_ips));
206
    }
207
    
208
    /**
209
     * Execute core component
210
     */
211
    private function executeCoreComponent($component, $action)
212
    {
213
        // define component namespace
214
        $ns = "\\Plinker\\".ucfirst($component);
215
216
        if (class_exists($ns)) {
217
            //
218
            $response = $this->execute($ns, $action);
219
        } elseif (class_exists($ns."\\".ucfirst($component))) {
220
            //
221
            $ns = "\\Plinker\\".ucfirst($component)."\\".ucfirst($component);
222
            $response = $this->execute($ns, $action);
223
        } else {
224
            if (empty($component) && $action === "info") {
225
                $response = $this->info();
226
            } else {
227
                $response = sprintf(Server::ERROR_EXT_COMPONENT, $component);
228
            }
229
        }
230
        
231
        return $response;
232
    }
233
234
    /**
235
     * Execute user component
236
     */
237
    private function executeUserComponent($component, $action)
238
    {
239
        $ns = null;
240
        
241
        //
242
        if (!empty($this->config["classes"][$component][0])) {
243
            $ns = $this->config["classes"][$component][0];
244
        }
245
246
        //
247
        if (!empty($this->config["classes"][$component][1])) {
248
            $this->config['config'] = array_merge(
249
                $this->config['config'],
250
                $this->config["classes"][$component][1]
251
            );
252
        }
253
254
        //
255
        if (!empty($ns) && !file_exists($ns)) {
256
            $this->response = serialize([
257
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
258
                "code"  => 422
259
            ]);
260
            return;
261
        }
262
263
        //
264
        require($ns);
265
266
        //
267
        if (!class_exists($component)) {
268
            $this->response = serialize([
269
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
270
                "code"  => 422
271
            ]);
272
            return;
273
        }
274
275
        //
276
        return $this->execute($component, $action);
277
    }
278
279
    /**
280
     * Execute component
281
     *
282
     * @param  string $ns      component class namespace
283
     * @param  string $action  component action
284
     * @return string
285
     */
286
    private function execute($ns, $action)
287
    {
288
        // filter out (secret, server, timeout) from construct config
289
        $config = array_filter($this->config['config'], function ($key) {
290
            return !in_array($key, ['secret', 'server', 'timeout']);
291
        }, ARRAY_FILTER_USE_KEY);
292
293
        // init component
294
        $component = new $ns($config);
295
296
        // call method, if exception return exception class
297
        try {
298
            if (method_exists($component, $action)) {
299
                $response = call_user_func_array(
300
                    [
301
                        $component,
302
                        $action
303
                    ],
304
                    $this->input["params"]
305
                );
306
            } else {
307
                $response = sprintf(Server::ERROR_ACTION, $action, $ns);
308
            }
309
            // Catch exception and return Exception\Server - PHP 5.6 compat
310
        } catch (\Exception $e) {
311
            return new Exception\Server($e->getMessage());
312
        } catch (Exception\Server $e) {
313
            return new Exception\Server($e->getMessage());
314
        }
315
316
        return $response;
317
    }
318
}
319