GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#123)
by
unknown
01:01
created

SslCertificate   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 284
Duplicated Lines 4.58 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 13
loc 284
rs 8.48
c 0
b 0
f 0
wmc 49
lcom 1
cbo 4

29 Methods

Rating   Name   Duplication   Size   Complexity  
A download() 0 4 1
A createForHostName() 0 4 1
A createFromString() 13 13 1
A __construct() 0 14 1
A getRawCertificateFields() 0 4 1
A getIssuer() 0 4 1
A getDomain() 0 16 4
A getSignatureAlgorithm() 0 4 1
A getOrganization() 0 4 1
A getFingerprint() 0 4 1
A getFingerprintSha256() 0 4 1
A getAdditionalDomains() 0 8 1
A validFromDate() 0 4 1
A expirationDate() 0 4 1
A isExpired() 0 4 1
A isValid() 0 12 3
A isSelfSigned() 0 4 1
A usesSha1Hash() 0 14 3
A isValidUntil() 0 8 2
A daysUntilExpirationDate() 0 8 1
A getDomains() 0 8 1
A appliesToUrl() 0 23 5
A wildcardHostCoversHost() 0 20 4
A getRawCertificateFieldsJson() 0 4 1
A getHash() 0 4 1
A getRemoteAddress() 0 4 1
A __toString() 0 4 1
A containsDomain() 0 16 4
A isPreCertificate() 0 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SslCertificate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SslCertificate, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Spatie\SslCertificate;
4
5
use Carbon\Carbon;
6
use Spatie\Macroable\Macroable;
7
8
class SslCertificate
9
{
10
    use Macroable;
11
12
    /** @var array */
13
    protected $rawCertificateFields = [];
14
15
    /** @var string */
16
    protected $fingerprint = '';
17
18
    /** @var string */
19
    private $fingerprintSha256 = '';
20
21
    /** @var string */
22
    private $remoteAddress = '';
23
24
    public static function download(): Downloader
25
    {
26
        return new Downloader();
27
    }
28
29
    public static function createForHostName(string $url, int $timeout = 30): self
30
    {
31
        return Downloader::downloadCertificateFromUrl($url, $timeout);
32
    }
33
34 View Code Duplication
    public static function createFromString(string $certificate): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
35
    {
36
        $certificateFields = openssl_x509_parse($certificate);
37
38
        $fingerprint = openssl_x509_fingerprint($certificate);
39
        $fingerprintSha256 = openssl_x509_fingerprint($certificate, 'sha256');
40
41
        return new self(
42
            $certificateFields,
43
            $fingerprint,
44
            $fingerprintSha256
45
        );
46
    }
47
48
    public function __construct(
49
        array $rawCertificateFields,
50
        string $fingerprint = '',
51
        string $fingerprintSha256 = '',
52
        string $remoteAddress = ''
53
    ) {
54
        $this->rawCertificateFields = $rawCertificateFields;
55
56
        $this->fingerprint = $fingerprint;
57
58
        $this->fingerprintSha256 = $fingerprintSha256;
59
60
        $this->remoteAddress = $remoteAddress;
61
    }
62
63
    public function getRawCertificateFields(): array
64
    {
65
        return $this->rawCertificateFields;
66
    }
67
68
    public function getIssuer(): string
69
    {
70
        return $this->rawCertificateFields['issuer']['CN'] ?? '';
71
    }
72
73
    public function getDomain(): string
74
    {
75
        if (! array_key_exists('CN', $this->rawCertificateFields['subject'])) {
76
            return '';
77
        }
78
79
        if (is_string($this->rawCertificateFields['subject']['CN'])) {
80
            return $this->rawCertificateFields['subject']['CN'];
81
        }
82
83
        if (is_array($this->rawCertificateFields['subject']['CN'])) {
84
            return $this->rawCertificateFields['subject']['CN'][0];
85
        }
86
87
        return '';
88
    }
89
90
    public function getSignatureAlgorithm(): string
91
    {
92
        return $this->rawCertificateFields['signatureTypeSN'] ?? '';
93
    }
94
95
    public function getOrganization(): string
96
    {
97
        return $this->rawCertificateFields['subject']['O'] ?? '';
98
    }
99
100
    public function getFingerprint(): string
101
    {
102
        return $this->fingerprint;
103
    }
104
105
    /**
106
     * @return string
107
     */
108
    public function getFingerprintSha256(): string
109
    {
110
        return $this->fingerprintSha256;
111
    }
112
113
    public function getAdditionalDomains(): array
114
    {
115
        $additionalDomains = explode(', ', $this->rawCertificateFields['extensions']['subjectAltName'] ?? '');
116
117
        return array_map(function (string $domain) {
118
            return str_replace('DNS:', '', $domain);
119
        }, $additionalDomains);
120
    }
121
122
    public function validFromDate(): Carbon
123
    {
124
        return Carbon::createFromTimestampUTC($this->rawCertificateFields['validFrom_time_t']);
125
    }
126
127
    public function expirationDate(): Carbon
128
    {
129
        return Carbon::createFromTimestampUTC($this->rawCertificateFields['validTo_time_t']);
130
    }
131
132
    public function isExpired(): bool
133
    {
134
        return $this->expirationDate()->isPast();
135
    }
136
137
    public function isValid(string $url = null)
138
    {
139
        if (! Carbon::now()->between($this->validFromDate(), $this->expirationDate())) {
140
            return false;
141
        }
142
143
        if (! empty($url)) {
144
            return $this->appliesToUrl($url ?? $this->getDomain());
145
        }
146
147
        return true;
148
    }
149
150
    public function isSelfSigned(): bool
151
    {
152
        return $this->getIssuer() === $this->getDomain();
153
    }
154
155
    public function usesSha1Hash(): bool
156
    {
157
        $certificateFields = $this->getRawCertificateFields();
158
159
        if ($certificateFields['signatureTypeSN'] === 'RSA-SHA1') {
160
            return true;
161
        }
162
163
        if ($certificateFields['signatureTypeLN'] === 'sha1WithRSAEncryption') {
164
            return true;
165
        }
166
167
        return false;
168
    }
169
170
    public function isValidUntil(Carbon $carbon, string $url = null): bool
171
    {
172
        if ($this->expirationDate()->lte($carbon)) {
173
            return false;
174
        }
175
176
        return $this->isValid($url);
177
    }
178
179
    public function daysUntilExpirationDate(): int
180
    {
181
        $endDate = $this->expirationDate();
182
183
        $interval = Carbon::now()->diff($endDate);
184
185
        return (int) $interval->format('%r%a');
186
    }
187
188
    public function getDomains(): array
189
    {
190
        $allDomains = $this->getAdditionalDomains();
191
        $allDomains[] = $this->getDomain();
192
        $uniqueDomains = array_unique($allDomains);
193
194
        return array_values(array_filter($uniqueDomains));
195
    }
196
197
    public function appliesToUrl(string $url): bool
198
    {
199
        if (filter_var($url, FILTER_VALIDATE_IP)) {
200
            $host = $url;
201
        } else {
202
            $host = (new Url($url))->getHostName();
203
        }
204
205
        $certificateHosts = $this->getDomains();
206
207
        foreach ($certificateHosts as $certificateHost) {
208
            $certificateHost = str_replace('ip address:', '', strtolower($certificateHost));
209
            if ($host === $certificateHost) {
210
                return true;
211
            }
212
213
            if ($this->wildcardHostCoversHost($certificateHost, $host)) {
214
                return true;
215
            }
216
        }
217
218
        return false;
219
    }
220
221
    protected function wildcardHostCoversHost(string $wildcardHost, string $host): bool
222
    {
223
        if ($host === $wildcardHost) {
224
            return true;
225
        }
226
227
        if (! starts_with($wildcardHost, '*')) {
228
            return false;
229
        }
230
231
        if (substr_count($wildcardHost, '.') < substr_count($host, '.')) {
232
            return false;
233
        }
234
235
        $wildcardHostWithoutWildcard = substr($wildcardHost, 1);
236
237
        $hostWithDottedPrefix = ".{$host}";
238
239
        return ends_with($hostWithDottedPrefix, $wildcardHostWithoutWildcard);
240
    }
241
242
    public function getRawCertificateFieldsJson(): string
243
    {
244
        return json_encode($this->getRawCertificateFields());
245
    }
246
247
    public function getHash(): string
248
    {
249
        return md5($this->getRawCertificateFieldsJson());
250
    }
251
252
    public function getRemoteAddress(): string
253
    {
254
        return $this->remoteAddress;
255
    }
256
257
    public function __toString(): string
258
    {
259
        return $this->getRawCertificateFieldsJson();
260
    }
261
262
    public function containsDomain(string $domain): bool
263
    {
264
        $certificateHosts = $this->getDomains();
265
266
        foreach ($certificateHosts as $certificateHost) {
267
            if ($certificateHost == $domain) {
268
                return true;
269
            }
270
271
            if (ends_with($domain, '.'.$certificateHost)) {
272
                return true;
273
            }
274
        }
275
276
        return false;
277
    }
278
279
    public function isPreCertificate(): bool
280
    {
281
        if (! array_key_exists('extensions', $this->rawCertificateFields)) {
282
            return false;
283
        }
284
285
        if (! array_key_exists('ct_precert_poison', $this->rawCertificateFields['extensions'])) {
286
            return false;
287
        }
288
289
        return true;
290
    }
291
}
292