Completed
Push — master ( 14770a...7c2760 )
by Damien
08:49
created

EntityDescriptor::firstCertificate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 4
nc 4
nop 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->getKeyDescriptors())) {
133
            return $store;
134
        }
135
136
        foreach ($keyDescriptors as $keyDescriptor) {
137
            $x509Certificates = $this->getX509Certificates($keyDescriptor);
138
            $store = array_merge(
139
                $store,
140
                $this->certificatesToXMLSecurityKeyStore($x509Certificates)
141
            );
142
        }
143
144
        return $store;
145
    }
146
147
    /**
148
     * @return XMLSecurityKey|null
149
     * @throws \Exception
150
     *
151
     * For Decryption
152
     * @see SecurityHelper::decryptAssertion()
153
     */
154
    public function encryptionKey()
155
    {
156
        if (! $certificate = $this->firstCertificateForEncryption()) {
157
            return null;
158
        }
159
160
        $pem = Certificate::convertToCertificate(
161
            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...
162
                $certificate->getCertificate()
163
            )
164
        );
165
166
        $xmlSecurityKey = new XMLSecurityKey(
167
            XMLSecurityKey::RSA_OAEP_MGF1P,
168
            [
169
                'type' => 'public',
170
            ]
171
        );
172
173
        $xmlSecurityKey->loadKey($pem);
174
175
        return $xmlSecurityKey;
176
    }
177
178
    /**
179
     * @param X509Certificate $certificate
180
     * @return XMLSecurityKey
181
     * @throws \Exception
182
     */
183
    protected function certificateToXMLSecurityKey(X509Certificate $certificate)
184
    {
185
        $pem = Certificate::convertToCertificate(
186
            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...
187
                $certificate->getCertificate()
188
            )
189
        );
190
191
        $xmlSecurityKey = new XMLSecurityKey(
192
            SecurityHelper::getPemAlgorithm($pem),
193
            [
194
                'type' => 'public',
195
            ]
196
        );
197
198
199
        $xmlSecurityKey->loadKey($pem, false, true);
200
201
        return $xmlSecurityKey;
202
    }
203
204
    /**
205
     * @param array $certificates
206
     * @return array
207
     * @throws \Exception
208
     */
209
    protected function certificatesToXMLSecurityKeyStore(array $certificates)
210
    {
211
        $store = [];
212
        foreach ($certificates as $certificate) {
213
            $store[] = $this->certificateToXMLSecurityKey($certificate);
214
        }
215
        return $store;
216
    }
217
218
    /**
219
     * @param array $keyDescriptors
220
     * @param string $signingOrEncrypt
221
     * @return KeyDescriptor|null
222
     */
223
    protected function firstKeyDescriptor(array $keyDescriptors, string $signingOrEncrypt)
224
    {
225
        /** @var KeyDescriptor[] $keyDescriptor */
226
        $keyDescriptor = array_filter(
227
            $keyDescriptors,
228
            function (KeyDescriptor $keyDescriptor) use ($signingOrEncrypt) {
229
                return $keyDescriptor->getUse() === $signingOrEncrypt;
230
            }
231
        );
232
233
        return count($keyDescriptor) > 0 ? array_shift($keyDescriptor) : null;
234
    }
235
236
    /**
237
     * @return X509Certificate|null
238
     */
239
    protected function firstCertificateForSigning()
240
    {
241
        return $this->firstCertificate(Key::USAGE_SIGNING);
242
    }
243
244
    /**
245
     * @return X509Certificate|null
246
     */
247
    protected function firstCertificateForEncryption()
248
    {
249
        return $this->firstCertificate(Key::USAGE_ENCRYPTION);
250
    }
251
252
    /**
253
     * @return X509Certificate|null
254
     */
255
    protected function firstCertificate($use)
256
    {
257
258
        if (! ($keyDescriptors = $this->getKeyDescriptors())) {
259
            return null;
260
        }
261
262
        /** @var KeyDescriptor $keyDescriptor */
263
        $keyDescriptor = $this->firstKeyDescriptor($keyDescriptors, $use);
264
265
        if (is_null($keyDescriptor)) {
266
            return null;
267
        }
268
269
        $certificates = $this->getX509Certificates($keyDescriptor);
270
        return ! empty($certificates) ? $certificates[0] : null;
271
    }
272
273
    /**
274
     * @return KeyDescriptor[]
275
     */
276
    private function getKeyDescriptors()
277
    {
278
279
        if (empty($this->spSsoDescriptors()) && empty($this->idpSsoDescriptors())) {
280
            return null;
281
        }
282
283
        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...
284
            /** @var IDPSSODescriptor $ssoDescriptor */
285
            $ssoDescriptor = $this->idpSsoDescriptors()[0];
286
        } else {
287
            /** @var SPSSODescriptor $ssoDescriptor */
288
            $ssoDescriptor = $this->spSsoDescriptors()[0];
289
        }
290
291
        return $ssoDescriptor->getKeyDescriptor();
292
    }
293
294
    /**
295
     * @param KeyDescriptor $keyDescriptor
296
     * @return array|X509Certificate[]
297
     */
298
    private function getX509Certificates(KeyDescriptor $keyDescriptor)
299
    {
300
301
        $x509Datas = array_filter(
302
            $keyDescriptor->getKeyInfo()->getInfo(),
303
            function ($keyInfo) {
304
                return $keyInfo instanceof X509Data;
305
            }
306
        );
307
308
        /** @var X509Certificate[] $x509Certificates */
309
        $x509Certificates = [];
310
311
        /** @var X509Data $x509Data */
312
        foreach ($x509Datas as $x509Data) {
313
            $x509Certificates = array_filter(
314
                $x509Data->getData(),
315
                function ($datum) {
316
                    return $datum instanceof X509Certificate;
317
                }
318
            );
319
        }
320
321
        return $x509Certificates;
322
    }
323
324
    /**
325
     * @return string
326
     */
327
    public function toXmlString()
328
    {
329
        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...
330
    }
331
332
    /**
333
     * @return string
334
     */
335
    public function __toString()
336
    {
337
        return $this->toXmlString();
338
    }
339
}
340