Skip to content

Commit 05c36cf

Browse files
authored
[Feature] Add Rule "no ssh keys" (#54)
* Add NoSshKeysRule * add ssh key finder * add service providers * move providers * up * Add EventDispatcherProvider * Add PrivateKeyFinder * Add RsaKeyFinderTest * change test * fix diff fragment * Add integration test * change config * fix false positive * split find methods * add test * add test * move test * fix rule name * up config * up config * Add Rule "@mr-linter/disable_file_extensions" * add test * fix stat analyse * add coverage ignore * add tests * add test * add test * add test * up version * change notification template * change notification template * update docs
1 parent 15084ee commit 05c36cf

34 files changed

+1235
-75
lines changed

.mr-linter.yml

+11-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ rules:
2626
- '[Tests]'
2727
- '[Docs]'
2828

29+
"@mr-linter/no_ssh_keys":
30+
stopOnFirstFailure: true
31+
critical: false
32+
2933
"@mr-linter/has_changes":
3034
- changes:
3135
- file: "src/Version.php"
@@ -38,6 +42,11 @@ rules:
3842
- '[Tests]'
3943
- '[Docs]'
4044

45+
"@mr-linter/disable_file_extensions":
46+
extensions:
47+
- pem
48+
- pub
49+
4150
custom:
4251
- definition: "Branch must be in kebab-case"
4352
rules:
@@ -77,7 +86,7 @@ notifications:
7786
lint_finished:
7887
channel: 'dev'
7988
template: |
80-
👀 Review on PR "{{ request.title }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
89+
👀 Review on PR "{{ request.title | raw }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
8190
8291
🌲 {{ request.sourceBranch }} ➡ {{ request.targetBranch }}
8392
@@ -86,7 +95,7 @@ notifications:
8695
📉 Notes: {{ result.notes.count }}
8796
8897
{% for note in result.notes %}
89-
- {{ note.description }}
98+
- {{ note.description | raw }}
9099
{% endfor %}
91100
92101
ci:

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ This file contains changelogs.
66

77
-----------------------------------------------------------------
88

9+
## [v0.15.3 (2023-07-29)](https://github.com/ArtARTs36/php-merge-request-linter/compare/0.15.2..0.15.3)
10+
11+
## Added
12+
* Added "no ssh keys" rule to prevent ssh keys from being included in the merge request.
13+
* Added "disable file extensions" rule to disable adding files of certain extensions.
14+
15+
[💾 Assets](https://github.com/ArtARTs36/php-merge-request-linter/releases/tag/0.15.3)
16+
17+
-----------------------------------------------------------------
18+
919
## [v0.15.2 (2023-07-29)](https://github.com/ArtARTs36/php-merge-request-linter/compare/0.15.1..0.15.2)
1020

1121
## Optimization

docs/notifications.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ notifications:
3434
lint_finished:
3535
channel: 'dev'
3636
template: |
37-
👀 Review on PR "{{ request.title }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
37+
👀 Review on PR "{{ request.title | raw }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
3838
3939
🌲 {{ request.sourceBranch }} ➡ {{ request.targetBranch }}
4040
@@ -43,7 +43,7 @@ notifications:
4343
📉 Notes: {{ result.notes.count }}
4444
4545
{% for note in result.notes %}
46-
- {{ note.description }}
46+
- {{ note.description | raw }}
4747
{% endfor %}
4848
```
4949
@@ -63,7 +63,7 @@ notifications:
6363
request.targetBranch:
6464
equals: "master"
6565
template: |
66-
👀 Review on PR "{{ request.title }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
66+
👀 Review on PR "{{ request.title | raw }}" by {{ request.author.login }} at {{ request.createdAt.format('Y-m-d H:i') }}
6767
6868
🌲 {{ request.sourceBranch }} ➡ {{ request.targetBranch }}
6969
@@ -72,7 +72,7 @@ notifications:
7272
📉 Notes: {{ result.notes.count }}
7373
7474
{% for note in result.notes %}
75-
- {{ note.description }}
75+
- {{ note.description | raw }}
7676
{% endfor %}
7777
```
7878

docs/rules.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ Currently is available that rules:
2020
| @mr-linter/forbid_changes | Forbid changes for files. | `files` - array of strings <br/> |
2121
| @mr-linter/update_changelog | Changelog must be contained new tag. | `file` - string <br/> `tags` - object <br/> |
2222
| @mr-linter/diff_limit | The request must contain no more than {linesMax} changes. | `linesMax` - integer <br/> `fileLinesMax` - integer <br/> |
23+
| @mr-linter/no_ssh_keys | Prevent ssh keys from being included in the merge request. | `sshKeyFinder` - <br/> `stopOnFirstFailure` - boolean <br/> |
24+
| @mr-linter/disable_file_extensions | Disable adding files of certain extensions. | `extensions` - array of strings <br/> |

mr-linter-config-schema.json

+80
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,36 @@
259259
"minItems": 1
260260
}
261261
]
262+
},
263+
"@mr-linter/no_ssh_keys": {
264+
"description": "Prevent ssh keys from being included in the merge request.",
265+
"oneOf": [
266+
{
267+
"$ref": "#/definitions/rules_properties_@mr-linter_no_ssh_keys"
268+
},
269+
{
270+
"type": "array",
271+
"items": {
272+
"$ref": "#/definitions/rules_properties_@mr-linter_no_ssh_keys"
273+
},
274+
"minItems": 1
275+
}
276+
]
277+
},
278+
"@mr-linter/disable_file_extensions": {
279+
"description": "Disable adding files of certain extensions.",
280+
"oneOf": [
281+
{
282+
"$ref": "#/definitions/rules_properties_@mr-linter_disable_file_extensions"
283+
},
284+
{
285+
"type": "array",
286+
"items": {
287+
"$ref": "#/definitions/rules_properties_@mr-linter_disable_file_extensions"
288+
},
289+
"minItems": 1
290+
}
291+
]
262292
}
263293
},
264294
"additionalProperties": false
@@ -4365,6 +4395,56 @@
43654395
},
43664396
"additionalProperties": false
43674397
},
4398+
"rules_properties_@mr-linter_no_ssh_keys": {
4399+
"type": "object",
4400+
"properties": {
4401+
"critical": {
4402+
"type": "boolean",
4403+
"default": true
4404+
},
4405+
"when": {
4406+
"description": "Conditions that determine whether the rule should run.",
4407+
"$ref": "#/definitions/rule_conditions"
4408+
},
4409+
"stopOnFirstFailure": {
4410+
"type": "boolean"
4411+
}
4412+
},
4413+
"additionalProperties": false,
4414+
"required": [
4415+
"stopOnFirstFailure"
4416+
]
4417+
},
4418+
"rules_properties_@mr-linter_disable_file_extensions": {
4419+
"type": "object",
4420+
"properties": {
4421+
"critical": {
4422+
"type": "boolean",
4423+
"default": true
4424+
},
4425+
"when": {
4426+
"description": "Conditions that determine whether the rule should run.",
4427+
"$ref": "#/definitions/rule_conditions"
4428+
},
4429+
"extensions": {
4430+
"type": "array",
4431+
"examples": [
4432+
"pem",
4433+
"pub",
4434+
"php"
4435+
],
4436+
"items": [
4437+
{
4438+
"type": "string"
4439+
}
4440+
]
4441+
}
4442+
},
4443+
"additionalProperties": false,
4444+
"required": [
4445+
"extensions"
4446+
]
4447+
},
43684448
"notifications_channel_telegram_bot": {
43694449
"properties": {
43704450
"type": {

src/Application/Rule/Rules/DefaultRules.php

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ final class DefaultRules
2929
ForbidChangesRule::NAME => ForbidChangesRule::class,
3030
UpdateChangelogRule::NAME => UpdateChangelogRule::class,
3131
DiffLimitRule::NAME => DiffLimitRule::class,
32+
NoSshKeysRule::NAME => NoSshKeysRule::class,
33+
DisableFileExtensionsRule::NAME => DisableFileExtensionsRule::class,
3234
];
3335

3436
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules;
4+
5+
use ArtARTs36\MergeRequestLinter\Application\Rule\Definition\Definition;
6+
use ArtARTs36\MergeRequestLinter\Domain\Note\LintNote;
7+
use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest;
8+
use ArtARTs36\MergeRequestLinter\Domain\Rule\RuleDefinition;
9+
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Example;
10+
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Generic;
11+
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Set;
12+
13+
/**
14+
* Disable adding files of certain extensions.
15+
*/
16+
final class DisableFileExtensionsRule extends NamedRule
17+
{
18+
public const NAME = '@mr-linter/disable_file_extensions';
19+
20+
/**
21+
* @param Set<string> $extensions
22+
*/
23+
public function __construct(
24+
#[Example('pem')]
25+
#[Example('pub')]
26+
#[Example('php')]
27+
#[Generic(Generic::OF_STRING)]
28+
private readonly Set $extensions,
29+
) {
30+
}
31+
32+
public function lint(MergeRequest $request): array
33+
{
34+
$notes = [];
35+
36+
foreach ($request->changes as $change) {
37+
$extension = $change->fileExtension();
38+
39+
if ($this->extensions->contains($extension)) {
40+
$notes[] = new LintNote(sprintf(
41+
'File "%s" has disabled extension "%s"',
42+
$change->file,
43+
$extension,
44+
));
45+
}
46+
}
47+
48+
return $notes;
49+
}
50+
51+
public function getDefinition(): RuleDefinition
52+
{
53+
return new Definition(sprintf(
54+
'Merge request must no contain files with extensions: [%s]',
55+
$this->extensions->implode(', '),
56+
));
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules;
4+
5+
use ArtARTs36\MergeRequestLinter\Application\Rule\Definition\Definition;
6+
use ArtARTs36\MergeRequestLinter\Domain\Note\LintNote;
7+
use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest;
8+
use ArtARTs36\MergeRequestLinter\Domain\Rule\RuleDefinition;
9+
use ArtARTs36\MergeRequestLinter\Shared\Text\Ssh\SshKeyFinder;
10+
use ArtARTs36\Str\Str;
11+
12+
/**
13+
* Prevent ssh keys from being included in the merge request.
14+
* @phpstan-import-type SshKeyType from SshKeyFinder
15+
*/
16+
final class NoSshKeysRule extends NamedRule
17+
{
18+
public const NAME = '@mr-linter/no_ssh_keys';
19+
20+
public function __construct(
21+
private readonly SshKeyFinder $sshKeyFinder,
22+
private readonly bool $stopOnFirstFailure,
23+
) {
24+
}
25+
26+
public function lint(MergeRequest $request): array
27+
{
28+
$notes = [];
29+
30+
foreach ($request->changes as $change) {
31+
foreach ($change->diff->newFragments as $line) {
32+
if (! $line->hasChanges()) {
33+
continue;
34+
}
35+
36+
$foundTypes = $this->findSshKeysTypes($line->content);
37+
38+
if (count($foundTypes) === 0) {
39+
continue;
40+
}
41+
42+
foreach ($foundTypes as $keyType) {
43+
$notes[] = new LintNote(sprintf(
44+
'File "%s" contains ssh key (%s)',
45+
$change->file,
46+
$keyType,
47+
));
48+
}
49+
50+
if ($this->stopOnFirstFailure) {
51+
break 2;
52+
}
53+
}
54+
}
55+
56+
return $notes;
57+
}
58+
59+
public function getDefinition(): RuleDefinition
60+
{
61+
return new Definition('Request must no contain ssh keys');
62+
}
63+
64+
/**
65+
* @return array<SshKeyType>
66+
*/
67+
private function findSshKeysTypes(Str $content): array
68+
{
69+
if ($this->stopOnFirstFailure) {
70+
$foundSshTypes = [];
71+
72+
$foundSshType = $this->sshKeyFinder->findFirst($content);
73+
74+
if ($foundSshType !== null) {
75+
$foundSshTypes = [$foundSshType];
76+
}
77+
78+
return $foundSshTypes;
79+
}
80+
81+
return $this->sshKeyFinder->findAll($content);
82+
}
83+
}

src/Domain/Request/Change.php

+5
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@ public function jsonSerialize(): array
2929
'diff' => $this->diff->allFragments->firsts(2)->mapToArray(fn (DiffFragment $f): string => $f->content),
3030
];
3131
}
32+
33+
public function fileExtension(): string
34+
{
35+
return pathinfo($this->file, PATHINFO_EXTENSION);
36+
}
3237
}

0 commit comments

Comments
 (0)