EntityDescriptor   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 6
dl 0
loc 329
rs 9.6
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A idpSsoDescriptors() 0 8 2
A spSsoDescriptors() 0 8 2
A firstSpAcsService() 0 4 1
A firstIdpSsoService() 0 4 1
A firstIdpSloService() 0 4 1
A firstSpSloService() 0 4 1
A signingXMLSecurityKey() 0 8 2
A signingXMLSecurityKeyStore() 0 20 3
A encryptionKey() 0 23 2
A certificateToXMLSecurityKey() 0 19 1
A certificatesToXMLSecurityKeyStore() 0 8 2
A firstKeyDescriptor() 0 7 2
A firstCertificateForSigning() 0 4 1
A firstCertificateForEncryption() 0 4 1
A firstCertificate() 0 17 4
A keyDescriptors() 0 17 4
A keyDescriptorsByType() 0 13 1
A x509Certificates() 0 25 2
A toXmlString() 0 4 1
A __toString() 0 4 1
1
<?php
2
3
4
namespace flipbox\saml\core\records\traits;
5
6
use flipbox\saml\core\helpers\EntityDescriptorHelper;
7
use flipbox\saml\core\helpers\SecurityHelper;
8
use flipbox\saml\core\records\AbstractProvider;
9
use RobRichards\XMLSecLibs\XMLSecurityKey;
10
use SAML2\Certificate\Key;
11
use SAML2\Utilities\Certificate;
12
use SAML2\XML\ds\X509Certificate;
13
use SAML2\XML\ds\X509Data;
14
use SAML2\XML\md\EntityDescriptor as SAML2EntityDescriptor;
15
use SAML2\XML\md\IDPSSODescriptor;
16
use SAML2\XML\md\IndexedEndpointType;
17
use SAML2\XML\md\KeyDescriptor;
18
use SAML2\XML\md\SPSSODescriptor;
19
20
/**
21
 * Trait EntityDescriptor
22
 * @package flipbox\saml\core\records\traits
23
 * @method SAML2EntityDescriptor getMetadataModel
24
 * @mixin AbstractProvider
25
 */
26
trait EntityDescriptor
27
{
28
29
    /**
30
     * IDP descriptors from the metadata
31
     * @see AbstractProvider::getMetadataModel()
32
     * @var IDPSSODescriptor[]
33
     */
34
    private $idpSsoDescriptors = [];
35
36
    /**
37
     * SP descriptors from the metadata
38
     * @see AbstractProvider::getMetadataModel()
39
     * @var SPSSODescriptor[]
40
     */
41
    private $spSsoDescriptors = [];
42
43
    /**
44
     * Get the role descriptors from metadata
45
     * @return IDPSSODescriptor[]
46
     */
47
    public function idpSsoDescriptors()
48
    {
49
        if (! $this->idpSsoDescriptors) {
50
            $this->idpSsoDescriptors = EntityDescriptorHelper::getIdpDescriptors($this->getMetadataModel());
0 ignored issues
show
Bug introduced by
It seems like getMetadataModel() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Documentation Bug introduced by
It seems like \flipbox\saml\core\helpe...is->getMetadataModel()) of type array<integer,object<SAM...\md\SSODescriptorType>> is incompatible with the declared type array<integer,object<SAM...L\md\IDPSSODescriptor>> of property $idpSsoDescriptors.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
51
        }
52
53
        return $this->idpSsoDescriptors;
54
    }
55
56
    /**
57
     * Get the role descriptors from metadata
58
     * @return SPSSODescriptor[]
59
     */
60
    public function spSsoDescriptors()
61
    {
62
        if (! $this->spSsoDescriptors) {
63
            $this->spSsoDescriptors = EntityDescriptorHelper::getSpDescriptors($this->getMetadataModel());
0 ignored issues
show
Bug introduced by
It seems like getMetadataModel() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Documentation Bug introduced by
It seems like \flipbox\saml\core\helpe...is->getMetadataModel()) of type array<integer,object<SAM...\md\SSODescriptorType>> is incompatible with the declared type array<integer,object<SAM...ML\md\SPSSODescriptor>> of property $spSsoDescriptors.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
64
        }
65
66
        return $this->spSsoDescriptors;
67
    }
68
69
    /**
70
     * @param null $binding
71
     * @return IndexedEndpointType|null
72
     */
73
    public function firstSpAcsService($binding = null)
74
    {
75
        return EntityDescriptorHelper::getFirstSpAssertionConsumerService($this->spSsoDescriptors(), $binding);
76
    }
77
78
    /** SSO */
79
80
    /**
81
     * @param null $binding
82
     * @return IndexedEndpointType|null
83
     */
84
    public function firstIdpSsoService($binding = null)
85
    {
86
        return EntityDescriptorHelper::getFirstIdpSSOService($this->idpSsoDescriptors(), $binding);
87
    }
88
89
    /** SLO */
90
91
    /**
92
     * @param null $binding
93
     * @return IndexedEndpointType|null
94
     */
95
    public function firstIdpSloService($binding = null)
96
    {
97
        return EntityDescriptorHelper::getFirstSLOService($this->idpSsoDescriptors(), $binding);
98
    }
99
100
    /**
101
     * @param null $binding
102
     * @return IndexedEndpointType|null
103
     */
104
    public function firstSpSloService($binding = null)
105
    {
106
        return EntityDescriptorHelper::getFirstSLOService($this->spSsoDescriptors(), $binding);
107
    }
108
109
    /** X509s */
110
111
    /**
112
     * @return XMLSecurityKey
113
     * @throws \Exception
114
     */
115
    public function signingXMLSecurityKey()
116
    {
117
        if (! $certificate = $this->firstCertificateForSigning()) {
118
            return null;
119
        }
120
121
        return $this->certificateToXMLSecurityKey($certificate);
122
    }
123
124
    /**
125
     * @return array
126
     * @throws \Exception
127
     */
128
    public function signingXMLSecurityKeyStore()
129
    {
130
131
        $store = [];
132
        if (! ($keyDescriptors = $this->keyDescriptors())) {
133
            return $store;
134
        }
135
136
        $keyDescriptors = $this->keyDescriptorsByType($keyDescriptors, Key::USAGE_SIGNING);
137
138
        foreach ($keyDescriptors as $keyDescriptor) {
139
            $x509Certificates = $this->x509Certificates($keyDescriptor);
140
            $store = array_merge(
141
                $store,
142
                $this->certificatesToXMLSecurityKeyStore($x509Certificates)
143
            );
144
        }
145
146
        return $store;
147
    }
148
149
    /**
150
     * @return XMLSecurityKey|null
151
     * @throws \Exception
152
     *
153
     * For Decryption
154
     * @see SecurityHelper::decryptAssertion()
155
     */
156
    public function encryptionKey()
157
    {
158
        if (! $certificate = $this->firstCertificateForEncryption()) {
159
            return null;
160
        }
161
162
        $pem = Certificate::convertToCertificate(
163
            SecurityHelper::cleanCertificateWhiteSpace(
0 ignored issues
show
Bug introduced by
It seems like \flipbox\saml\core\helpe...cate->getCertificate()) targeting flipbox\saml\core\helper...CertificateWhiteSpace() can also be of type array<integer,string> or null; however, SAML2\Utilities\Certific...:convertToCertificate() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
164
                $certificate->getCertificate()
165
            )
166
        );
167
168
        $xmlSecurityKey = new XMLSecurityKey(
169
            XMLSecurityKey::RSA_OAEP_MGF1P,
170
            [
171
                'type' => 'public',
172
            ]
173
        );
174
175
        $xmlSecurityKey->loadKey($pem);
176
177
        return $xmlSecurityKey;
178
    }
179
180
    /**
181
     * @param X509Certificate $certificate
182
     * @return XMLSecurityKey
183
     * @throws \Exception
184
     */
185
    protected function certificateToXMLSecurityKey(X509Certificate $certificate)
186
    {
187
        $pem = Certificate::convertToCertificate(
188
            SecurityHelper::cleanCertificateWhiteSpace(
0 ignored issues
show
Bug introduced by
It seems like \flipbox\saml\core\helpe...cate->getCertificate()) targeting flipbox\saml\core\helper...CertificateWhiteSpace() can also be of type array<integer,string> or null; however, SAML2\Utilities\Certific...:convertToCertificate() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
189
                $certificate->getCertificate()
190
            )
191
        );
192
193
        $xmlSecurityKey = new XMLSecurityKey(
194
            XMLSecurityKey::RSA_SHA256,
195
            [
196
                'type' => 'public',
197
            ]
198
        );
199
200
        $xmlSecurityKey->loadKey($pem, false, true);
201
202
        return $xmlSecurityKey;
203
    }
204
205
    /**
206
     * @param array $certificates
207
     * @return array
208
     * @throws \Exception
209
     */
210
    protected function certificatesToXMLSecurityKeyStore(array $certificates)
211
    {
212
        $store = [];
213
        foreach ($certificates as $certificate) {
214
            $store[] = $this->certificateToXMLSecurityKey($certificate);
215
        }
216
        return $store;
217
    }
218
219
    /**
220
     * @param array $keyDescriptors
221
     * @param string $signingOrEncrypt
222
     * @return KeyDescriptor|null
223
     */
224
    protected function firstKeyDescriptor(array $keyDescriptors, string $signingOrEncrypt)
225
    {
226
        /** @var KeyDescriptor[] $keyDescriptorFiltered */
227
        $keyDescriptorFiltered = $this->keyDescriptorsByType($keyDescriptors, $signingOrEncrypt);
228
229
        return count($keyDescriptorFiltered) > 0 ? array_shift($keyDescriptorFiltered) : null;
230
    }
231
232
    /**
233
     * @return X509Certificate|null
234
     */
235
    protected function firstCertificateForSigning()
236
    {
237
        return $this->firstCertificate(Key::USAGE_SIGNING);
238
    }
239
240
    /**
241
     * @return X509Certificate|null
242
     */
243
    protected function firstCertificateForEncryption()
244
    {
245
        return $this->firstCertificate(Key::USAGE_ENCRYPTION);
246
    }
247
248
    /**
249
     * @return X509Certificate|null
250
     */
251
    protected function firstCertificate($use)
252
    {
253
254
        if (! ($keyDescriptors = $this->keyDescriptors())) {
255
            return null;
256
        }
257
258
        /** @var KeyDescriptor $keyDescriptor */
259
        $keyDescriptor = $this->firstKeyDescriptor($keyDescriptors, $use);
260
261
        if (is_null($keyDescriptor)) {
262
            return null;
263
        }
264
265
        $certificates = $this->x509Certificates($keyDescriptor);
266
        return ! empty($certificates) ? $certificates[0] : null;
267
    }
268
269
    /**
270
     * @return KeyDescriptor[]
271
     */
272
    private function keyDescriptors()
273
    {
274
275
        if (empty($this->spSsoDescriptors()) && empty($this->idpSsoDescriptors())) {
276
            return null;
277
        }
278
279
        if ($this->isIdentityProvider()) {
0 ignored issues
show
Bug introduced by
It seems like isIdentityProvider() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
280
            /** @var IDPSSODescriptor $ssoDescriptor */
281
            $ssoDescriptor = $this->idpSsoDescriptors()[0];
282
        } else {
283
            /** @var SPSSODescriptor $ssoDescriptor */
284
            $ssoDescriptor = $this->spSsoDescriptors()[0];
285
        }
286
287
        return $ssoDescriptor->getKeyDescriptor();
288
    }
289
290
    /**
291
     * @param array $keyDescriptors
292
     * @param string $signingOrEncrypt
293
     * @return KeyDescriptor[]
294
     */
295
    private function keyDescriptorsByType(array $keyDescriptors, string $signingOrEncrypt)
296
    {
297
298
        /** @var KeyDescriptor[] $keyDescriptorsFiltered */
299
        $keyDescriptorsFiltered = array_filter(
300
            $keyDescriptors,
301
            function (KeyDescriptor $keyDescriptor) use ($signingOrEncrypt) {
302
                return $keyDescriptor->getUse() === $signingOrEncrypt;
303
            }
304
        );
305
306
        return $keyDescriptorsFiltered;
307
    }
308
309
    /**
310
     * @param KeyDescriptor $keyDescriptor
311
     * @return array|X509Certificate[]
312
     */
313
    private function x509Certificates(KeyDescriptor $keyDescriptor)
314
    {
315
316
        $x509Datas = array_filter(
317
            $keyDescriptor->getKeyInfo()->getInfo(),
318
            function ($keyInfo) {
319
                return $keyInfo instanceof X509Data;
320
            }
321
        );
322
323
        /** @var X509Certificate[] $x509Certificates */
324
        $x509Certificates = [];
325
326
        /** @var X509Data $x509Data */
327
        foreach ($x509Datas as $x509Data) {
328
            $x509Certificates = array_filter(
329
                $x509Data->getData(),
330
                function ($datum) {
331
                    return $datum instanceof X509Certificate;
332
                }
333
            );
334
        }
335
336
        return $x509Certificates;
337
    }
338
339
    /**
340
     * @return string
341
     */
342
    public function toXmlString()
343
    {
344
        return $this->getMetadataModel()->toXML()->ownerDocument->saveXML();
0 ignored issues
show
Bug introduced by
It seems like getMetadataModel() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
345
    }
346
347
    /**
348
     * @return string
349
     */
350
    public function __toString()
351
    {
352
        return $this->toXmlString();
353
    }
354
}
355