Passed
Pull Request — master (#317)
by Tim
12:30
created

AttributeQuery   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 153
rs 10
c 0
b 0
f 0
wmc 8

4 Methods

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