Completed
Push — master ( 4b394d...0ca4f0 )
by Damien
09:55
created

Metadata::create()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 9.296
c 0
b 0
f 0
nc 3
cc 3
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\EntityDescriptor;
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
use SAML2\XML\md\SSODescriptorType;
20
use yii\base\Event;
21
use yii\base\InvalidConfigException;
22
23
/**
24
 * Class AbstractMetadata
25
 * @package flipbox\saml\core\services\messages
26
 */
27
class Metadata extends Component
28
{
29
30
    const SET_SIGNING = Key::USAGE_SIGNING;
31
    const SET_ENCRYPTION = Key::USAGE_ENCRYPTION;
32
33
    const EVENT_AFTER_MESSAGE_CREATED = 'eventAfterMessageCreated';
34
35
    /**
36
     * @var array
37
     */
38
    protected $supportedBindings = [
39
        Constants::BINDING_HTTP_POST,
40
    ];
41
42
    /**
43
     * @return array
44
     */
45
    public function getSupportedBindings()
46
    {
47
        return $this->supportedBindings;
48
    }
49
50
    /**
51
     * @return bool
52
     */
53
    protected function supportsRedirect()
54
    {
55
        return in_array(Constants::BINDING_HTTP_REDIRECT, $this->getSupportedBindings());
56
    }
57
58
    /**
59
     * @return bool
60
     */
61
    protected function supportsPost()
62
    {
63
        return in_array(Constants::BINDING_HTTP_POST, $this->getSupportedBindings());
64
    }
65
66
    /**
67
     * @param SettingsInterface $settings
68
     * @param KeyChainRecord|null $withKeyPair
69
     * @return EntityDescriptor
70
     * @throws InvalidConfigException
71
     */
72
    public function create(
73
        SettingsInterface $settings,
74
        KeyChainRecord $withKeyPair = null
75
    ): EntityDescriptor
76
    {
77
78
        $entityDescriptor = new EntityDescriptor();
79
80
        $entityId = $settings->getEntityId();
81
82
        $entityDescriptor->setEntityID($entityId);
83
84
        foreach ($this->getSupportedBindings() as $binding) {
85
            $entityDescriptor->addRoleDescriptor(
86
                $descriptor = $this->createDescriptor($binding, $settings)
87
            );
88
89
            /**
90
             * Add security settings
91
             */
92
            if ($withKeyPair) {
93
                $this->setEncrypt($descriptor, $withKeyPair);
94
                $this->setSign($descriptor, $withKeyPair);
95
            }
96
        }
97
98
        /**
99
         * Kick off event here so people can manipulate this object if needed
100
         */
101
        $event = new Event();
102
103
        /**
104
         * response
105
         */
106
        $event->data = $entityDescriptor;
107
        $this->trigger(static::EVENT_AFTER_MESSAGE_CREATED, $event);
108
109
        return $entityDescriptor;
110
    }
111
112
    /**
113
     * @param string $binding
114
     * @return IdpSsoDescriptor|SpSsoDescriptor
115
     * @throws InvalidConfigException
116
     */
117
    protected function createDescriptor(string $binding, SettingsInterface $settings)
118
    {
119
        if (! in_array($binding, [
120
            Constants::BINDING_HTTP_POST,
121
            Constants::BINDING_HTTP_REDIRECT,
122
        ])) {
123
            throw new InvalidConfigException('Binding not supported: ' . $binding);
124
        }
125
126
        if ($settings->getMyType() === $settings::SP) {
127
            $descriptor = $this->createSpDescriptor($binding, $settings);
128
        } else {
129
            $descriptor = $this->createIdpDescriptor($binding, $settings);
130
        }
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
144
        // SSO
145
        $ssoEndpoint = new IndexedEndpointType();
146
        $ssoEndpoint->setIndex(1);
147
        $ssoEndpoint->setBinding($binding);
148
        $ssoEndpoint->setLocation(
149
            $settings->getDefaultLoginEndpoint()
150
        );
151
        $descriptor->setSingleSignOnService([
152
            $ssoEndpoint,
153
        ]);
154
155
        // SLO
156
        $sloEndpoint = new IndexedEndpointType();
157
        $sloEndpoint->setIndex(1);
158
        $sloEndpoint->setBinding($binding);
159
        $sloEndpoint->setLocation(
160
            $settings->getDefaultLogoutRequestEndpoint()
161
        );
162
        $sloEndpoint->setResponseLocation(
163
            $settings->getDefaultLogoutEndpoint()
164
        );
165
166
        $descriptor->setSingleLogoutService([
167
            $sloEndpoint,
168
        ]);
169
170
171
        return $descriptor;
172
    }
173
174
    /**
175
     * @param string $binding
176
     * @return SPSSODescriptor
177
     */
178
    protected function createSpDescriptor(string $binding, SettingsInterface $settings)
179
    {
180
181
        $descriptor = new SPSSODescriptor();
182
183
        if (property_exists($settings, 'wantsSignedAssertions') &&
184
            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...
185
        ) {
186
            $descriptor->setAuthnRequestsSigned(
187
                $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...
188
            );
189
        }
190
191
        // ACS
192
        $acsEndpoint = new IndexedEndpointType();
193
        $acsEndpoint->setIndex(1);
194
        $acsEndpoint->setBinding($binding);
195
        $acsEndpoint->setLocation(
196
            $settings->getDefaultLoginEndpoint()
197
        );
198
199
        $descriptor->setAssertionConsumerService([
200
            $acsEndpoint,
201
        ]);
202
203
        //SLO
204
        $sloEndpoint = new IndexedEndpointType();
205
        $sloEndpoint->setIndex(1);
206
        $sloEndpoint->setBinding($binding);
207
        $sloEndpoint->setLocation(
208
            $settings->getDefaultLogoutRequestEndpoint()
209
        );
210
        $sloEndpoint->setResponseLocation(
211
            $settings->getDefaultLogoutEndpoint()
212
        );
213
        $descriptor->setSingleLogoutService([
214
            $sloEndpoint,
215
        ]);
216
217
218
        return $descriptor;
219
    }
220
221
    /**
222
     * @param SSODescriptorType $ssoDescriptor
223
     * @param KeyChainRecord $keyChainRecord
224
     */
225
    protected function setCertificate(
226
        SSODescriptorType $ssoDescriptor,
227
        KeyChainRecord $keyChainRecord,
228
        string $signOrEncrypt
229
    )
230
    {
231
        /**
232
         * Validate use string
233
         */
234
        if (! in_array($signOrEncrypt, [
235
            self::SET_SIGNING,
236
            self::SET_ENCRYPTION,
237
        ])) {
238
            throw new \InvalidArgumentException('Sign or Encrypt argument can only be "signing" or "encrypt"');
239
        }
240
241
        /**
242
         * Create sub object
243
         */
244
        $keyDescriptor = new KeyDescriptor();
245
        $keyInfo = new KeyInfo();
246
        $x509Data = new X509Data();
247
        $x509Certificate = new X509Certificate();
248
249
        $keyInfo->addInfo($x509Data);
250
251
        $x509Certificate->setCertificate(
252
            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...
253
        );
254
255
        $x509Data->addData($x509Certificate);
256
        $keyDescriptor->setKeyInfo($keyInfo);
257
258
        $keyDescriptor->setUse($signOrEncrypt);
259
        $ssoDescriptor->addKeyDescriptor($keyDescriptor);
260
261
    }
262
263
    /**
264
     * @param SSODescriptorType
265
     * @param KeyChainRecord $keyChainRecord
266
     */
267
    protected function setSign(SSODescriptorType $ssoDescriptor, KeyChainRecord $keyChainRecord)
268
    {
269
        $this->setCertificate($ssoDescriptor, $keyChainRecord, static::SET_SIGNING);
270
    }
271
272
    /**
273
     * @param SSODescriptorType
274
     * @param KeyChainRecord $keyChainRecord
275
     */
276
    protected function setEncrypt(SSODescriptorType $ssoDescriptor, KeyChainRecord $keyChainRecord)
277
    {
278
        $this->setCertificate($ssoDescriptor, $keyChainRecord, static::SET_ENCRYPTION);
279
    }
280
}
281