Skip to content

Commit fe52677

Browse files
authored
Merge pull request #4 from redbitcz/plugin
Add Plugin ability
2 parents e3227b1 + 15f3e20 commit fe52677

File tree

13 files changed

+890
-20
lines changed

13 files changed

+890
-20
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.gitattributes export-ignore
22
.gitignore export-ignore
33
.github export-ignore
4+
phpstan.neon export-ignores
45
tests export-ignore

.github/workflows/code_analysis.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ jobs:
1818
run: composer phpstan
1919

2020
- name: Nette Tester
21-
run: composer tester
21+
run: composer tester -- -C
22+
2223
php:
2324
- "7.4"
2425
- "8.0"
25-
- "8.1"
2626

2727
name: ${{ matrix.actions.name }} on PHP ${{ matrix.php }}
2828
runs-on: ubuntu-latest
@@ -38,6 +38,7 @@ jobs:
3838
uses: shivammathur/setup-php@v2
3939
with:
4040
php-version: ${{ matrix.php }}
41+
extensions: json
4142
coverage: none
4243

4344

@@ -53,8 +54,6 @@ jobs:
5354
path: |
5455
${{ steps.composer-cache.outputs.dir }}
5556
key: ${{ runner.os }}-composer-data-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}
56-
restore-keys: |
57-
${{ runner.os }}-composer-data-
5857

5958

6059
- uses: actions/cache@v2

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/vendor
21
/composer.lock
3-
/tests/temp
2+
/tests/**/output
43
/tests/lock
4+
/tests/temp
5+
/vendor

.phpstorm.meta.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,47 @@
3434
\Redbitcz\DebugMode\Detector::MODE_ENV,
3535
\Redbitcz\DebugMode\Detector::MODE_IP
3636
);
37+
38+
expectedArguments(
39+
\Redbitcz\DebugMode\Plugin\SignedUrl::__construct(),
40+
1,
41+
'ES384',
42+
'ES256',
43+
'HS256',
44+
'HS384',
45+
'HS512',
46+
'RS256',
47+
'RS384',
48+
'RS512'
49+
);
50+
51+
expectedArguments(
52+
\Redbitcz\DebugMode\Plugin\SignedUrl::signUrl(),
53+
2,
54+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_REQUEST,
55+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_ENABLER,
56+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_DEACTIVATE_ENABLER
57+
);
58+
59+
expectedArguments(
60+
\Redbitcz\DebugMode\Plugin\SignedUrl::signUrl(),
61+
3,
62+
\Redbitcz\DebugMode\Plugin\SignedUrl::VALUE_DISABLE,
63+
\Redbitcz\DebugMode\Plugin\SignedUrl::VALUE_ENABLE
64+
);
65+
66+
expectedArguments(
67+
\Redbitcz\DebugMode\Plugin\SignedUrl::getToken(),
68+
3,
69+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_REQUEST,
70+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_ENABLER,
71+
\Redbitcz\DebugMode\Plugin\SignedUrl::MODE_DEACTIVATE_ENABLER
72+
);
73+
74+
expectedArguments(
75+
\Redbitcz\DebugMode\Plugin\SignedUrl::getToken(),
76+
4,
77+
\Redbitcz\DebugMode\Plugin\SignedUrl::VALUE_DISABLE,
78+
\Redbitcz\DebugMode\Plugin\SignedUrl::VALUE_ENABLE
79+
);
3780
}

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,74 @@ services:
113113
imported: true
114114
```
115115

116+
## Plugins
117+
118+
Detector supports custom plugin. You can build custom plugin to provide your own roles to manage Debug Mode. Plugin must
119+
implements `Plugin` interface what means add `__invoke()` method. That method is called always is Detector aksed to
120+
detect mode.
121+
122+
Plugin retuns result of detection:
123+
124+
- `null` – no result – Detector will try to ask another plugin or detection method to decide
125+
- `true` – force turn-on debug mode for current request
126+
- `false` – force turn-off debug mode for current request
127+
128+
Note: You should return `null` value when Plugin doesn't explicitly matches rule. Boolean value is always stops
129+
processing detection rules.
130+
131+
Don't do this:
132+
133+
```php
134+
if (…) {
135+
return true;
136+
} else {
137+
return false;
138+
}
139+
```
140+
141+
instead return `null` when your rule is not matched:
142+
143+
```php
144+
if (…) {
145+
return true;
146+
} else {
147+
return null;
148+
}
149+
```
150+
151+
Your Plugin you can register to Detector with method `appendPlugin()` or `prepedndPlugin()`.
152+
153+
```php
154+
$detector = new \Redbitcz\DebugMode\Detector();
155+
156+
$plugin = new MyPlugin();
157+
158+
$detector->appendPlugin($plugin);
159+
160+
$detector->isDebugMode(); // <---- this invoke all Plugins
161+
```
162+
163+
## SignUrl plugin
164+
165+
`SignUrl` plugin provide secure way to share link with activated Debug Mode.
166+
167+
```php
168+
$plugin = new \Redbitcz\DebugMode\Plugin\SignedUrl('secretkey', 'HS256', 'https://myapp.cz');
169+
$detector->appendPlugin($plugin);
170+
171+
$signedUrl = $plugin->signUrl('https://myapp.cz/failingPage', '+1 hour');
172+
173+
echo 'Private link with activated Debug mode: ' . htmlspecialchars($signedUrl, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE);
174+
```
175+
176+
### Security notes
177+
178+
Wrong usage of the `SignUrl` plugin can open critical vulnerability issue at your App. Follow this instructions:
179+
180+
- Always create `SignUrl` with strong and Secret key, use key generator like: `base64_encode(random_bytes(32))`
181+
- Always create `SignUrl` with specified `$audience` parameter which distinguishes versions of app (stage vs production)
182+
to prevent unwanted re-using signatures between them
183+
([read more about importance of audience](https://stackoverflow.com/a/41237822/1641372)).
184+
116185
## License
117186
The MIT License (MIT). Please see [License File](LICENSE) for more information.

composer.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@
1616
],
1717
"require": {
1818
"php": ">=7.4",
19+
"ext-json": "*",
1920
"nette/utils": "^3.0"
2021
},
22+
"require-dev": {
23+
"firebase/php-jwt": "^5.0",
24+
"nette/tester": "^2.4",
25+
"phpstan/phpstan": "^0.12.98"
26+
},
27+
"suggest": {
28+
"firebase/php-jwt": "Optional, required for SignedUrl plugin"
29+
},
2130
"autoload": {
2231
"psr-4": {
2332
"Redbitcz\\DebugMode\\": "src/"
@@ -36,10 +45,6 @@
3645
"dev-master": "2.0-dev"
3746
}
3847
},
39-
"require-dev": {
40-
"phpstan/phpstan": "^0.12.98",
41-
"nette/tester": "^2.4"
42-
},
4348
"scripts": {
4449
"phpstan": "phpstan analyze src -c phpstan.neon --level 8",
4550
"tester": "tester tests"

src/Detector.php

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
namespace Redbitcz\DebugMode;
1010

11+
use Redbitcz\DebugMode\Plugin\Plugin;
12+
1113
class Detector
1214
{
1315
/** Name of Environment variable used to detect Debug mode */
@@ -36,10 +38,12 @@ class Detector
3638

3739

3840
private ?Enabler $enabler;
39-
private int $mode;
4041
/** @var string[] */
4142
private array $ips = ['::1', '127.0.0.1'];
4243

44+
/** @var array<int, callable> */
45+
private array $detections;
46+
4347
/**
4448
* @param int $mode Enables methods which is used to detect Debug mode
4549
* @param Enabler|null $enabler Enabler instance. Optional, but required when Enabler mode is enabled
@@ -52,8 +56,21 @@ public function __construct(int $mode = self::MODE_SIMPLE, ?Enabler $enabler = n
5256
);
5357
}
5458

59+
$this->detections = array_filter(
60+
[
61+
$mode & self::MODE_ENABLER ? [$this, 'isDebugModeByEnabler'] : null,
62+
$mode & self::MODE_COOKIE ? [$this, 'isDebugModeByCookie'] : null,
63+
$mode & self::MODE_ENV ? [$this, 'isDebugModeByEnv'] : null,
64+
$mode & self::MODE_IP ? [$this, 'isDebugModeByIp'] : null,
65+
]
66+
);
67+
5568
$this->enabler = $enabler;
56-
$this->mode = $mode;
69+
}
70+
71+
public function hasEnabler(): bool
72+
{
73+
return $this->enabler !== null;
5774
}
5875

5976
public function getEnabler(): Enabler
@@ -74,11 +91,12 @@ public function getEnabler(): Enabler
7491
*/
7592
public function isDebugMode(?bool $default = false): ?bool
7693
{
77-
return ($this->mode & self::MODE_ENABLER ? $this->isDebugModeByEnabler() : null)
78-
?? ($this->mode & self::MODE_COOKIE ? $this->isDebugModeByCookie() : null)
79-
?? ($this->mode & self::MODE_ENV ? $this->isDebugModeByEnv() : null)
80-
?? ($this->mode & self::MODE_IP ? $this->isDebugModeByIp() : null)
81-
?? $default;
94+
foreach ($this->detections as $detection) {
95+
if (($result = $detection($this)) !== null) {
96+
return $result;
97+
}
98+
}
99+
return $default;
82100
}
83101

84102
/**
@@ -152,6 +170,7 @@ public function isDebugModeByIp(): ?bool
152170

153171
/**
154172
* Set client IP address with allowed Debug mode
173+
* @return static
155174
*/
156175
public function setAllowedIp(string ...$ips): self
157176
{
@@ -161,13 +180,28 @@ public function setAllowedIp(string ...$ips): self
161180

162181
/**
163182
* Add client IP address with allowed Debug mode
183+
* @return static
164184
*/
165185
public function addAllowedIp(string ...$ips): self
166186
{
167187
$this->ips = array_merge($this->ips, $ips);
168188
return $this;
169189
}
170190

191+
/** @return static */
192+
public function prependPlugin(Plugin $plugin): self
193+
{
194+
array_unshift($this->detections, $plugin);
195+
return $this;
196+
}
197+
198+
/** @return static */
199+
public function appendPlugin(Plugin $plugin): self
200+
{
201+
$this->detections[] = $plugin;
202+
return $this;
203+
}
204+
171205
/**
172206
* Shortcut to simple detect Debug mode by all method enabled by Detector mode (argument `$mode`)
173207
*

src/Enabler.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Redbitcz\DebugMode;
1010

11+
use DateTimeInterface;
1112
use Nette\IOException;
1213
use Nette\Utils\DateTime as NetteDateTime;
1314
use Nette\Utils\FileSystem;
@@ -68,14 +69,17 @@ public function setCookieOptions(
6869
return $this;
6970
}
7071

71-
public function activate(bool $isDebug, ?string $time = self::DEFAULT_TTL): void
72+
/**
73+
* @param string|int|DateTimeInterface|null $expires
74+
*/
75+
public function activate(bool $isDebug, $expires = null): void
7276
{
7377
if ($tokenName = $this->getTokenName()) {
7478
$this->destroyToken($tokenName);
7579
}
7680

77-
$tokenExpires = (int)NetteDateTime::from($time ?? self::DEFAULT_TTL)->format('U');
78-
$cookieExpires = $time === null ? 0 : $tokenExpires;
81+
$tokenExpires = (int)NetteDateTime::from($expires ?? self::DEFAULT_TTL)->format('U');
82+
$cookieExpires = $expires === null ? 0 : $tokenExpires;
7983
$tokenName = $this->createToken($isDebug, $tokenExpires);
8084
setcookie(self::DEBUG_COOKIE_NAME, $tokenName, ['expires' => $cookieExpires] + $this->cookieOptions);
8185

src/Plugin/Plugin.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Redbitcz\DebugMode\Plugin;
6+
7+
use Redbitcz\DebugMode\Detector;
8+
9+
interface Plugin
10+
{
11+
/**
12+
* Method invoked when Debug mode detection is called
13+
* Returned value:
14+
* - `true` (force to turn-on debug mode)
15+
* - `false` (force to turn-off debug mode)
16+
* - `null` (unknown debug mode state, next detection method will be called)
17+
*/
18+
public function __invoke(Detector $detector): ?bool;
19+
}

0 commit comments

Comments
 (0)