PolicyTree   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 64
lcom 1
cbo 8
dl 0
loc 410
ccs 167
cts 167
cp 1
rs 3.28
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A processPolicies() 0 20 4
A processMappings() 0 14 4
C calculateIntersection() 0 61 10
A policiesAtDepth() 0 9 2
B _processPolicy() 0 28 6
B _processAnyPolicy() 0 23 7
A _applyMappings() 0 25 5
B _applyAnyPolicyMapping() 0 33 6
A _deleteMappings() 0 15 3
A _pruneTree() 0 16 5
A _nodesAtDepth() 0 16 4
A _validPolicyNodeSet() 0 25 5
A _gatherChildren() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like PolicyTree 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 PolicyTree, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace X509\CertificationPath\Policy;
6
7
use X509\Certificate\Certificate;
8
use X509\Certificate\Extension\CertificatePolicy\PolicyInformation;
9
use X509\CertificationPath\PathValidation\ValidatorState;
10
11
class PolicyTree
12
{
13
    /**
14
     * Root node at depth zero.
15
     *
16
     * @var PolicyNode|null
17
     */
18
    protected $_root;
19
    
20
    /**
21
     * Constructor.
22
     *
23
     * @param PolicyNode $root Initial root node
24
     */
25 49
    public function __construct(PolicyNode $root)
26
    {
27 49
        $this->_root = $root;
28 49
    }
29
    
30
    /**
31
     * Process policy information from the certificate.
32
     *
33
     * Certificate policies extension must be present.
34
     *
35
     * @param ValidatorState $state
36
     * @param Certificate $cert
37
     * @return ValidatorState
38
     */
39 14
    public function processPolicies(ValidatorState $state, Certificate $cert): ValidatorState
40
    {
41 14
        $policies = $cert->tbsCertificate()
42 14
            ->extensions()
43 14
            ->certificatePolicies();
44 14
        $tree = clone $this;
45
        // (d.1) for each policy P not equal to anyPolicy
46 14
        foreach ($policies as $policy) {
47 14
            if ($policy->isAnyPolicy()) {
48 6
                $tree->_processAnyPolicy($policy, $cert, $state);
49
            } else {
50 14
                $tree->_processPolicy($policy, $state);
51
            }
52
        }
53
        // if whole tree is pruned
54 14
        if (!$tree->_pruneTree($state->index() - 1)) {
55 1
            return $state->withoutValidPolicyTree();
56
        }
57 14
        return $state->withValidPolicyTree($tree);
58
    }
59
    
60
    /**
61
     * Process policy mappings from the certificate.
62
     *
63
     * @param ValidatorState $state
64
     * @param Certificate $cert
65
     * @return ValidatorState
66
     */
67 3
    public function processMappings(ValidatorState $state, Certificate $cert): ValidatorState
68
    {
69 3
        $tree = clone $this;
70 3
        if ($state->policyMapping() > 0) {
71 2
            $tree->_applyMappings($cert, $state);
72 1
        } else if ($state->policyMapping() == 0) {
73 1
            $tree->_deleteMappings($cert, $state);
74
        }
75
        // if whole tree is pruned
76 3
        if (!$tree->_root) {
77 1
            return $state->withoutValidPolicyTree();
78
        }
79 2
        return $state->withValidPolicyTree($tree);
80
    }
81
    
82
    /**
83
     * Calculate policy intersection as specified in Wrap-Up Procedure 6.1.5.g.
84
     *
85
     * @param ValidatorState $state
86
     * @param array $policies
87
     * @return ValidatorState
88
     */
89 6
    public function calculateIntersection(ValidatorState $state, array $policies): ValidatorState
90
    {
91 6
        $tree = clone $this;
92 6
        $valid_policy_node_set = $tree->_validPolicyNodeSet();
93
        // 2. If the valid_policy of any node in the valid_policy_node_set
94
        // is not in the user-initial-policy-set and is not anyPolicy,
95
        // delete this node and all its children.
96 6
        $valid_policy_node_set = array_filter($valid_policy_node_set,
97 6
            function (PolicyNode $node) use ($policies) {
98 6
                if ($node->isAnyPolicy()) {
99 4
                    return true;
100
                }
101 5
                if (in_array($node->validPolicy(), $policies)) {
102 3
                    return true;
103
                }
104 3
                $node->remove();
105 3
                return false;
106 6
            });
107
        // array of valid policy OIDs
108 6
        $valid_policy_set = array_map(
109 6
            function (PolicyNode $node) {
110 5
                return $node->validPolicy();
111 6
            }, $valid_policy_node_set);
112
        // 3. If the valid_policy_tree includes a node of depth n with
113
        // the valid_policy anyPolicy and the user-initial-policy-set 
114
        // is not any-policy
115 6
        foreach ($tree->_nodesAtDepth($state->index()) as $node) {
116 4
            if ($node->hasParent() && $node->isAnyPolicy()) {
117
                // a. Set P-Q to the qualifier_set in the node of depth n
118
                // with valid_policy anyPolicy.
119 1
                $pq = $node->qualifiers();
120
                // b. For each P-OID in the user-initial-policy-set that is not
121
                // the valid_policy of a node in the valid_policy_node_set,
122
                // create a child node whose parent is the node of depth n-1
123
                // with the valid_policy anyPolicy.
124 1
                $poids = array_diff($policies, $valid_policy_set);
125 1
                foreach ($tree->_nodesAtDepth($state->index() - 1) as $parent) {
126 1
                    if ($parent->isAnyPolicy()) {
127
                        // Set the values in the child node as follows: 
128
                        // set the valid_policy to P-OID, set the qualifier_set
129
                        // to P-Q, and set the expected_policy_set to {P-OID}.
130 1
                        foreach ($poids as $poid) {
131 1
                            $parent->addChild(
132 1
                                new PolicyNode($poid, $pq, array($poid)));
133
                        }
134 1
                        break;
135
                    }
136
                }
137
                // c. Delete the node of depth n with the
138
                // valid_policy anyPolicy.
139 4
                $node->remove();
140
            }
141
        }
142
        // 4. If there is a node in the valid_policy_tree of depth n-1 or less
143
        // without any child nodes, delete that node. Repeat this step until
144
        // there are no nodes of depth n-1 or less without children.
145 6
        if (!$tree->_pruneTree($state->index() - 1)) {
146 2
            return $state->withoutValidPolicyTree();
147
        }
148 4
        return $state->withValidPolicyTree($tree);
149
    }
150
    
151
    /**
152
     * Get policies at given policy tree depth.
153
     *
154
     * @param int $i Depth in range 1..n
155
     * @return PolicyInformation[]
156
     */
157 6
    public function policiesAtDepth(int $i): array
158
    {
159 6
        $policies = array();
160 6
        foreach ($this->_nodesAtDepth($i) as $node) {
161 5
            $policies[] = new PolicyInformation($node->validPolicy(),
162 5
                ...$node->qualifiers());
163
        }
164 6
        return $policies;
165
    }
166
    
167
    /**
168
     * Process single policy information.
169
     *
170
     * @param PolicyInformation $policy
171
     * @param ValidatorState $state
172
     */
173 12
    protected function _processPolicy(PolicyInformation $policy,
174
        ValidatorState $state)
175
    {
176 12
        $p_oid = $policy->oid();
177 12
        $i = $state->index();
178 12
        $match_count = 0;
179
        // (d.1.i) for each node of depth i-1 in the valid_policy_tree...
180 12
        foreach ($this->_nodesAtDepth($i - 1) as $node) {
181
            // ...where P-OID is in the expected_policy_set
182 12
            if ($node->hasExpectedPolicy($p_oid)) {
183 6
                $node->addChild(
184 6
                    new PolicyNode($p_oid, $policy->qualifiers(), array($p_oid)));
185 12
                ++$match_count;
186
            }
187
        }
188
        // (d.1.ii) if there was no match in step (i)...
189 12
        if (!$match_count) {
190
            // ...and the valid_policy_tree includes a node of depth i-1 with
191
            // the valid_policy anyPolicy
192 11
            foreach ($this->_nodesAtDepth($i - 1) as $node) {
193 11
                if ($node->isAnyPolicy()) {
194 11
                    $node->addChild(
195 11
                        new PolicyNode($p_oid, $policy->qualifiers(),
196 11
                            array($p_oid)));
197
                }
198
            }
199
        }
200 12
    }
201
    
202
    /**
203
     * Process anyPolicy policy information.
204
     *
205
     * @param PolicyInformation $policy
206
     * @param Certificate $cert
207
     * @param ValidatorState $state
208
     */
209 6
    protected function _processAnyPolicy(PolicyInformation $policy,
210
        Certificate $cert, ValidatorState $state)
211
    {
212 6
        $i = $state->index();
213
        // if (a) inhibit_anyPolicy is greater than 0 or
214
        // (b) i<n and the certificate is self-issued
215 6
        if (!($state->inhibitAnyPolicy() > 0 ||
216 6
             ($i < $state->pathLength() && $cert->isSelfIssued()))) {
217 1
            return;
218
        }
219
        // for each node in the valid_policy_tree of depth i-1
220 6
        foreach ($this->_nodesAtDepth($i - 1) as $node) {
221
            // for each value in the expected_policy_set
222 6
            foreach ($node->expectedPolicies() as $p_oid) {
223
                // that does not appear in a child node
224 6
                if (!$node->hasChildWithValidPolicy($p_oid)) {
225 6
                    $node->addChild(
226 6
                        new PolicyNode($p_oid, $policy->qualifiers(),
227 6
                            array($p_oid)));
228
                }
229
            }
230
        }
231 6
    }
232
    
233
    /**
234
     * Apply policy mappings to the policy tree.
235
     *
236
     * @param Certificate $cert
237
     * @param ValidatorState $state
238
     */
239 2
    protected function _applyMappings(Certificate $cert, ValidatorState $state)
240
    {
241 2
        $policy_mappings = $cert->tbsCertificate()
242 2
            ->extensions()
243 2
            ->policyMappings();
244
        // (6.1.4. b.1.) for each node in the valid_policy_tree of depth i...
245 2
        foreach ($policy_mappings->flattenedMappings() as $idp => $sdps) {
246 2
            $match_count = 0;
247 2
            foreach ($this->_nodesAtDepth($state->index()) as $node) {
248
                // ...where ID-P is the valid_policy
249 2
                if ($node->validPolicy() == $idp) {
250
                    // set expected_policy_set to the set of subjectDomainPolicy
251
                    // values that are specified as equivalent to ID-P by
252
                    // the policy mappings extension
253 1
                    $node->setExpectedPolicies(...$sdps);
254 2
                    ++$match_count;
255
                }
256
            }
257
            // if no node of depth i in the valid_policy_tree has
258
            // a valid_policy of ID-P...
259 2
            if (!$match_count) {
260 2
                $this->_applyAnyPolicyMapping($cert, $state, $idp, $sdps);
261
            }
262
        }
263 2
    }
264
    
265
    /**
266
     * Apply anyPolicy mapping to the policy tree as specified in 6.1.4 (b)(1).
267
     *
268
     * @param Certificate $cert
269
     * @param ValidatorState $state
270
     * @param string $idp OID of the issuer domain policy
271
     * @param array $sdps Array of subject domain policy OIDs
272
     */
273 2
    protected function _applyAnyPolicyMapping(Certificate $cert,
274
        ValidatorState $state, $idp, array $sdps)
275
    {
276
        // (6.1.4. b.1.) ...but there is a node of depth i with
277
        // a valid_policy of anyPolicy
278 2
        foreach ($this->_nodesAtDepth($state->index()) as $node) {
279 2
            if ($node->isAnyPolicy()) {
280
                // then generate a child node of the node of depth i-1
281
                // that has a valid_policy of anyPolicy as follows...
282 2
                foreach ($this->_nodesAtDepth($state->index() - 1) as $node) {
283 2
                    if ($node->isAnyPolicy()) {
284
                        // try to fetch qualifiers of anyPolicy certificate policy
285 2
                        $qualifiers = array();
286
                        try {
287 2
                            $qualifiers = $cert->tbsCertificate()
288 2
                                ->extensions()
289 2
                                ->certificatePolicies()
290 2
                                ->anyPolicy()
291 1
                                ->qualifiers();
292 1
                        } catch (\LogicException $e) {
293
                            // if there's no policies or no qualifiers
294
                        }
295 2
                        $node->addChild(
296 2
                            new PolicyNode($idp, $qualifiers, $sdps));
297
                        // bail after first anyPolicy has been processed
298 2
                        break;
299
                    }
300
                }
301
                // bail after first anyPolicy has been processed
302 2
                break;
303
            }
304
        }
305 2
    }
306
    
307
    /**
308
     * Delete nodes as specified in 6.1.4 (b)(2).
309
     *
310
     * @param Certificate $cert
311
     * @param ValidatorState $state
312
     */
313 1
    protected function _deleteMappings(Certificate $cert, ValidatorState $state)
314
    {
315 1
        $idps = $cert->tbsCertificate()
316 1
            ->extensions()
317 1
            ->policyMappings()
318 1
            ->issuerDomainPolicies();
319
        // delete each node of depth i in the valid_policy_tree
320
        // where ID-P is the valid_policy
321 1
        foreach ($this->_nodesAtDepth($state->index()) as $node) {
322 1
            if (in_array($node->validPolicy(), $idps)) {
323 1
                $node->remove();
324
            }
325
        }
326 1
        $this->_pruneTree($state->index() - 1);
327 1
    }
328
    
329
    /**
330
     * Prune tree starting from given depth.
331
     *
332
     * @param int $depth
333
     * @return int The number of nodes left in a tree
334
     */
335 14
    protected function _pruneTree(int $depth): int
336
    {
337 14
        for ($i = $depth; $i > 0; --$i) {
338 11
            foreach ($this->_nodesAtDepth($i) as $node) {
339 11
                if (!count($node)) {
340 11
                    $node->remove();
341
                }
342
            }
343
        }
344
        // if root has no children left
345 14
        if (!count($this->_root)) {
346 4
            $this->_root = null;
347 4
            return 0;
348
        }
349 14
        return $this->_root->nodeCount();
350
    }
351
    
352
    /**
353
     * Get all nodes at given depth.
354
     *
355
     * @param int $i
356
     * @return PolicyNode[]
357
     */
358 16
    protected function _nodesAtDepth(int $i): array
359
    {
360 16
        if (!$this->_root) {
361 1
            return array();
362
        }
363 15
        $depth = 0;
364 15
        $nodes = array($this->_root);
365 15
        while ($depth < $i) {
366 12
            $nodes = self::_gatherChildren(...$nodes);
367 12
            if (!count($nodes)) {
368 2
                break;
369
            }
370 12
            ++$depth;
371
        }
372 15
        return $nodes;
373
    }
374
    
375
    /**
376
     * Get the valid policy node set as specified in spec 6.1.5.(g)(iii)1.
377
     *
378
     * @return PolicyNode[]
379
     */
380 7
    protected function _validPolicyNodeSet(): array
381
    {
382
        // 1. Determine the set of policy nodes whose parent nodes have
383
        // a valid_policy of anyPolicy. This is the valid_policy_node_set.
384 7
        $set = array();
385 7
        if (!$this->_root) {
386 1
            return $set;
387
        }
388
        // for each node in a tree
389 6
        $this->_root->walkNodes(
390 6
            function (PolicyNode $node) use (&$set) {
391 6
                $parents = $node->parents();
392
                // node has parents
393 6
                if (count($parents)) {
394
                    // check that each ancestor is an anyPolicy node
395 6
                    foreach ($parents as $ancestor) {
396 6
                        if (!$ancestor->isAnyPolicy()) {
397 6
                            return;
398
                        }
399
                    }
400 6
                    $set[] = $node;
401
                }
402 6
            });
403 6
        return $set;
404
    }
405
    
406
    /**
407
     * Gather all children of given nodes to a flattened array.
408
     *
409
     * @param PolicyNode ...$nodes
410
     * @return PolicyNode[]
411
     */
412 12
    private static function _gatherChildren(PolicyNode ...$nodes): array
413
    {
414 12
        $children = array();
415 12
        foreach ($nodes as $node) {
416 12
            $children = array_merge($children, $node->children());
417
        }
418 12
        return $children;
419
    }
420
}
421