Passed
Push — master ( 66592a...152b2f )
by Tim
02:08
created

AbstractEndpointType::processArrayContents()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 48
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 28
nc 8
nop 1
dl 0
loc 48
rs 9.1608
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\md;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\SAML2\Constants as C;
10
use SimpleSAML\SAML2\Exception\ArrayValidationException;
11
use SimpleSAML\XML\ArrayizableElementInterface;
12
use SimpleSAML\XML\Attribute as XMLAttribute;
13
use SimpleSAML\XML\Chunk;
14
use SimpleSAML\XML\Exception\InvalidDOMElementException;
15
use SimpleSAML\XML\Exception\SchemaViolationException;
16
use SimpleSAML\XML\ExtendableAttributesTrait;
17
use SimpleSAML\XML\ExtendableElementTrait;
18
use SimpleSAML\XML\SerializableElementInterface;
19
20
use function array_change_key_case;
21
use function array_key_exists;
22
use function array_keys;
23
24
/**
25
 * Class representing SAML 2 EndpointType.
26
 *
27
 * This class can be used in two different ways:
28
 *
29
 *   - You can extend the class without extending the constructor. Then you can use the methods available and the class
30
 *     will generate an element with the same name as the extending class
31
 *     (e.g. \SimpleSAML\SAML2\XML\md\AttributeService).
32
 *
33
 *   - Alternatively, you may want to extend the type to add new attributes (e.g look at IndexedEndpointType). In that
34
 *     case, you cannot use this class normally, as if you change the signature of the constructor, you cannot call
35
 *     fromXML() in this class. In order to process an XML document, you can use the get*Attribute() static methods
36
 *     from AbstractElement, and reimplement the fromXML() method with them to suit your new constructor.
37
 *
38
 * @package simplesamlphp/saml2
39
 */
40
abstract class AbstractEndpointType extends AbstractMdElement implements ArrayizableElementInterface
41
{
42
    use ExtendableAttributesTrait;
1 ignored issue
show
introduced by
The trait SimpleSAML\XML\ExtendableAttributesTrait requires some properties which are not provided by SimpleSAML\SAML2\XML\md\AbstractEndpointType: $localName, $nodeValue, $namespaceURI, $prefix, $attributes
Loading history...
43
    use ExtendableElementTrait;
44
45
    /** The namespace-attribute for the xs:any element */
46
    public const XS_ANY_ELT_NAMESPACE = C::XS_ANY_NS_OTHER;
47
48
    /** The namespace-attribute for the xs:anyAttribute element */
49
    public const XS_ANY_ATTR_NAMESPACE = C::XS_ANY_NS_OTHER;
50
51
52
    /**
53
     * EndpointType constructor.
54
     *
55
     * @param string $binding
56
     * @param string $location
57
     * @param string|null $responseLocation
58
     * @param \SimpleSAML\XML\ElementInterface[] $children
59
     * @param list<\SimpleSAML\XML\Attribute> $attributes
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\XML\md\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
60
     *
61
     * @throws \SimpleSAML\Assert\AssertionFailedException
62
     */
63
    public function __construct(
64
        protected string $binding,
65
        protected string $location,
66
        protected ?string $responseLocation = null,
67
        array $children = [],
68
        array $attributes = [],
69
    ) {
70
        Assert::validURI($binding, SchemaViolationException::class); // Covers the empty string
71
        Assert::validURI($location, SchemaViolationException::class); // Covers the empty string
72
        Assert::nullOrValidURI($responseLocation, SchemaViolationException::class); // Covers the empty string
73
74
        $this->setElements($children);
75
        $this->setAttributesNS($attributes);
76
    }
77
78
79
    /**
80
     * Collect the value of the Binding property.
81
     *
82
     * @return string
83
     */
84
    public function getBinding(): string
85
    {
86
        return $this->binding;
87
    }
88
89
90
    /**
91
     * Collect the value of the Location property.
92
     *
93
     * @return string
94
     */
95
    public function getLocation(): string
96
    {
97
        return $this->location;
98
    }
99
100
101
    /**
102
     * Collect the value of the ResponseLocation property.
103
     *
104
     * @return string|null
105
     */
106
    public function getResponseLocation(): ?string
107
    {
108
        return $this->responseLocation;
109
    }
110
111
112
    /**
113
     * Initialize an EndpointType.
114
     *
115
     * Note: this method cannot be used when extending this class, if the constructor has a different signature.
116
     *
117
     * @param \DOMElement $xml The XML element we should load.
118
     * @return static
119
     *
120
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
121
     *   if the qualified name of the supplied element is wrong
122
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
123
     *   if the supplied element is missing any of the mandatory attributes
124
     */
125
    public static function fromXML(DOMElement $xml): static
126
    {
127
        $qualifiedName = static::getClassName(static::class);
128
        Assert::eq(
129
            $xml->localName,
130
            $qualifiedName,
131
            'Unexpected name for endpoint: ' . $xml->localName . '. Expected: ' . $qualifiedName . '.',
132
            InvalidDOMElementException::class,
133
        );
134
135
        $binding = self::getAttribute($xml, 'Binding');
136
        $location = self::getAttribute($xml, 'Location');
137
138
        $children = [];
139
        foreach ($xml->childNodes as $child) {
140
            if ($child->namespaceURI === C::NS_MD) {
141
                continue;
142
            } elseif (!($child instanceof DOMElement)) {
143
                continue;
144
            }
145
146
            $children[] = new Chunk($child);
147
        }
148
149
        return new static(
150
            $binding,
151
            $location,
152
            self::getOptionalAttribute($xml, 'ResponseLocation', null),
153
            $children,
154
            self::getAttributesNSFromXML($xml),
155
        );
156
    }
157
158
159
    /**
160
     * Add this endpoint to an XML element.
161
     *
162
     * @param \DOMElement $parent The element we should append this endpoint to.
163
     * @return \DOMElement
164
     */
165
    public function toXML(DOMElement $parent = null): DOMElement
166
    {
167
        $e = parent::instantiateParentElement($parent);
168
169
        $e->setAttribute('Binding', $this->getBinding());
170
        $e->setAttribute('Location', $this->getLocation());
171
172
        if ($this->getResponseLocation() !== null) {
173
            $e->setAttribute('ResponseLocation', $this->getResponseLocation());
174
        }
175
176
        foreach ($this->getAttributesNS() as $attr) {
177
            $attr->toXML($e);
178
        }
179
180
        /** @var \SimpleSAML\XML\SerializableElementInterface $child */
181
        foreach ($this->getElements() as $child) {
182
            if (!$child->isEmptyElement()) {
183
                $child->toXML($e);
184
            }
185
        }
186
187
        return $e;
188
    }
189
190
191
    /**
192
     * Create a class from an array
193
     *
194
     * @param array $data
195
     * @return static
196
     */
197
    public static function fromArray(array $data): static
198
    {
199
        $data = self::processArrayContents($data);
200
201
        return new static(
202
            $data['Binding'],
203
            $data['Location'],
204
            $data['ResponseLocation'] ?? null,
205
            $data['children'] ?? [],
206
            $data['attributes'] ?? [],
207
        );
208
    }
209
210
211
    /**
212
     * Validates an array representation of this object and returns the same array with
213
     * rationalized keys (casing) and parsed sub-elements.
214
     *
215
     * @param array $data
216
     * @return array $data
217
     */
218
    private static function processArrayContents(array $data): array
219
    {
220
        $data = array_change_key_case($data, CASE_LOWER);
221
222
        // Make sure the array keys are known for this kind of object
223
        Assert::allOneOf(
224
            array_keys($data),
225
            ['binding', 'location', 'responselocation', 'children', 'attributes'],
226
            ArrayValidationException::class,
227
        );
228
229
        // Make sure all the mandatory items exist
230
        Assert::keyExists($data, 'binding', ArrayValidationException::class);
231
        Assert::keyExists($data, 'location', ArrayValidationException::class);
232
233
        // Make sure the items have the correct data type
234
        Assert::string($data['binding'], ArrayValidationException::class);
235
        Assert::string($data['location'], ArrayValidationException::class);
236
237
        $retval = [
238
            'Binding' => $data['binding'],
239
            'Location' => $data['location'],
240
        ];
241
242
        if (array_key_exists('responselocation', $data)) {
243
            Assert::string($data['responselocation'], ArrayValidationException::class);
244
            $retval['ResponseLocation'] = $data['responselocation'];
245
        }
246
247
        if (array_key_exists('children', $data)) {
248
            Assert::isArray($data['children'], ArrayValidationException::class);
249
            Assert::allIsInstanceOf(
250
                $data['children'],
251
                SerializableElementInterface::class,
252
                ArrayValidationException::class,
253
            );
254
            $retval['children'] = $data['children'];
255
        }
256
257
        if (array_key_exists('attributes', $data)) {
258
            Assert::isArray($data['attributes'], ArrayValidationException::class);
259
            Assert::allIsArray($data['attributes'], ArrayValidationException::class);
260
            foreach ($data['attributes'] as $i => $attr) {
261
                $retval['attributes'][] = XMLAttribute::fromArray($attr);
262
            }
263
        }
264
265
        return $retval;
266
    }
267
268
269
    /**
270
     * Create an array from this class
271
     *
272
     * @return array
273
     */
274
    public function toArray(): array
275
    {
276
        $data = [
277
            'Binding' => $this->getBinding(),
278
            'Location' => $this->getLocation(),
279
            'ResponseLocation' => $this->getResponseLocation(),
280
            'children' => $this->getElements(),
281
        ];
282
283
        foreach ($this->getAttributesNS() as $a) {
284
            $data['attributes'][] = $a->toArray();
285
        }
286
287
        return array_filter($data);
288
    }
289
}
290