Passed
Push — 2.x ( 60303b...35f371 )
by Terry
01:57
created

Panel::getRoutes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 32
rs 9.472
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 * 
10
 * php version 7.1.0
11
 * 
12
 * @category  Web-security
13
 * @package   Shieldon
14
 * @author    Terry Lin <[email protected]>
15
 * @copyright 2019 terrylinooo
16
 * @license   https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT
17
 * @link      https://github.com/terrylinooo/shieldon
18
 * @see       https://shieldon.io
19
 */
20
21
declare(strict_types=1);
22
23
namespace Shieldon\Firewall;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Shieldon\Firewall\HttpResolver;
27
use Shieldon\Firewall\Panel\CsrfTrait;
28
use Shieldon\Firewall\Panel\DemoModeTrait;
29
use Shieldon\Firewall\Panel\User;
30
use Shieldon\Firewall\Container;
31
use Shieldon\Firewall\Firewall;
32
use RuntimeException;
33
use function Shieldon\Firewall\get_request;
34
use function Shieldon\Firewall\get_response;
35
use function call_user_func;
36
use function explode;
37
use function in_array;
38
use function property_exists;
39
use function str_replace;
40
use function trim;
41
use function ucfirst;
42
43
/**
44
 * Firewall's Control Panel
45
 *
46
 * Display a Control Panel UI for developers or administrators.
47
 */
48
class Panel
49
{
50
    /**
51
     *   Public methods       | Desctiotion
52
     *  ----------------------|---------------------------------------------
53
     *   __call               | Magic method. Let property can run as a method.
54
     *   entry                | Initialize the entry point of the control panel
55
     *  ----------------------|---------------------------------------------
56
     */
57
58
    /**
59
     *   Public methods       | Desctiotion
60
     *  ----------------------|---------------------------------------------
61
     *   demo                 | Start a demo mode. Setting fields are hidden.
62
     *  ----------------------|---------------------------------------------
63
     */
64
    use DemoModeTrait;
65
66
    /**
67
     *   Public methods       | Desctiotion
68
     *  ----------------------|---------------------------------------------
69
     *   csrf                 | Receive the CSRF name and token from the App.
70
     *   setCsrfField         | Set CSRF input fields.
71
     *   fieldCsrf            | Output HTML input element with CSRF token.
72
     *  ----------------------|---------------------------------------------
73
     */
74
    use CsrfTrait;
75
76
    /**
77
     * Route map.
78
     *
79
     * @var array
80
     */
81
    protected $registerRoutes;
82
83
    /**
84
     * The HTTP resolver.
85
     * 
86
     * We need to resolve the HTTP result by ourselves to prevent conficts
87
     * with other frameworks.
88
     *
89
     * @var \Shieldon\Firewall\HttpResolver
90
     */
91
    protected $resolver = null;
92
93
    /**
94
     * Firewall panel constructor.                         
95
     */
96
    public function __construct() 
97
    {
98
        $this->registerRoutes = self::getRoutes();
99
        $this->resolver = new HttpResolver();
100
    }
101
102
    /**
103
     * Display pages.
104
     *
105
     * @return void
106
     */
107
    public function entry(): void
108
    {
109
        $firewall = $this->getFirewallInstance();
110
111
        $response = get_response();
112
113
        /**
114
         * Ex: /firewall/panel/user/login/
115
         *   => firewall/panel/user/login
116
         */
117
        $path = $firewall->getKernel()->getCurrentUrl();
118
        $path = trim($path, '/');
119
120
        /**
121
         * Ex: /firewall/panel/
122
         *   => firewall/panel
123
         */
124
        $base = $firewall->controlPanel();
125
        $base = trim($base, '/');
126
127
        /**
128
         * Ex: /user/login
129
         *   => user/login
130
         */
131
        $urlSegment = str_replace($base, '', $path);
132
        $urlSegment = trim($urlSegment, '/');
133
134
        if ($urlSegment === $base || $urlSegment === '') {
135
            $urlSegment = 'home/index';
136
        }
137
138
        $urlParts = explode('/', $urlSegment);
139
140
        $controller = $urlParts[0] ?? 'home';
141
        $method = $urlParts[1] ?? 'index';
142
143
        if (in_array($controller . '/' . $method, $this->registerRoutes)) {
144
145
            $this->setRouteBase($base);
146
            $this->checkAuth();
147
148
            $controller = __CLASS__ . '\\' . ucfirst($controller);
149
            $controllerClass = new $controller();
150
            $controllerClass->setCsrfField($this->getCsrfField());
151
152
            if ('demo' === $this->mode) {
153
                // For security reasons, the POST method is not allowed 
154
                // in the Demo mode.
155
                set_request(get_request()->withParsedBody([])->withMethod('GET'));
156
                unset_superglobal(null, 'post');
157
158
                $controllerClass->demo(
159
                    $this->demoUser['user'],
160
                    $this->demoUser['pass']
161
                );
162
            }
163
164
            $this->resolver(call_user_func([$controllerClass, $method]));
165
        }
166
167
        $this->resolver($response->withStatus(404));
168
    }
169
170
    /**
171
     * Get routes.
172
     *
173
     * @return array
174
     */
175
    public static function getRoutes(): array
176
    {
177
        return [
178
            'ajax/changeLocale',
179
            'ajax/tryMessenger',
180
            'circle/filter',
181
            'circle/rule',
182
            'circle/session',
183
            'home/index',
184
            'home/overview',
185
            'iptables/ip4',
186
            'iptables/ip4status',
187
            'iptables/ip6',
188
            'iptables/ip6status',
189
            'report/actionLog',
190
            'report/operation',
191
            'security/authentication',
192
            'security/xssProtection',
193
            'setting/basic',
194
            'setting/exclusion',
195
            'setting/export',
196
            'setting/import',
197
            'setting/ipManager',
198
            'setting/messenger',
199
            'user/login',
200
            'user/logout',
201
            // Render the static asset files for embedding.
202
            // Since 2.0, not link to shieldon-io.github.io anymore.
203
            'asset/css',
204
            'asset/js',
205
            'asset/favicon',
206
            'asset/logo',
207
        ];
208
    }
209
210
    /**
211
     * Set the base route for the panel.
212
     *
213
     * @param string $base The base path.
214
     *
215
     * @return void
216
     */
217
    protected function setRouteBase(string $base)
218
    {
219
        if (!defined('SHIELDON_PANEL_BASE')) {
220
            // @codeCoverageIgnoreStart
221
            define('SHIELDON_PANEL_BASE', $base);
222
            // @codeCoverageIgnoreEnd
223
        }
224
    }
225
226
    /**
227
     * Prompt an authorization login.
228
     *
229
     * @return void
230
     */
231
    protected function checkAuth(): void
232
    {
233
        $check = get_session_instance()->get('shieldon_user_login');
234
235
        if (empty($check)) {
236
            $user = new User();
237
            $user->setCsrfField($this->getCsrfField());
238
239
            if ($this->mode === 'demo') {
240
                $user->demo(
241
                    $this->demoUser['user'],
242
                    $this->demoUser['pass']
243
                );
244
            }
245
246
            $this->resolver($user->login());
247
        }
248
    }
249
250
    /**
251
     * Magic method.
252
     * 
253
     * Helps the property `$resolver` to work like a function.
254
     * 
255
     * @param string $method The method name.
256
     * @param array  $args   The arguments.
257
     *
258
     * @return mixed
259
     */
260
    public function __call($method, $args)
261
    {
262
        if (property_exists($this, $method)) {
263
            $callable = $this->{$method};
264
265
            if (isset($args[0]) && $args[0] instanceof ResponseInterface) {
266
                return $callable($args[0]);
267
            }
268
        }
269
        // @codeCoverageIgnoreStart
270
    }
271
    // @codeCoverageIgnoreEnd
272
273
    /**
274
     * Get Firewall instance.
275
     *
276
     * @return Firewall
277
     */
278
    private function getFirewallInstance(): Firewall
279
    {
280
        $firewall = Container::get('firewall');
281
282
        if (!($firewall instanceof Firewall)) {
283
            throw new RuntimeException(
284
                'The Firewall instance should be initialized first.'
285
            );
286
        }
287
288
        return $firewall;
289
    }
290
}
291