Passed
Pull Request — master (#337)
by Tim
02:05
created

AbstractEndpointType::validateArray()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 24
nc 8
nop 1
dl 0
loc 43
rs 9.2248
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\XML\ArrayizableElementInterface;
11
use SimpleSAML\XML\Attribute as XMLAttribute;
12
use SimpleSAML\XML\Chunk;
13
use SimpleSAML\XML\Exception\InvalidDOMElementException;
14
use SimpleSAML\XML\Exception\SchemaViolationException;
15
use SimpleSAML\XML\ExtendableAttributesTrait;
16
use SimpleSAML\XML\ExtendableElementTrait;
17
use SimpleSAML\XML\SerializableElementInterface;
18
19
use function array_change_key_case;
20
use function array_key_exists;
21
use function array_keys;
22
23
/**
24
 * Class representing SAML 2 EndpointType.
25
 *
26
 * This class can be used in two different ways:
27
 *
28
 *   - You can extend the class without extending the constructor. Then you can use the methods available and the class
29
 *     will generate an element with the same name as the extending class
30
 *     (e.g. \SimpleSAML\SAML2\XML\md\AttributeService).
31
 *
32
 *   - Alternatively, you may want to extend the type to add new attributes (e.g look at IndexedEndpointType). In that
33
 *     case, you cannot use this class normally, as if you change the signature of the constructor, you cannot call
34
 *     fromXML() in this class. In order to process an XML document, you can use the get*Attribute() static methods
35
 *     from AbstractElement, and reimplement the fromXML() method with them to suit your new constructor.
36
 *
37
 * @package simplesamlphp/saml2
38
 */
39
abstract class AbstractEndpointType extends AbstractMdElement implements ArrayizableElementInterface
40
{
41
    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...
42
    use ExtendableElementTrait;
43
44
    /** The namespace-attribute for the xs:any element */
45
    public const XS_ANY_ELT_NAMESPACE = C::XS_ANY_NS_OTHER;
46
47
    /** The namespace-attribute for the xs:anyAttribute element */
48
    public const XS_ANY_ATTR_NAMESPACE = C::XS_ANY_NS_OTHER;
49
50
51
    /**
52
     * EndpointType constructor.
53
     *
54
     * @param string $binding
55
     * @param string $location
56
     * @param string|null $responseLocation
57
     * @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...
58
     * @param \SimpleSAML\XML\ElementInterface[] $children
59
     *
60
     * @throws \SimpleSAML\Assert\AssertionFailedException
61
     */
62
    public function __construct(
63
        protected string $binding,
64
        protected string $location,
65
        protected ?string $responseLocation = null,
66
        array $attributes = [],
67
        array $children = [],
68
    ) {
69
        Assert::validURI($binding, SchemaViolationException::class); // Covers the empty string
70
        Assert::validURI($location, SchemaViolationException::class); // Covers the empty string
71
        Assert::nullOrValidURI($responseLocation, SchemaViolationException::class); // Covers the empty string
72
73
        $this->setAttributesNS($attributes);
74
        $this->setElements($children);
75
    }
76
77
78
    /**
79
     * Collect the value of the Binding property.
80
     *
81
     * @return string
82
     */
83
    public function getBinding(): string
84
    {
85
        return $this->binding;
86
    }
87
88
89
    /**
90
     * Collect the value of the Location property.
91
     *
92
     * @return string
93
     */
94
    public function getLocation(): string
95
    {
96
        return $this->location;
97
    }
98
99
100
    /**
101
     * Collect the value of the ResponseLocation property.
102
     *
103
     * @return string|null
104
     */
105
    public function getResponseLocation(): ?string
106
    {
107
        return $this->responseLocation;
108
    }
109
110
111
    /**
112
     * Initialize an EndpointType.
113
     *
114
     * Note: this method cannot be used when extending this class, if the constructor has a different signature.
115
     *
116
     * @param \DOMElement $xml The XML element we should load.
117
     * @return static
118
     *
119
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
120
     *   if the qualified name of the supplied element is wrong
121
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
122
     *   if the supplied element is missing any of the mandatory attributes
123
     */
124
    public static function fromXML(DOMElement $xml): static
125
    {
126
        $qualifiedName = static::getClassName(static::class);
127
        Assert::eq(
128
            $xml->localName,
129
            $qualifiedName,
130
            'Unexpected name for endpoint: ' . $xml->localName . '. Expected: ' . $qualifiedName . '.',
131
            InvalidDOMElementException::class,
132
        );
133
134
        $binding = self::getAttribute($xml, 'Binding');
135
        $location = self::getAttribute($xml, 'Location');
136
137
        $children = [];
138
        foreach ($xml->childNodes as $child) {
139
            if ($child->namespaceURI === C::NS_MD) {
140
                continue;
141
            } elseif (!($child instanceof DOMElement)) {
142
                continue;
143
            }
144
145
            $children[] = new Chunk($child);
146
        }
147
148
        return new static(
149
            $binding,
150
            $location,
151
            self::getOptionalAttribute($xml, 'ResponseLocation', null),
152
            self::getAttributesNSFromXML($xml),
153
            $children,
154
        );
155
    }
156
157
158
    /**
159
     * Add this endpoint to an XML element.
160
     *
161
     * @param \DOMElement $parent The element we should append this endpoint to.
162
     * @return \DOMElement
163
     */
164
    public function toXML(DOMElement $parent = null): DOMElement
165
    {
166
        $e = parent::instantiateParentElement($parent);
167
168
        $e->setAttribute('Binding', $this->getBinding());
169
        $e->setAttribute('Location', $this->getLocation());
170
171
        if ($this->getResponseLocation() !== null) {
172
            $e->setAttribute('ResponseLocation', $this->getResponseLocation());
173
        }
174
175
        foreach ($this->getAttributesNS() as $attr) {
176
            $attr->toXML($e);
177
        }
178
179
        /** @var \SimpleSAML\XML\SerializableElementInterface $child */
180
        foreach ($this->getElements() as $child) {
181
            if (!$child->isEmptyElement()) {
182
                $child->toXML($e);
183
            }
184
        }
185
186
        return $e;
187
    }
188
189
190
    /**
191
     * Create a class from an array
192
     *
193
     * @param array $data
194
     * @return static
195
     */
196
    public static function fromArray(array $data): static
197
    {
198
        $data = self::validateArray($data);
199
200
        $responseLocation = array_key_exists('ResponseLocation', $data) ? $data['ResponseLocation'] : null;
201
        $Extensions = array_key_exists('Extensions', $data) ? $data['Extensions'] : null;
202
203
        return new static($data['Binding'], $data['Location'], $responseLocation, $data['attributes'], $Extensions);
0 ignored issues
show
Bug introduced by
It seems like $Extensions can also be of type null; however, parameter $children of SimpleSAML\SAML2\XML\md\...ointType::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
        return new static($data['Binding'], $data['Location'], $responseLocation, $data['attributes'], /** @scrutinizer ignore-type */ $Extensions);
Loading history...
204
    }
205
206
207
    /**
208
     * Validates an array representation of this object and returns the same array with
209
     * rationalized keys (casing) and parsed sub-elements.
210
     *
211
     * @param array $data
212
     * @return array $data
213
     */
214
    private static function validateArray(array $data): array
215
    {
216
        $data = array_change_key_case($data, CASE_LOWER);
217
218
        // Make sure the array keys are known for this kind of object
219
        Assert::allOneOf(
220
            array_keys($data),
221
            ['binding', 'location', 'responselocation', 'extensions', 'attributes'],
222
        );
223
224
        // Make sure all the mandatory items exist
225
        Assert::keyExists($data, 'binding');
226
        Assert::keyExists($data, 'location');
227
228
        // Make sure the items have the correct data type
229
        Assert::string($data['binding']);
230
        Assert::string($data['location']);
231
232
        $retval = [
233
            'Binding' => $data['binding'],
234
            'Location' => $data['location'],
235
        ];
236
237
        if (array_key_exists('responselocation', $data)) {
238
            Assert::string($data['responselocation']);
239
            $retval['ResponseLocation'] = $data['responselocation'];
240
        }
241
242
        if (array_key_exists('extensions', $data)) {
243
            Assert::isArray($data['extensions']);
244
            Assert::allIsInstanceOf($data['extensions'], SerializableElementInterface::class);
245
            $retval['Extensions'] = $data['extensions'];
246
        }
247
248
        if (array_key_exists('attributes', $data)) {
249
            Assert::isArray($data['attributes']);
250
            Assert::allIsArray($data['attributes']);
251
            foreach ($data['attributes'] as $i => $attr) {
252
                $retval['attributes'][] = XMLAttribute::fromArray($attr);
253
            }
254
        }
255
256
        return $retval;
257
    }
258
259
260
    /**
261
     * Create an array from this class
262
     *
263
     * @return array
264
     */
265
    public function toArray(): array
266
    {
267
        $data = [
268
            'Binding' => $this->getBinding(),
269
            'Location' => $this->getLocation(),
270
            'ResponseLocation' => $this->getResponseLocation(),
271
            'Extensions' => $this->getElements(),
272
        ];
273
274
        foreach ($this->getAttributesNS() as $a) {
275
            $data['attributes'][] = $a->toArray();
276
        }
277
278
        return array_filter($data);
279
    }
280
}
281