Issues (30)

src/SecurityTxtModule.php (7 issues)

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
Missing member variable doc comment
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
Coding Style Documentation introduced by
Missing member variable doc comment
Loading history...
54
55
    public string|null $headerComment = null;
0 ignored issues
show
Coding Style Documentation introduced by
Missing member variable doc comment
Loading history...
56
    public string|null $footerComment = null;
0 ignored issues
show
Coding Style Documentation introduced by
Missing member variable doc comment
Loading history...
57
58
    public array $fieldComments = [];
0 ignored issues
show
Coding Style Documentation introduced by
Missing member variable doc comment
Loading history...
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
The condition $value === null is always false.
Loading history...
266 5
            return [];
267 10
        } elseif (is_string($value)) {
0 ignored issues
show
The condition is_string($value) is always false.
Loading history...
268 9
            $value = [$value];
269
        }
270 10
        return array_filter($value);
271
    }
272
}
273