*/
55 | const FILES = [
56 | self::DEFAULT_KEY => 'blocked.html.php',
57 | BadJsonException::class => 'bad_json.html.php',
58 | BadRequestException::class => 'bad_request.html.php',
59 | BadSignatureException::class => 'bad_signature.html.php',
60 | CorruptedFileException::class => 'corrupted_file.html.php',
61 | FailedConnectionException::class => 'failed_connection.html.php',
62 | InvalidProfileException::class => 'invalid_profile.html.php',
63 | MissingConfigEntryException::class => 'missing_config_entry.html.php',
64 | MissingFileException::class => 'missing_file.html.php',
65 | ProcessingException::class => 'processing.html.php',
66 | UnknownPathException::class => 'unknown_path.html.php'
67 | ];
68 |
69 | /** @var \Exception|null */
70 | private $exception;
71 |
72 | /** @var bool */
73 | private $debug;
74 |
75 | /**
76 | * Template constructor.
77 | *
78 | * @param \Exception|null $exception
79 | * @param bool $debug
80 | */
81 | public function __construct($exception, $debug)
82 | {
83 | $this->exception = $exception;
84 | $this->debug = $debug;
85 | }
86 |
87 | /**
88 | * Evaluate and print the base template.
89 | *
90 | * @return void
91 | */
92 | public function show()
93 | {
94 | require(SHADOWD_ROOT_DIR . '/tpl/base.html.php');
95 | }
96 |
97 | /**
98 | * Return the title that matches the exception.
99 | *
100 | * @return string
101 | */
102 | public function getTitle()
103 | {
104 | if ($this->exception) {
105 | $class = get_class($this->exception);
106 | if (isset(self::TITLES[$class])) {
107 | return self::TITLES[$class];
108 | }
109 | }
110 |
111 | return self::TITLES[self::DEFAULT_KEY];
112 | }
113 |
114 | /**
115 | * Return the file that matches the exception.
116 | *
117 | * @return string
118 | */
119 | public function getFile()
120 | {
121 | if ($this->exception) {
122 | $class = get_class($this->exception);
123 | if (isset(self::FILES[$class])) {
124 | return self::FILES[$class];
125 | }
126 | }
127 |
128 | return self::FILES[self::DEFAULT_KEY];
129 | }
130 |
131 | /**
132 | * Evaluate and print the description template that matches the exception.
133 | *
134 | * @return void
135 | */
136 | public function printDescription()
137 | {
138 | require(SHADOWD_ROOT_DIR . '/tpl/' . $this->getFile());
139 | }
140 |
141 | /**
142 | * Is debug mode enabled?
143 | *
144 | * @return bool
145 | */
146 | public function isDebug()
147 | {
148 | return $this->debug;
149 | }
150 |
151 | /**
152 | * Is the error message triggered by an exception?
153 | *
154 | * @return bool
155 | */
156 | public function isException()
157 | {
158 | return !is_null($this->exception);
159 | }
160 |
161 | /**
162 | * Return the exception class.
163 | *
164 | * @return false|string
165 | */
166 | public function getExceptionClass()
167 | {
168 | return get_class($this->exception);
169 | }
170 |
171 | /**
172 | * Return the exception message.
173 | *
174 | * @return string
175 | */
176 | public function getExceptionMessage()
177 | {
178 | return $this->exception->getMessage();
179 | }
180 |
181 | /**
182 | * Return the exception stack trace.
183 | *
184 | * @return string
185 | */
186 | public function getStackTrace()
187 | {
188 | return $this->exception->getTraceAsString();
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/tests/ConfigTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This file is part of Shadow Daemon. Shadow Daemon is free software: you can
9 | * redistribute it and/or modify it under the terms of the GNU General Public
10 | * License as published by the Free Software Foundation, version 2.
11 | *
12 | * This program is distributed in the hope that it will be useful, but WITHOUT
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 | * details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with this program. If not, see .
19 | */
20 |
21 | use shadowd\Config;
22 | use shadowd\Exceptions\CorruptedFileException;
23 | use shadowd\Exceptions\MissingConfigEntryException;
24 | use shadowd\Exceptions\MissingFileException;
25 | use PHPUnit\Framework\TestCase;
26 |
27 | class ConfigTest extends TestCase {
28 | public function testConstructorCorruptedFile() {
29 | $this->expectException(CorruptedFileException::class);
30 | new Config(SHADOWD_MISC_TESTS . '/connectors_invalid.ini', '');
31 | }
32 |
33 | public function testConstructorMissingFile() {
34 | $this->expectException(MissingFileException::class);
35 | new Config(SHADOWD_MISC_TESTS . '/notfound', '');
36 | }
37 |
38 | public function testGet() {
39 | $config = new Config(SHADOWD_MISC_TESTS . '/connectors_valid.ini', 'shadowd_php');
40 |
41 | $optionalValue1 = $config->get('notfound');
42 | $this->assertFalse($optionalValue1);
43 |
44 | $optionalValue2 = $config->get('profile');
45 | $this->assertEquals('1', $optionalValue2);
46 |
47 | $requiredValue = $config->get('profile', true);
48 | $this->assertEquals('1', $requiredValue);
49 | }
50 |
51 | public function testEnvGet() {
52 | $config = new Config(SHADOWD_MISC_TESTS . '/connectors_valid.ini', 'shadowd_php');
53 |
54 | $optionalValue1 = $config->get('foo');
55 | $this->assertFalse($optionalValue1);
56 |
57 | putenv(SHADOWD_CONFIG_ENV_PREFIX . 'FOO=1');
58 | $optionalValue2 = $config->get('foo');
59 | $this->assertEquals('1', $optionalValue2);
60 | }
61 |
62 | public function testGetMissingEntry() {
63 | $config = new Config(SHADOWD_MISC_TESTS . '/connectors_valid.ini', '');
64 |
65 | $this->expectException(MissingConfigEntryException::class);
66 | $config->get('notfound', true);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/InputTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This file is part of Shadow Daemon. Shadow Daemon is free software: you can
9 | * redistribute it and/or modify it under the terms of the GNU General Public
10 | * License as published by the Free Software Foundation, version 2.
11 | *
12 | * This program is distributed in the hope that it will be useful, but WITHOUT
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 | * details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with this program. If not, see .
19 | */
20 |
21 | use shadowd\Input;
22 | use PHPUnit\Framework\TestCase;
23 |
24 | class InputTest extends TestCase {
25 | public function testGetInput() {
26 | $_GET['foo'] = 'bar';
27 | $_POST['foo'] = 'bar';
28 | $_COOKIE['foo'] = 'bar';
29 | $_SERVER['HTTP_FOO'] = 'bar';
30 | $_SERVER['foo'] = 'bar';
31 | $_FILES['foo']['name'] = 'bar';
32 |
33 | $i = new Input([]);
34 | $input = $i->getInput();
35 |
36 | $this->assertTrue(array_key_exists('GET|foo', $input));
37 | $this->assertEquals('bar', $input['GET|foo']);
38 | $this->assertTrue(array_key_exists('POST|foo', $input));
39 | $this->assertEquals('bar', $input['POST|foo']);
40 | $this->assertTrue(array_key_exists('COOKIE|foo', $input));
41 | $this->assertEquals('bar', $input['COOKIE|foo']);
42 | $this->assertTrue(array_key_exists('SERVER|HTTP_FOO', $input));
43 | $this->assertEquals('bar', $input['SERVER|HTTP_FOO']);
44 | $this->assertFalse(array_key_exists('SERVER|foo', $input));
45 | $this->assertTrue(array_key_exists('FILES|foo', $input));
46 | $this->assertEquals('bar', $input['FILES|foo']);
47 | }
48 |
49 | public function testFlatten() {
50 | $input = [
51 | 'foo' => 'bar',
52 | 'boo' => [
53 | 'quz' => 'qoz'
54 | ]
55 | ];
56 |
57 | $i = new Input([]);
58 | $flattened = $i->flatten($input);
59 |
60 | $this->assertTrue(array_key_exists('foo', $flattened));
61 | $this->assertEquals('bar', $flattened['foo']);
62 | $this->assertTrue(array_key_exists('boo|quz', $flattened));
63 | $this->assertEquals('qoz', $flattened['boo|quz']);
64 | }
65 |
66 | public function testDefuseInput() {
67 | $_GET['foo'] = 'bar';
68 | $_POST['foo'] = 'bar';
69 | $_COOKIE['foo'] = 'bar';
70 | $_SERVER['HTTP_FOO'] = 'bar';
71 | $_FILES['foo']['name'] = 'bar';
72 |
73 | $i = new Input([]);
74 | $this->assertTrue($i->defuseInput([
75 | 'GET|foo',
76 | 'POST|foo',
77 | 'COOKIE|foo',
78 | 'SERVER|HTTP_FOO',
79 | 'FILES|foo'
80 | ]));
81 |
82 | $this->assertArrayNotHasKey('foo', $_GET);
83 | $this->assertArrayNotHasKey('foo', $_POST);
84 | $this->assertArrayNotHasKey('foo', $_REQUEST);
85 | $this->assertArrayNotHasKey('foo', $_COOKIE);
86 | $this->assertArrayNotHasKey('HTTP_FOO', $_SERVER);
87 | $this->assertArrayNotHasKey('foo', $_FILES);
88 | }
89 |
90 | public function testEscapeKey() {
91 | $i = new Input([]);
92 |
93 | $this->assertEquals('foo', $i->escapeKey('foo'));
94 | $this->assertEquals('foo\\|bar', $i->escapeKey('foo|bar'));
95 | $this->assertEquals('foo\\\\\\|bar', $i->escapeKey('foo\\|bar'));
96 | $this->assertEquals('foo\\|\\|bar', $i->escapeKey('foo||bar'));
97 | $this->assertEquals('foo\\\\\\\\bar', $i->escapeKey('foo\\\\bar'));
98 | }
99 |
100 | public function testUnescapeKey() {
101 | $i = new Input([]);
102 |
103 | $this->assertEquals('foo', $i->unescapeKey('foo'));
104 | $this->assertEquals('foo|bar', $i->unescapeKey('foo\\|bar'));
105 | $this->assertEquals('foo\\bar', $i->unescapeKey('foo\\\\bar'));
106 | $this->assertEquals('foo\\|bar', $i->unescapeKey('foo\\\\\\|bar'));
107 | }
108 |
109 | public function testSplitSpath() {
110 | $i = new Input([]);
111 |
112 | $test1 = $i->splitPath('foo');
113 | $this->assertEquals(1, count($test1));
114 | $this->assertEquals('foo', $test1[0]);
115 |
116 | $test2 = $i->splitPath('foo|bar');
117 | $this->assertEquals(2, count($test2));
118 | $this->assertEquals('foo', $test2[0]);
119 | $this->assertEquals('bar', $test2[1]);
120 |
121 | $test3 = $i->splitPath('foo\\|bar');
122 | $this->assertEquals(1, count($test3));
123 | $this->assertEquals('foo\\|bar', $test3[0]);
124 |
125 | $test4 = $i->splitPath('foo\\\\|bar');
126 | $this->assertEquals(2, count($test4));
127 | $this->assertEquals('foo\\\\', $test4[0]);
128 | $this->assertEquals('bar', $test4[1]);
129 |
130 | $test5 = $i->splitPath('foo\\\\\\|bar');
131 | $this->assertEquals(1, count($test5));
132 | $this->assertEquals('foo\\\\\\|bar', $test5[0]);
133 |
134 | $test6 = $i->splitPath('foo\\');
135 | $this->assertEquals(1, count($test6));
136 | $this->assertEquals('foo\\', $test6[0]);
137 | }
138 |
139 | public function testRemoveIgnoredCaller() {
140 | $i = new Input([
141 | 'callerKey' => 'shadowd_caller',
142 | 'ignoreFile' => SHADOWD_MISC_TESTS . 'ignore1.json'
143 | ]);
144 | $input = [
145 | 'GET|bar' => 'foobar'
146 | ];
147 |
148 | $_SERVER['shadowd_caller'] = 'foo';
149 | $output = $i->removeIgnored($input);
150 | $this->assertArrayNotHasKey('GET|bar', $output);
151 |
152 | $_SERVER['shadowd_caller'] = 'boo';
153 | $output = $i->removeIgnored($input);
154 | $this->assertArrayHasKey('GET|bar', $output);
155 | }
156 |
157 | public function testRemoveIgnoredPath() {
158 | $i = new Input([
159 | 'ignoreFile' => SHADOWD_MISC_TESTS . 'ignore2.json'
160 | ]);
161 |
162 | $input = [
163 | 'GET|bar' => 'foobar'
164 | ];
165 | $output = $i->removeIgnored($input);
166 | $this->assertArrayNotHasKey('GET|bar', $output);
167 |
168 | $input = [
169 | 'GET|boo' => 'foobar'
170 | ];
171 | $output = $i->removeIgnored($input);
172 | $this->assertArrayHasKey('GET|boo', $output);
173 | }
174 |
175 | public function testRemoveIgnoredCallerPath() {
176 | $i = new Input([
177 | 'callerKey' => 'shadowd_caller',
178 | 'ignoreFile' => SHADOWD_MISC_TESTS . 'ignore3.json'
179 | ]);
180 |
181 | $_SERVER['shadowd_caller'] = 'foo';
182 | $input = [
183 | 'GET|bar' => 'foobar'
184 | ];
185 | $output = $i->removeIgnored($input);
186 | $this->assertArrayNotHasKey('GET|bar', $output);
187 |
188 | $_SERVER['shadowd_caller'] = 'foo';
189 | $input = [
190 | 'GET|boo' => 'foobar'
191 | ];
192 | $output = $i->removeIgnored($input);
193 | $this->assertArrayHasKey('GET|boo', $output);
194 |
195 | $_SERVER['shadowd_caller'] = 'boo';
196 | $input = [
197 | 'GET|bar' => 'foobar'
198 | ];
199 | $output = $i->removeIgnored($input);
200 | $this->assertArrayHasKey('GET|bar', $output);
201 | }
202 |
203 | public function testGetHashes() {
204 | $_SERVER['SCRIPT_FILENAME'] = SHADOWD_MISC_TESTS . 'hashes';
205 |
206 | $i = new Input([]);
207 | $hashes = $i->getHashes();
208 |
209 | $this->assertTrue(array_key_exists('sha256', $hashes));
210 | $this->assertEquals('aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f', $hashes['sha256']);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/tpl/bad_json.html.php:
--------------------------------------------------------------------------------
1 | The Shadow Daemon server received invalid JSON data from the connector.
2 | This should not happen. Please create an issue in the Shadow Daemon issue tracker. Do not forget to include the debug output.
--------------------------------------------------------------------------------
/tpl/bad_request.html.php:
--------------------------------------------------------------------------------
1 | Your request was rejected by the Shadow Daemon web application firewall.
2 | To prevent harm it was blocked. If you think this is an error please contact your administrator.
--------------------------------------------------------------------------------
/tpl/bad_signature.html.php:
--------------------------------------------------------------------------------
1 | The Shadow Daemon server received an invalid message authentication code from the connector.
2 | The key of the profile and the key of the connector do not match. One or both of them have to be corrected.
3 |
--------------------------------------------------------------------------------
/tpl/base.html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Shadow Daemon - getTitle()); ?>
5 |
49 |
50 |
51 |
52 |
53 |

54 |
55 |
getTitle()); ?>
56 | printDescription(); ?>
57 |
58 |
59 | isDebug() && $this->isException()): ?>
60 |
61 |
getExceptionClass()); ?>
62 |
getExceptionMessage()); ?>
63 |
getStackTrace()); ?>
64 |
You are seeing this stack trace because the debug mode is enabled. Debug output might leak information and should be disabled when not needed.
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tpl/blocked.html.php:
--------------------------------------------------------------------------------
1 | Your request was classified as dangerous by the Shadow Daemon web application firewall.
2 | To prevent harm it was blocked. If you think this is an error please contact your administrator.
--------------------------------------------------------------------------------
/tpl/corrupted_file.html.php:
--------------------------------------------------------------------------------
1 | A required file to initialize the Shadow Daemon web application firewall is corrupted and can not be parsed.
2 |
3 | isDebug()): ?>
4 | Enable the debug setting to get additional information about the corrupted file.
5 |
--------------------------------------------------------------------------------
/tpl/failed_connection.html.php:
--------------------------------------------------------------------------------
1 | No connection could be established with the Shadow Daemon server.
2 | getExceptionMessage())): ?>
3 | The server is either unreachable or the wrong address is specified in the configuration file of the connector.
4 | isDebug()): ?>
5 | Enable the debug setting to get additional information.
6 |
7 |
8 | The exact cause is not known. It might be related to an invalid SSL public key.
9 |
--------------------------------------------------------------------------------
/tpl/invalid_profile.html.php:
--------------------------------------------------------------------------------
1 | An invalid profile id is specified in the configuration file of the Shadow Daemon connector.
2 | The profile id has to be a positive integer. It can be found in the user interface (Management → Profiles).
3 |
4 | isDebug()): ?>
5 | Enable the debug setting to get additional information about the invalid profile id.
6 |
--------------------------------------------------------------------------------
/tpl/missing_config_entry.html.php:
--------------------------------------------------------------------------------
1 | A required configuration entry to initialize the Shadow Daemon web application firewall is missing.
2 |
3 | isDebug()): ?>
4 | Enable the debug setting to get additional information about the missing entry.
5 |
--------------------------------------------------------------------------------
/tpl/missing_file.html.php:
--------------------------------------------------------------------------------
1 | A required file to initialize the Shadow Daemon web application firewall is missing.
2 | This indicates that the installation is incomplete or corrupted. The error can also be caused by insufficient permissions to access a required file.
3 |
4 | isDebug()): ?>
5 | Enable the debug setting to get additional information about the missing file.
6 |
--------------------------------------------------------------------------------
/tpl/processing.html.php:
--------------------------------------------------------------------------------
1 | The connector received a response from the Shadow Daemon server that could not be parsed.
2 | A wrong address (host/port) might be specified in the configuration file of the connector.
3 |
4 | isDebug()): ?>
5 | Enable the debug setting to get additional information about the processing error.
6 |
--------------------------------------------------------------------------------
/tpl/unknown_path.html.php:
--------------------------------------------------------------------------------
1 | The Shadow Daemon server sent an invalid path to the connector.
2 | This should not happen. Please create an issue in the Shadow Daemon issue tracker. Do not forget to include the debug output.
3 |
--------------------------------------------------------------------------------