Passed
Pull Request — master (#22)
by
unknown
04:14 queued 02:25
created

Attributes   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 50
dl 0
loc 173
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getIsFromNewLogin() 0 3 1
A getLongTermAuthenticationRequestTokenUsed() 0 3 1
A __construct() 0 7 1
A getAuthenticationDate() 0 3 1
A toXML() 0 16 3
A assertZeroOrOneOrExactlyOneWhenStrict() 0 13 3
A fromXML() 0 34 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\CAS\XML;
6
7
use DOMElement;
8
use SimpleSAML\CAS\Assert\Assert;
9
use SimpleSAML\CAS\Constants as C;
10
use SimpleSAML\XML\ExtendableElementTrait;
11
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
12
use SimpleSAML\XMLSchema\Exception\MissingElementException;
13
use SimpleSAML\XMLSchema\XML\Constants\NS;
14
15
/**
16
 * Class for CAS attributes
17
 *
18
 * @package simplesamlphp/cas
19
 */
20
final class Attributes extends AbstractCasElement
21
{
22
    use ExtendableElementTrait;
23
24
25
    /** @var string */
26
    final public const LOCALNAME = 'attributes';
27
28
    /** The namespace-attribute for the xs:any element */
29
    final public const XS_ANY_ELT_NAMESPACE = NS::ANY;
30
31
    /** The exclusions for the xs:any element */
32
    final public const XS_ANY_ELT_EXCLUSIONS = [
33
        [C::NS_CAS, 'authenticationDate'],
34
        [C::NS_CAS, 'longTermAuthenticationRequestTokenUsed'],
35
        [C::NS_CAS, 'isFromNewLogin'],
36
    ];
37
38
39
    /**
40
     * Initialize a cas:attributes element
41
     *
42
     * @param \SimpleSAML\CAS\XML\AuthenticationDate|null $authenticationDate
43
     * @param \SimpleSAML\CAS\XML\LongTermAuthenticationRequestTokenUsed|null $longTermAuthenticationRequestTokenUsed
44
     * @param \SimpleSAML\CAS\XML\IsFromNewLogin|null $isFromNewLogin
45
     * @param list<\SimpleSAML\XML\SerializableElementInterface> $elts
46
     */
47
    final public function __construct(
48
        protected ?AuthenticationDate $authenticationDate,
49
        protected ?LongTermAuthenticationRequestTokenUsed $longTermAuthenticationRequestTokenUsed,
50
        protected ?IsFromNewLogin $isFromNewLogin,
51
        array $elts = [],
52
    ) {
53
        $this->setElements($elts);
54
    }
55
56
57
    /**
58
     * @return \SimpleSAML\CAS\XML\AuthenticationDate|null
59
     */
60
    public function getAuthenticationDate(): ?AuthenticationDate
61
    {
62
        return $this->authenticationDate;
63
    }
64
65
66
    /**
67
     * @return \SimpleSAML\CAS\XML\LongTermAuthenticationRequestTokenUsed|null
68
     */
69
    public function getLongTermAuthenticationRequestTokenUsed(): ?LongTermAuthenticationRequestTokenUsed
70
    {
71
        return $this->longTermAuthenticationRequestTokenUsed;
72
    }
73
74
75
    /**
76
     * @return \SimpleSAML\CAS\XML\IsFromNewLogin|null
77
     */
78
    public function getIsFromNewLogin(): ?IsFromNewLogin
79
    {
80
        return $this->isFromNewLogin;
81
    }
82
83
84
    /**
85
     * Convert XML into a cas:attributes-element
86
     *
87
     * This parser supports a "lenient" mode for interoperability with CAS servers
88
     * that omit core CAS attributes but still embed additional attributes, such as
89
     * Technolutions Slate. In Slate's CAS responses, <cas:attributes> may only
90
     * contain custom fields (e.g. cas:firstname, cas:lastname, cas:email) and
91
     * omit the CAS 3.0 attributes authenticationDate, longTermAuthenticationRequestTokenUsed
92
     * and isFromNewLogin. To allow consuming those responses, strict parsing is
93
     * disabled by default and can be enabled explicitly via $strictParsing.
94
     *
95
     * @param \DOMElement $xml The XML element we should load
96
     * @param bool        $strictParsing Whether to enforce CAS 3.0 required attributes
97
     * @return static
98
     *
99
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
100
     *   if the qualified name of the supplied element is wrong
101
     * @throws \SimpleSAML\XMLSchema\Exception\MissingAttributeException
102
     *   if the supplied element is missing one of the mandatory attributes
103
     */
104
    public static function fromXML(DOMElement $xml, bool $strictParsing = false): static
105
    {
106
        Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
107
        Assert::same($xml->namespaceURI, static::getNamespaceURI(), InvalidDOMElementException::class);
108
109
        $authenticationDate = AuthenticationDate::getChildrenOfClass($xml);
110
        self::assertZeroOrOneOrExactlyOneWhenStrict(
111
            $authenticationDate,
112
            $strictParsing,
113
            'Exactly one <cas:authenticationDate> must be specified.',
114
            'At most one <cas:authenticationDate> may be specified.',
115
        );
116
117
        $longTermAuthenticationRequestTokenUsed = LongTermAuthenticationRequestTokenUsed::getChildrenOfClass($xml);
118
        self::assertZeroOrOneOrExactlyOneWhenStrict(
119
            $longTermAuthenticationRequestTokenUsed,
120
            $strictParsing,
121
            'Exactly one <cas:longTermAuthenticationRequestTokenUsed> must be specified.',
122
            'At most one <cas:longTermAuthenticationRequestTokenUsed> may be specified.',
123
        );
124
125
        $isFromNewLogin = IsFromNewLogin::getChildrenOfClass($xml);
126
        self::assertZeroOrOneOrExactlyOneWhenStrict(
127
            $isFromNewLogin,
128
            $strictParsing,
129
            'Exactly one <cas:isFromNewLogin> must be specified.',
130
            'At most one <cas:isFromNewLogin> may be specified.',
131
        );
132
133
        return new static(
134
            $authenticationDate[0] ?? null,
135
            $longTermAuthenticationRequestTokenUsed[0] ?? null,
136
            $isFromNewLogin[0] ?? null,
137
            self::getChildElementsFromXML($xml),
138
        );
139
    }
140
141
142
    /**
143
     * Convert this Attributes to XML.
144
     *
145
     * @param \DOMElement|null $parent The element we should append this Attributes to.
146
     * @return \DOMElement
147
     */
148
    public function toXML(?DOMElement $parent = null): DOMElement
149
    {
150
        $e = $this->instantiateParentElement($parent);
151
152
        $this->getAuthenticationDate()?->toXML($e);
153
        $this->getLongTermAuthenticationRequestTokenUsed()?->toXML($e);
154
        $this->getIsFromNewLogin()?->toXML($e);
155
156
        /** @psalm-var \SimpleSAML\XML\SerializableElementInterface $elt */
157
        foreach ($this->elements as $elt) {
158
            if (!$elt->isEmptyElement()) {
159
                $elt->toXML($e);
160
            }
161
        }
162
163
        return $e;
164
    }
165
166
167
    /**
168
     * Assert that an array contains exactly one element when strict parsing is enabled,
169
     * or zero or one element when strict parsing is disabled.
170
     *
171
     * @param array<mixed> $elements The array to check
172
     * @param bool $strict Whether strict parsing is enabled
173
     * @param string $exactlyOneMessage The message to show when exactly one element is required
174
     * @param string $atMostOneMessage The message to show when at most one element is allowed
175
     * @return void
176
     *
177
     * @throws \SimpleSAML\XMLSchema\Exception\MissingElementException
178
     *   if the array contains more than one element, or if strict parsing is enabled and the array is empty
179
     */
180
    private static function assertZeroOrOneOrExactlyOneWhenStrict(
181
        array $elements,
182
        bool $strict,
183
        string $exactlyOneMessage,
184
        string $atMostOneMessage,
185
    ): void {
186
        if ($strict) {
187
            Assert::count($elements, 1, $exactlyOneMessage, MissingElementException::class);
188
            return;
189
        }
190
191
        if (count($elements) > 1) {
192
            throw new MissingElementException($atMostOneMessage);
193
        }
194
    }
195
}
196