Completed
Push — master ( 44bbd6...1490fc )
by Lawrence
01:19
created

Server::checkAllowedIps()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace Plinker\Core;
4
5
/**
6
 * Server endpoint class.
7
 */
8
class Server
9
{
10
    private $post = [];
11
    private $config = [];
12
    private $publicKey = '';
13
    private $privateKey = '';
14
15
    /**
16
     * @param string $post
0 ignored issues
show
Documentation introduced by
Should the type for parameter $post not be string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
17
     * @param string $publicKey
18
     * @param string $privateKey
19
     * @param array  $config
20
     */
21
    public function __construct(
22
        $post = [],
23
        $publicKey = '',
24
        $privateKey = '',
25
        $config = []
26
    ) {
27
        // define vars
28
        $this->post = $post;
0 ignored issues
show
Documentation Bug introduced by
It seems like $post can also be of type string. However, the property $post is declared as type array. 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...
29
        $this->config = $config;
30
        $this->publicKey = hash('sha256', gmdate('h').$publicKey);
31
        $this->privateKey = hash('sha256', gmdate('h').$privateKey);
32
33
        // init signer
34
        $this->signer = new Signer(
0 ignored issues
show
Bug introduced by
The property signer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
35
            $this->publicKey,
36
            $this->privateKey,
37
            (!empty($this->post['encrypt']) ? true : false)
38
        );
39
    }
40
    
41
    /**
42
     * Check client IP is in allowed list if allowed list is set.
43
     *
44
     * @return bool
45
     */
46
    final private function checkAllowedIps()
0 ignored issues
show
Coding Style introduced by
function checkAllowedIps() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Coding Style introduced by
checkAllowedIps uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
47
    {
48
        return !(
49
            !empty($this->config['allowed_ips']) &&
50
            !in_array($_SERVER['REMOTE_ADDR'], $this->config['allowed_ips'])
51
        );
52
    }
53
    
54
    /**
55
     * Check client IP is in allowed list if allowed list is set.
56
     *
57
     * @return bool
58
     */
59
    final private function verifyRequestToken()
0 ignored issues
show
Coding Style introduced by
function verifyRequestToken() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Coding Style introduced by
verifyRequestToken uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
60
    {
61
        return !(
62
            empty($this->post['token']) ||
63
            empty($_SERVER['HTTP_TOKEN']) ||
64
            hash_hmac('sha256', $this->post['token'], $this->privateKey) != $_SERVER['HTTP_TOKEN']
65
        );
66
    }
67
68
    /**
69
     * Server exection method.
70
     *
71
     * @return string
72
     */
73
    public function execute()
0 ignored issues
show
Coding Style introduced by
execute uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
74
    {
75
        // set response header
76
        header('Content-Type: text/plain; charset=utf-8');
77
78
        // check allowed ips
79
        if (!$this->checkAllowedIps()) {
80
            return serialize($this->signer->encode([
81
                'response' => [
82
                    'error' => 'IP not in allowed list: '.$_SERVER['REMOTE_ADDR'],
83
                ]
84
            ]));
85
        }
86
87
        // verify request token
88
        if (!$this->verifyRequestToken()) {
89
            return serialize($this->signer->encode([
90
                'response' => [
91
                    'error' => 'invalid request token',
92
                ]
93
            ]));
94
        }
95
96
        // decode post payload
97
        $data = $this->signer->decode(
98
            $this->post
99
        );
100
101
        // check client post is an array
102
        if (!is_array($data)) {
103
            return serialize($data);
104
        }
105
106
        // check data params array or set
107
        if (!isset($data['params'])) {
108
            $data['params'] = [];
109
        }
110
111
        // check data config array, set into scope
112
        if (!empty($data['config'])) {
113
            $this->config = $data['config'];
114
        }
115
116
        // check for empty
117
        if (empty($data['component']) || empty($data['action'])) {
118
            $error = empty($data['component']) ? 'component class' : 'action';
119
            return serialize($this->signer->encode([
120
                'response' => [
121
                    'error' => $error.' cannot be empty',
122
                ]
123
            ]));
124
        }
125
126
        $class = '\\Plinker\\'.$data['component'];
127
128
        if (class_exists($class)) {
129
            $componentClass = new $class($this->config + $data + $this->post);
130
131
            if (method_exists($componentClass, $data['action'])) {
132
                $return = call_user_func(
133
                    [
134
                        $componentClass,
135
                        $data['action']
136
                    ],
137
                    $data['params']
138
                );
139
            } else {
140
                $return = 'action not implemented';
141
            }
142
        } else {
143
            $return = 'not implemented';
144
        }
145
146
        return serialize($this->signer->encode([
147
            'response' => $return,
148
        ]));
149
    }
150
}
151