Passed
Push — master ( 336c6f...084e1e )
by Tim
01:24
created

CustomAssertionTrait   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 41
eloc 76
c 3
b 0
f 0
dl 0
loc 215
rs 9.1199

10 Methods

Rating   Name   Duplication   Size   Complexity  
A validQName() 0 9 4
A validURI() 0 11 5
A validURL() 0 6 3
A validDateTime() 0 9 4
A validDateTimeZulu() 0 15 6
A validDuration() 0 6 3
A validNCName() 0 6 3
B stringPlausibleBase64() 0 21 7
A notInArray() 0 11 3
A validURN() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like CustomAssertionTrait 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.

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 CustomAssertionTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Assert;
6
7
use DateTimeImmutable; // Requires ext-date
8
use InvalidArgumentException;
9
10
use function array_map;
11
use function base64_decode;
12
use function base64_encode;
13
use function call_user_func_array;
14
use function filter_var;
15
use function implode;
16
use function in_array;
17
use function reset;
18
use function sprintf;
19
20
/**
21
 * @package simplesamlphp/assert
22
 */
23
trait CustomAssertionTrait
24
{
25
    private static string $duration_regex = '/^(-?)P(?=.)((\d+)Y)?((\d+)M)?((\d+)D)?(T(?=.)((\d+)H)?((\d+)M)?(\d*(\.\d+)?S)?)?$/i';
26
27
    private static string $qname_regex = '/^[a-zA-Z_][\w.-]*:[a-zA-Z_][\w.-]*$/';
28
29
    private static string $ncname_regex = '/^[a-zA-Z_][\w.-]*$/';
30
31
    private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';
32
33
    private static string $uri_same_document_regex = '#^(?:\#([A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?|(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?))$#';
34
35
    private static string $urn_regex = '/\A(?i:urn:(?!urn:)(?<nid>[a-z0-9][a-z0-9-]{1,31}):(?<nss>(?:[-a-z0-9()+,.:=@;$_!*\'&~\/]|%[0-9a-f]{2})+)(?:\?\+(?<rcomponent>.*?))?(?:\?=(?<qcomponent>.*?))?(?:#(?<fcomponent>.*?))?)\z/';
36
37
    private static string $uri_regex = '#[A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?#';
38
39
    private static string $hostname_regex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';
40
41
    /***********************************************************************************
42
     *  NOTE:  Custom assertions may be added below this line.                         *
43
     *         They SHOULD be marked as `private` to ensure the call is forced         *
44
     *          through __callStatic().                                                *
45
     *         Assertions marked `public` are called directly and will                 *
46
     *          not handle any custom exception passed to it.                          *
47
     ***********************************************************************************/
48
49
50
    /**
51
     * @param string $value
52
     */
53
    private static function validDuration(string $value, string $message = ''): void
54
    {
55
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
56
            throw new InvalidArgumentException(sprintf(
57
                $message ?: '\'%s\' is not a valid xs:duration',
58
                $value
59
            ));
60
        }
61
    }
62
63
64
    /**
65
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
66
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
67
     *
68
     * @param string $value
69
     * @param string $message
70
     */
71
    private static function stringPlausibleBase64(string $value, string $message = ''): void
72
    {
73
        $result = true;
74
75
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
76
            $result = false;
77
        } elseif (strlen($value) % 4 !== 0) {
78
            $result = false;
79
        } else {
80
            $decoded = base64_decode($value, true);
81
            if ($decoded === false) {
82
                $result = false;
83
            } elseif (base64_encode($decoded) !== $value) {
84
                $result = false;
85
            }
86
        }
87
88
        if ($result === false) {
89
            throw new InvalidArgumentException(sprintf(
90
                $message ?: '\'%s\' is not a valid Base64 encoded string',
91
                $value
92
            ));
93
        }
94
    }
95
96
97
    /**
98
     * @param string $value
99
     * @param string $message
100
     */
101
    private static function validDateTime(string $value, string $message = ''): void
102
    {
103
        if (
104
            DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value) === false &&
105
            DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value) === false
106
        ) {
107
            throw new InvalidArgumentException(sprintf(
108
                $message ?: '\'%s\' is not a valid DateTime',
109
                $value
110
            ));
111
        }
112
    }
113
114
115
    /**
116
     * @param string $value
117
     * @param string $message
118
     */
119
    private static function validDateTimeZulu(string $value, string $message = ''): void
120
    {
121
        $dateTime1 = DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value);
122
        $dateTime2 = DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value);
123
124
        $dateTime = $dateTime1 ?: $dateTime2;
0 ignored issues
show
introduced by
$dateTime1 is of type DateTimeImmutable, thus it always evaluated to true.
Loading history...
125
        if ($dateTime === false) {
0 ignored issues
show
introduced by
The condition $dateTime === false is always false.
Loading history...
126
            throw new InvalidArgumentException(sprintf(
127
                $message ?: '\'%s\' is not a valid DateTime',
128
                $value
129
            ));
130
        } elseif ($dateTime->getTimezone()->getName() !== 'Z') {
131
            throw new InvalidArgumentException(sprintf(
132
                $message ?: '\'%s\' is not a DateTime expressed in the UTC timezone using the \'Z\' timezone identifier.',
133
                $value
134
            ));
135
        }
136
    }
137
138
139
    /**
140
     * @param mixed $value
141
     * @param array $values
142
     * @param string $message
143
     */
144
    private static function notInArray($value, array $values, string $message = ''): void
145
    {
146
        if (in_array($value, $values, true)) {
147
            $callable = /** @param mixed $val */function ($val) {
148
                return self::valueToString($val);
149
            };
150
151
            throw new InvalidArgumentException(sprintf(
152
                $message ?: 'Expected none of: %2$s. Got: %s',
153
                self::valueToString($value),
154
                implode(', ', array_map($callable, $values)),
155
            ));
156
        }
157
    }
158
159
160
    /**
161
     * @param string $value
162
     * @param string $message
163
     */
164
    private static function validURN(string $value, string $message = ''): void
165
    {
166
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false) {
167
            throw new InvalidArgumentException(sprintf(
168
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
169
                $value
170
            ));
171
        }
172
    }
173
174
175
    /**
176
     * @param string $value
177
     * @param string $message
178
     */
179
    private static function validURL(string $value, string $message = ''): void
180
    {
181
        if (filter_var($value, FILTER_VALIDATE_URL) === false) {
182
            throw new InvalidArgumentException(sprintf(
183
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
184
                $value
185
            ));
186
        }
187
    }
188
189
190
    /**
191
     * @param string $value
192
     * @param string $message
193
     */
194
    private static function validURI(string $value, string $message = ''): void
195
    {
196
        if (
197
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_regex]]) === false &&
198
            // We're very lenient here to accept DNS hostnames without a scheme
199
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$hostname_regex]]) === false &&
200
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_same_document_regex]]) === false
201
        ) {
202
            throw new InvalidArgumentException(sprintf(
203
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
204
                $value
205
            ));
206
        }
207
    }
208
209
210
    /**
211
     * @param string $value
212
     * @param string $message
213
     */
214
    private static function validNCName(string $value, string $message = ''): void
215
    {
216
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
217
            throw new InvalidArgumentException(sprintf(
218
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
219
                $value
220
            ));
221
        }
222
    }
223
224
225
    /**
226
     * @param string $value
227
     * @param string $message
228
     */
229
    private static function validQName(string $value, string $message = ''): void
230
    {
231
        if (
232
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
233
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
234
        ) {
235
            throw new InvalidArgumentException(sprintf(
236
                $message ?: '\'%s\' is not a valid qualified name (QName)',
237
                $value
238
            ));
239
        }
240
    }
241
}
242