Completed
Push — master ( 28b7de...6d9006 )
by Damien
10:13
created

Metadata::createIdpDescriptor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 9.312
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
namespace flipbox\saml\core\services;
4
5
use craft\base\Component;
6
use flipbox\keychain\records\KeyChainRecord;
7
use flipbox\saml\core\helpers\SecurityHelper;
8
use flipbox\saml\core\models\SettingsInterface;
9
use SAML2\Certificate\Key;
10
use SAML2\Constants;
11
use SAML2\XML\ds\KeyInfo;
12
use SAML2\XML\ds\X509Certificate;
13
use SAML2\XML\ds\X509Data;
14
use SAML2\XML\md\EndpointType;
15
use SAML2\XML\md\IndexedEndpointType;
16
use SAML2\XML\md\EntityDescriptor;
17
use SAML2\XML\md\IDPSSODescriptor;
18
use SAML2\XML\md\KeyDescriptor;
19
use SAML2\XML\md\SPSSODescriptor;
20
use SAML2\XML\md\SSODescriptorType;
21
use yii\base\Event;
22
use yii\base\InvalidConfigException;
23
24
/**
25
 * Class AbstractMetadata
26
 * @package flipbox\saml\core\services\messages
27
 */
28
class Metadata extends Component
29
{
30
31
    const SET_SIGNING = Key::USAGE_SIGNING;
32
    const SET_ENCRYPTION = Key::USAGE_ENCRYPTION;
33
    const PROTOCOL = Constants::NS_SAMLP;
34
35
    const EVENT_AFTER_MESSAGE_CREATED = 'eventAfterMessageCreated';
36
37
    /**
38
     * @var array
39
     */
40
    protected $supportedBindings = [
41
        Constants::BINDING_HTTP_POST,
42
    ];
43
44
    /**
45
     * @return array
46
     */
47
    public function getSupportedBindings()
48
    {
49
        return $this->supportedBindings;
50
    }
51
52
    /**
53
     * @return bool
54
     */
55
    protected function supportsRedirect()
56
    {
57
        return in_array(Constants::BINDING_HTTP_REDIRECT, $this->getSupportedBindings());
58
    }
59
60
    /**
61
     * @return bool
62
     */
63
    protected function supportsPost()
64
    {
65
        return in_array(Constants::BINDING_HTTP_POST, $this->getSupportedBindings());
66
    }
67
68
    /**
69
     * @param SettingsInterface $settings
70
     * @param KeyChainRecord|null $withKeyPair
71
     * @return EntityDescriptor
72
     * @throws InvalidConfigException
73
     */
74
    public function create(
75
        SettingsInterface $settings,
76
        KeyChainRecord $withKeyPair = null
77
    ): EntityDescriptor {
78
79
        $entityDescriptor = new EntityDescriptor();
80
81
        $entityId = $settings->getEntityId();
82
83
        $entityDescriptor->setEntityID($entityId);
84
85
        foreach ($this->getSupportedBindings() as $binding) {
86
            $entityDescriptor->addRoleDescriptor(
87
                $descriptor = $this->createDescriptor($binding, $settings)
88
            );
89
90
            /**
91
             * Add security settings
92
             */
93
            if ($withKeyPair) {
94
                $this->setEncrypt($descriptor, $withKeyPair);
95
                $this->setSign($descriptor, $withKeyPair);
96
            }
97
        }
98
99
        /**
100
         * Kick off event here so people can manipulate this object if needed
101
         */
102
        $event = new Event();
103
104
        /**
105
         * response
106
         */
107
        $event->data = $entityDescriptor;
108
        $this->trigger(static::EVENT_AFTER_MESSAGE_CREATED, $event);
109
110
        return $entityDescriptor;
111
    }
112
113
    /**
114
     * @param string $binding
115
     * @return IdpSsoDescriptor|SpSsoDescriptor
116
     * @throws InvalidConfigException
117
     */
118
    protected function createDescriptor(string $binding, SettingsInterface $settings)
119
    {
120
        if (! in_array($binding, [
121
            Constants::BINDING_HTTP_POST,
122
            Constants::BINDING_HTTP_REDIRECT,
123
        ])) {
124
            throw new InvalidConfigException('Binding not supported: ' . $binding);
125
        }
126
127
        if ($settings->getMyType() === $settings::SP) {
128
            $descriptor = $this->createSpDescriptor($binding, $settings);
129
        } else {
130
            $descriptor = $this->createIdpDescriptor($binding, $settings);
131
        }
132
133
        return $descriptor;
134
    }
135
136
    /**
137
     * @param string $binding
138
     * @return IDPSSODescriptor
139
     */
140
    protected function createIdpDescriptor(string $binding, SettingsInterface $settings)
141
    {
142
        $descriptor = new \SAML2\XML\md\IDPSSODescriptor();
143
        $descriptor->setProtocolSupportEnumeration([
144
            static::PROTOCOL,
145
        ]);
146
147
        if (property_exists($settings, 'wantsAuthnRequestsSigned')) {
148
            $descriptor->setWantAuthnRequestsSigned(
149
                $settings->wantsAuthnRequestsSigned
0 ignored issues
show
Bug introduced by
Accessing wantsAuthnRequestsSigned on the interface flipbox\saml\core\models\SettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
150
            );
151
        }
152
153
        // SSO
154
        $ssoEndpoint = new EndpointType();
155
        $ssoEndpoint->setBinding($binding);
156
        $ssoEndpoint->setLocation(
157
            $settings->getDefaultLoginEndpoint()
158
        );
159
        $descriptor->setSingleSignOnService([
160
            $ssoEndpoint,
161
        ]);
162
163
        // SLO
164
        $this->addSloEndpoint(
165
            $descriptor,
166
            $settings
167
        );
168
169
        // todo add attributes from mapping
170
//        $attribute = new Attribute();
171
//        $attribute->setName('Username');
172
//        $descriptor->addAttribute(
173
//            $attribute
174
//        );
175
176
        return $descriptor;
177
    }
178
179
    /**
180
     * @param string $binding
181
     * @return SPSSODescriptor
182
     */
183
    protected function createSpDescriptor(string $binding, SettingsInterface $settings)
184
    {
185
186
        $descriptor = new SPSSODescriptor();
187
        $descriptor->setProtocolSupportEnumeration([
188
            static::PROTOCOL,
189
        ]);
190
191
        if (property_exists($settings, 'wantsSignedAssertions') &&
192
            is_bool($settings->wantsSignedAssertions)
0 ignored issues
show
Bug introduced by
Accessing wantsSignedAssertions on the interface flipbox\saml\core\models\SettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
193
        ) {
194
            $descriptor->setWantAssertionsSigned(
195
                $settings->wantsSignedAssertions
0 ignored issues
show
Bug introduced by
Accessing wantsSignedAssertions on the interface flipbox\saml\core\models\SettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
196
            );
197
        }
198
199
200
        // ACS
201
        $acsEndpoint = new IndexedEndpointType();
202
        $acsEndpoint->setIndex(1);
203
        $acsEndpoint->setBinding($binding);
204
        $acsEndpoint->setLocation(
205
            $settings->getDefaultLoginEndpoint()
206
        );
207
208
        $descriptor->setAssertionConsumerService([
209
            $acsEndpoint,
210
        ]);
211
212
        //SLO
213
        $this->addSloEndpoint(
214
            $descriptor,
215
            $settings
216
        );
217
218
        //todo add attribute consuming service
219
//        $attributeConsumingService = new AttributeConsumingService();
220
//        $attributeConsumingService->addRequestedAttribute($att = new RequestedAttribute());
221
//        $att->setName('username');
222
//        $descriptor->addAttributeConsumingService($attributeConsumingService);
223
224
        return $descriptor;
225
    }
226
227
    protected function addSloEndpoint(SSODescriptorType $descriptorType, SettingsInterface $settings)
228
    {
229
        $sloEndpointRedirect = new EndpointType();
230
        $sloEndpointRedirect->setBinding(
231
            Constants::BINDING_HTTP_REDIRECT
232
        );
233
        $sloEndpointRedirect->setLocation(
234
            $settings->getDefaultLogoutEndpoint()
235
        );
236
237
        $sloEndpointPost = new EndpointType();
238
        $sloEndpointPost->setBinding(
239
            Constants::BINDING_HTTP_POST
240
        );
241
        $sloEndpointPost->setLocation(
242
            $settings->getDefaultLogoutEndpoint()
243
        );
244
245
        $descriptorType->setSingleLogoutService([
246
            $sloEndpointRedirect,
247
            $sloEndpointPost,
248
        ]);
249
    }
250
251
    /**
252
     * @param SSODescriptorType $ssoDescriptor
253
     * @param KeyChainRecord $keyChainRecord
254
     */
255
    protected function setCertificate(
256
        SSODescriptorType $ssoDescriptor,
257
        KeyChainRecord $keyChainRecord,
258
        string $signOrEncrypt
259
    ) {
260
        /**
261
         * Validate use string
262
         */
263
        if (! in_array($signOrEncrypt, [
264
            self::SET_SIGNING,
265
            self::SET_ENCRYPTION,
266
        ])) {
267
            throw new \InvalidArgumentException('Sign or Encrypt argument can only be "signing" or "encrypt"');
268
        }
269
270
        /**
271
         * Create sub object
272
         */
273
        $keyDescriptor = new KeyDescriptor();
274
        $keyInfo = new KeyInfo();
275
        $x509Data = new X509Data();
276
        $x509Certificate = new X509Certificate();
277
278
        $keyInfo->addInfo($x509Data);
279
280
        $x509Certificate->setCertificate(
281
            SecurityHelper::cleanCertificate($keyChainRecord->getDecryptedCertificate())
0 ignored issues
show
Bug introduced by
It seems like $keyChainRecord->getDecryptedCertificate() targeting flipbox\keychain\records...tDecryptedCertificate() can also be of type boolean; however, flipbox\saml\core\helper...per::cleanCertificate() 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...
Bug introduced by
It seems like \flipbox\saml\core\helpe...DecryptedCertificate()) targeting flipbox\saml\core\helper...per::cleanCertificate() can also be of type array<integer,string> or null; however, SAML2\XML\ds\X509Certificate::setCertificate() 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...
282
        );
283
284
        $x509Data->addData($x509Certificate);
285
        $keyDescriptor->setKeyInfo($keyInfo);
286
287
        $keyDescriptor->setUse($signOrEncrypt);
288
        $ssoDescriptor->addKeyDescriptor($keyDescriptor);
289
    }
290
291
    /**
292
     * @param SSODescriptorType
293
     * @param KeyChainRecord $keyChainRecord
294
     */
295
    protected function setSign(SSODescriptorType $ssoDescriptor, KeyChainRecord $keyChainRecord)
296
    {
297
        $this->setCertificate($ssoDescriptor, $keyChainRecord, static::SET_SIGNING);
298
    }
299
300
    /**
301
     * @param SSODescriptorType
302
     * @param KeyChainRecord $keyChainRecord
303
     */
304
    protected function setEncrypt(SSODescriptorType $ssoDescriptor, KeyChainRecord $keyChainRecord)
305
    {
306
        $this->setCertificate($ssoDescriptor, $keyChainRecord, static::SET_ENCRYPTION);
307
    }
308
}
309