Passed
Pull Request — master (#280)
by Tim
02:34
created

AttributeQuery::toUnsignedXML()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\samlp;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
10
use SimpleSAML\SAML2\XML\saml\Attribute;
11
use SimpleSAML\SAML2\XML\saml\Issuer;
12
use SimpleSAML\SAML2\XML\saml\Subject;
13
use SimpleSAML\XML\Exception\InvalidDOMElementException;
14
use SimpleSAML\XML\Exception\MissingElementException;
15
use SimpleSAML\XML\Exception\TooManyElementsException;
16
use SimpleSAML\XML\Utils as XMLUtils;
17
use SimpleSAML\XMLSecurity\XML\ds\Signature;
18
19
use function array_pop;
20
use function in_array;
21
22
/**
23
 * Class for SAML 2 attribute query messages.
24
 *
25
 * An attribute query asks for a set of attributes. The following
26
 * rules apply:
27
 *
28
 * - If no attributes are present in the query, all attributes should be
29
 *   returned.
30
 * - If any attributes are present, only those attributes which are present
31
 *   in the query should be returned.
32
 * - If an attribute contains any attribute values, only the attribute values
33
 *   which match those in the query should be returned.
34
 *
35
 * @package simplesamlphp/saml2
36
 */
37
class AttributeQuery extends AbstractSubjectQuery
38
{
39
    /**
40
     * The attributes, as an associative array.
41
     *
42
     * @var \SimpleSAML\SAML2\XML\saml\Attribute[]
43
     */
44
    protected array $attributes = [];
45
46
47
    /**
48
     * Constructor for SAML 2 AttributeQuery.
49
     *
50
     * @param \SimpleSAML\SAML2\XML\saml\Subject $subject
51
     * @param \SimpleSAML\SAML2\XML\saml\Attribute[] $attributes
52
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer
53
     * @param string $id
54
     * @param int $issueInstant
55
     * @param string|null $destination
56
     * @param string|null $consent
57
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
58
     */
59
    public function __construct(
60
        Subject $subject,
61
        array $attributes = [],
62
        ?Issuer $issuer = null,
63
        ?string $id = null,
64
        ?int $issueInstant = null,
65
        ?string $destination = null,
66
        ?string $consent = null,
67
        ?Extensions $extensions = null
68
    ) {
69
        parent::__construct($subject, $issuer, $id, $issueInstant, $destination, $consent, $extensions);
70
71
        $this->setAttributes($attributes);
72
    }
73
74
75
    /**
76
     * Retrieve all requested attributes.
77
     *
78
     * @return \SimpleSAML\SAML2\XML\saml\Attribute[] All requested attributes, as an associative array.
79
     */
80
    public function getAttributes(): array
81
    {
82
        return $this->attributes;
83
    }
84
85
86
    /**
87
     * Set all requested attributes.
88
     *
89
     * @param \SimpleSAML\SAML2\XML\saml\Attribute[] $attributes All requested attributes, as an associative array.
90
     */
91
    public function setAttributes(array $attributes): void
92
    {
93
        Assert::allIsInstanceOf($attributes, Attribute::class);
94
95
        $cache = [];
96
        foreach ($attributes as $attribute) {
97
            $name = $attribute->getName();
98
            $nameFormat = $attribute->getNameFormat();
99
100
            if (isset($cache[$nameFormat])) {
101
                Assert::true(
102
                    !in_array($name, $cache[$nameFormat], true),
103
                    'A single query MUST NOT contain two <saml:Attribute> elements with the same Name and NameFormat.',
104
                    ProtocolViolationException::class
105
                );
106
            }
107
            $cache[$nameFormat][] = $name;
108
        }
109
        unset($cache);
110
111
        $this->attributes = $attributes;
112
    }
113
114
115
    /**
116
     * Create a class from XML
117
     *
118
     * @param \DOMElement $xml
119
     * @return self
120
     *
121
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
122
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
123
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
124
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
125
     */
126
    public static function fromXML(DOMElement $xml): object
127
    {
128
        Assert::same($xml->localName, 'AttributeQuery', InvalidDOMElementException::class);
129
        Assert::same($xml->namespaceURI, AttributeQuery::NS, InvalidDOMElementException::class);
130
        Assert::same('2.0', self::getAttribute($xml, 'Version'));
131
132
        $id = self::getAttribute($xml, 'ID');
133
        $destination = self::getAttribute($xml, 'Destination', null);
134
        $consent = self::getAttribute($xml, 'Consent', null);
135
136
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
137
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
138
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
139
140
        $issuer = Issuer::getChildrenOfClass($xml);
141
        Assert::countBetween($issuer, 0, 1);
142
143
        $extensions = Extensions::getChildrenOfClass($xml);
144
        Assert::maxCount($extensions, 1, 'Only one saml:Extensions element is allowed.', TooManyElementsException::class);
145
146
        $subject = Subject::getChildrenOfClass($xml);
147
        Assert::notEmpty($subject, 'Missing subject in subject query.', MissingElementException::class);
148
        Assert::maxCount($subject, 1, 'More than one <saml:Subject> in AttributeQuery', TooManyElementsException::class);
149
150
        $signature = Signature::getChildrenOfClass($xml);
151
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.', TooManyElementsException::class);
152
153
        $request = new self(
154
            array_pop($subject),
155
            Attribute::getChildrenOfClass($xml),
156
            array_pop($issuer),
157
            $id,
158
            $issueInstant,
159
            $destination,
160
            $consent,
161
            array_pop($extensions)
162
        );
163
164
        if (!empty($signature)) {
165
            $request->setSignature($signature[0]);
166
        }
167
168
        $request->setXML($xml);
169
        return $request;
170
    }
171
172
173
    /**
174
     * Convert this message to an unsigned XML document.
175
     * This method does not sign the resulting XML document.
176
     *
177
     * @return \DOMElement The root element of the DOM tree
178
     */
179
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
180
    {
181
        $e = parent::toUnsignedXML($parent);
182
183
        foreach ($this->attributes as $attribute) {
184
            $attribute->toXML($e);
185
        }
186
187
        return $e;
188
    }
189
}
190