rhertogh /
yii2-security.txt
| 1 | <?php |
||
| 2 | |||
| 3 | namespace rhertogh\Yii2SecurityTxt; |
||
| 4 | |||
| 5 | // phpcs:disable Generic.Files.LineLength.TooLong |
||
| 6 | use DateInterval; |
||
| 7 | use DateTimeImmutable; |
||
| 8 | use Exception; |
||
| 9 | use rhertogh\Yii2SecurityTxt\controllers\web\SecurityTxtWellKnownController; |
||
| 10 | use Yii; |
||
| 11 | use yii\base\BootstrapInterface; |
||
| 12 | use yii\base\InvalidConfigException; |
||
| 13 | use yii\base\Module; |
||
| 14 | use yii\helpers\Url; |
||
| 15 | use yii\web\UrlRule; |
||
| 16 | |||
| 17 | // phpcs:enable Generic.Files.LineLength.TooLong |
||
| 18 | |||
| 19 | /** |
||
| 20 | * This is the main module class for the Yii2 SecurityTxtModule module. |
||
| 21 | * To use it, include it as a module in the application configuration like the following: |
||
| 22 | * |
||
| 23 | * ~~~ |
||
| 24 | * return [ |
||
| 25 | * 'bootstrap' => ['security.txt'], |
||
| 26 | * 'modules' => [ |
||
| 27 | * 'security.txt' => [ |
||
| 28 | * 'class' => 'rhertogh\Yii2SecurityTxt\SecurityTxtModule', |
||
| 29 | * // ... Please check docs/guide/start-installation.md further details |
||
| 30 | * ], |
||
| 31 | * ], |
||
| 32 | * ] |
||
| 33 | * ~~~ |
||
| 34 | * |
||
| 35 | * @since 1.0.0 |
||
| 36 | */ |
||
| 37 | class SecurityTxtModule extends Module implements BootstrapInterface |
||
| 38 | { |
||
| 39 | public $controllerMap = [ |
||
|
0 ignored issues
–
show
Coding Style
Documentation
introduced
by
Loading history...
|
|||
| 40 | SecurityTxtWellKnownController::CONTROLLER_NAME => SecurityTxtWellKnownController::class, |
||
| 41 | ]; |
||
| 42 | |||
| 43 | /** |
||
| 44 | * The URL path to the security.txt endpoint. |
||
| 45 | * If set to `null` the endpoint will be disabled. |
||
| 46 | * Note: This path is defined in the |
||
| 47 | * [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116.html#name-well-known-uris-registry) |
||
| 48 | * specification and should normally not be changed. |
||
| 49 | * @since 1.0.0 |
||
| 50 | */ |
||
| 51 | public string|false|null $securityTxtPath = '.well-known/security.txt'; |
||
| 52 | |||
| 53 | public int|bool $cacheControl = true; |
||
|
0 ignored issues
–
show
|
|||
| 54 | |||
| 55 | public string|null $headerComment = null; |
||
|
0 ignored issues
–
show
|
|||
| 56 | public string|null $footerComment = null; |
||
|
0 ignored issues
–
show
|
|||
| 57 | |||
| 58 | public array $fieldComments = []; |
||
|
0 ignored issues
–
show
|
|||
| 59 | |||
| 60 | /** |
||
| 61 | * Defines the "Acknowledgments" section in the security.txt |
||
| 62 | * |
||
| 63 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-acknowledgments |
||
| 64 | * @since 1.0.0 |
||
| 65 | */ |
||
| 66 | public array|string|null $acknowledgments = null; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Defines the "Canonical" section in the security.txt |
||
| 70 | * If `null` (default value) the current URL will be used. |
||
| 71 | * |
||
| 72 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-canonical |
||
| 73 | * @since 1.0.0 |
||
| 74 | */ |
||
| 75 | public array|string|null $canonical = null; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * Defines the "Contact" section in the security.txt |
||
| 79 | * |
||
| 80 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-contact |
||
| 81 | * @since 1.0.0 |
||
| 82 | */ |
||
| 83 | public array|string $contact; |
||
| 84 | |||
| 85 | /** |
||
| 86 | * Defines the "Encryption" section in the security.txt |
||
| 87 | * |
||
| 88 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-encryption |
||
| 89 | * @since 1.0.0 |
||
| 90 | */ |
||
| 91 | public array|string|null $encryption = null; |
||
| 92 | |||
| 93 | /** |
||
| 94 | * Defines the "Expires" section in the security.txt |
||
| 95 | * |
||
| 96 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-expires |
||
| 97 | * @see https://www.php.net/manual/en/datetime.formats.php |
||
| 98 | * @since 1.0.0 |
||
| 99 | */ |
||
| 100 | public string|DateTimeImmutable|DateInterval $expires = '+1 day midnight'; |
||
| 101 | |||
| 102 | /** |
||
| 103 | * Defines the "Hiring" section in the security.txt |
||
| 104 | * |
||
| 105 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-hiring |
||
| 106 | * @since 1.0.0 |
||
| 107 | */ |
||
| 108 | public array|string|null $hiring = null; |
||
| 109 | |||
| 110 | /** |
||
| 111 | * Defines the "Policy" section in the security.txt |
||
| 112 | * |
||
| 113 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-policy |
||
| 114 | * @since 1.0.0 |
||
| 115 | */ |
||
| 116 | public array|string|null $policy = null; |
||
| 117 | |||
| 118 | /** |
||
| 119 | * Defines the "Preferred languages" section in the security.txt |
||
| 120 | * |
||
| 121 | * @see https://www.rfc-editor.org/rfc/rfc9116.html#name-preferred-languages |
||
| 122 | * @since 1.0.0 |
||
| 123 | */ |
||
| 124 | public array|string|null $preferredLanguages = null; |
||
| 125 | |||
| 126 | /** |
||
| 127 | * @var string|null ASCII-armored PGP private key. |
||
| 128 | */ |
||
| 129 | public string|null $pgpPrivateKey = null; |
||
| 130 | |||
| 131 | /** |
||
| 132 | * @inheritdoc |
||
| 133 | * @throws InvalidConfigException |
||
| 134 | * @since 1.0.0 |
||
| 135 | */ |
||
| 136 | 58 | public function bootstrap($app): void |
|
| 137 | { |
||
| 138 | 58 | $urlManager = $app->getUrlManager(); |
|
| 139 | 58 | if (!$urlManager->enablePrettyUrl) { |
|
| 140 | 1 | throw new InvalidConfigException('`enablePrettyUrl` must be enabled for application urlManager.'); |
|
| 141 | } |
||
| 142 | |||
| 143 | 57 | if ($this->securityTxtPath) { |
|
| 144 | 57 | $urlManager->addRules([ |
|
| 145 | 57 | Yii::createObject([ |
|
| 146 | 57 | 'class' => UrlRule::class, |
|
| 147 | 57 | 'pattern' => $this->securityTxtPath, |
|
| 148 | 57 | 'route' => $this->id |
|
| 149 | 57 | . '/' . SecurityTxtWellKnownController::CONTROLLER_NAME |
|
| 150 | 57 | . '/' . SecurityTxtWellKnownController::ACTION_NAME_SECURITY_TXT, |
|
| 151 | 57 | ]), |
|
| 152 | 57 | ]); |
|
| 153 | } |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * @return string[] |
||
| 158 | * @since 1.0.0 |
||
| 159 | */ |
||
| 160 | 2 | public function getParsedAcknowledgments(): array |
|
| 161 | { |
||
| 162 | 2 | return $this->getCleanArray($this->acknowledgments); |
|
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * @return string[] |
||
| 167 | * @throws InvalidConfigException |
||
| 168 | * @since 1.0.0 |
||
| 169 | */ |
||
| 170 | 3 | public function getParsedCanonical(): array |
|
| 171 | { |
||
| 172 | 3 | $canonical = $this->canonical; |
|
| 173 | 3 | if ($canonical === null) { |
|
| 174 | 1 | $canonical = Url::canonical(); |
|
| 175 | } |
||
| 176 | 3 | $canonical = $this->getCleanArray($canonical); |
|
| 177 | 3 | if (empty($canonical)) { |
|
| 178 | 3 | throw new InvalidConfigException(static::class . '::$canonical can not be empty.'); |
|
| 179 | } |
||
| 180 | 2 | return $canonical; |
|
| 181 | } |
||
| 182 | |||
| 183 | /** |
||
| 184 | * @return string[] |
||
| 185 | * @throws InvalidConfigException |
||
| 186 | * @since 1.0.0 |
||
| 187 | */ |
||
| 188 | 4 | public function getParsedContact(): array |
|
| 189 | { |
||
| 190 | 4 | if (empty($this->contact)) { |
|
| 191 | 3 | throw new InvalidConfigException(static::class . '::$contact must be set.'); |
|
| 192 | } |
||
| 193 | 3 | $contact = $this->getCleanArray($this->contact); |
|
| 194 | 3 | if (empty($contact)) { |
|
| 195 | 1 | throw new InvalidConfigException(static::class . '::$contact can not be empty.'); |
|
| 196 | } |
||
| 197 | 2 | return $contact; |
|
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * @return string[] |
||
| 202 | * @since 1.0.0 |
||
| 203 | */ |
||
| 204 | 2 | public function getParsedEncryption(): array |
|
| 205 | { |
||
| 206 | 2 | return $this->getCleanArray($this->encryption); |
|
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * @throws Exception |
||
| 211 | * @since 1.0.0 |
||
| 212 | */ |
||
| 213 | 4 | public function getParsedExpires(): DateTimeImmutable |
|
| 214 | { |
||
| 215 | 3 | $expires = $this->expires; |
|
| 216 | 3 | $now = Yii::$container->has('Psr\Clock\ClockInterface') |
|
| 217 | 2 | ? Yii::$container->get('Psr\Clock\ClockInterface')->now() |
|
| 218 | 1 | : new DateTimeImmutable(); |
|
| 219 | |||
| 220 | 3 | if (empty($expires)) { |
|
| 221 | 1 | throw new InvalidConfigException(static::class . '::$expires can not be empty.'); |
|
| 222 | } |
||
| 223 | 3 | if (is_string($expires)) { |
|
| 224 | 3 | $expires = $now->modify($expires); |
|
| 225 | 4 | } elseif ($expires instanceof DateInterval) { |
|
| 226 | 2 | $expires = $now->add($expires); |
|
| 227 | } |
||
| 228 | 3 | if ($expires < $now) { |
|
| 229 | 2 | throw new InvalidConfigException(static::class . '::$expires can not be in the past.'); |
|
| 230 | } |
||
| 231 | 3 | return $expires; |
|
| 232 | } |
||
| 233 | |||
| 234 | /** |
||
| 235 | * @return string[] |
||
| 236 | * @since 1.0.0 |
||
| 237 | */ |
||
| 238 | 2 | public function getParsedHiring(): array |
|
| 239 | { |
||
| 240 | 2 | return $this->getCleanArray($this->hiring); |
|
| 241 | } |
||
| 242 | |||
| 243 | /** |
||
| 244 | * @return string[] |
||
| 245 | * @since 1.0.0 |
||
| 246 | */ |
||
| 247 | 2 | public function getParsedPolicy(): array |
|
| 248 | { |
||
| 249 | 2 | return $this->getCleanArray($this->policy); |
|
| 250 | } |
||
| 251 | |||
| 252 | /** |
||
| 253 | * @since 1.0.0 |
||
| 254 | */ |
||
| 255 | 2 | public function getParsedPreferredLanguages(): string |
|
| 256 | { |
||
| 257 | 2 | return implode(', ', $this->getCleanArray($this->preferredLanguages)); |
|
| 258 | } |
||
| 259 | |||
| 260 | /** |
||
| 261 | * @since 1.0.0 |
||
| 262 | */ |
||
| 263 | 10 | protected function getCleanArray(array|string|null $value): array |
|
| 264 | { |
||
| 265 | 10 | if ($value === null) { |
|
|
0 ignored issues
–
show
|
|||
| 266 | 5 | return []; |
|
| 267 | 10 | } elseif (is_string($value)) { |
|
|
0 ignored issues
–
show
|
|||
| 268 | 9 | $value = [$value]; |
|
| 269 | } |
||
| 270 | 10 | return array_filter($value); |
|
| 271 | } |
||
| 272 | } |
||
| 273 |