AuthenticationSuccess   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 18
eloc 53
dl 0
loc 184
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getProxyGrantingTicket() 0 3 1
A toXML() 0 18 3
B fromXML() 0 70 9
A getAuthenticationSuccessMetadata() 0 3 1
A getAttributes() 0 3 1
A getProxies() 0 3 1
A __construct() 0 6 1
A getUser() 0 3 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\XMLSchema\Exception\InvalidDOMElementException;
10
use SimpleSAML\XMLSchema\Exception\MissingElementException;
11
12
use function array_pop;
13
14
/**
15
 * Class for CAS authenticationSuccess
16
 *
17
 * @package simplesamlphp/cas
18
 */
19
final class AuthenticationSuccess extends AbstractResponse
20
{
21
    /** @var string */
22
    final public const LOCALNAME = 'authenticationSuccess';
23
24
25
    /**
26
     * Non-CAS child elements directly under <cas:authenticationSuccess> to preserve round-trip.
27
     * @var array<\DOMElement>
28
     */
29
    protected array $authenticationSuccessMetadata = [];
30
31
32
    /**
33
     * Initialize a cas:authenticationSuccess element
34
     *
35
     * @param \SimpleSAML\CAS\XML\User $user
36
     * @param \SimpleSAML\CAS\XML\Attributes $attributes
37
     * @param \SimpleSAML\CAS\XML\ProxyGrantingTicket|null $proxyGrantingTicket
38
     * @param \SimpleSAML\CAS\XML\Proxies|null $proxies
39
     */
40
    final public function __construct(
41
        protected User $user,
42
        protected Attributes $attributes,
43
        protected ?ProxyGrantingTicket $proxyGrantingTicket = null,
44
        protected ?Proxies $proxies = null,
45
    ) {
46
    }
47
48
49
    /**
50
     * @return \SimpleSAML\CAS\XML\User
51
     */
52
    public function getUser(): User
53
    {
54
        return $this->user;
55
    }
56
57
58
    /**
59
     * @return \SimpleSAML\CAS\XML\Attributes
60
     */
61
    public function getAttributes(): Attributes
62
    {
63
        return $this->attributes;
64
    }
65
66
67
    /**
68
     * Get Non-CAS child elements directly under <cas:authenticationSuccess> to preserve round-trip.
69
     * @return array<\DOMElement>
70
     */
71
    public function getAuthenticationSuccessMetadata(): array
72
    {
73
        return $this->authenticationSuccessMetadata;
74
    }
75
76
77
    /**
78
     * @return \SimpleSAML\CAS\XML\ProxyGrantingTicket
79
     */
80
    public function getProxyGrantingTicket(): ?ProxyGrantingTicket
81
    {
82
        return $this->proxyGrantingTicket;
83
    }
84
85
86
    /**
87
     * @return \SimpleSAML\CAS\XML\Proxies
88
     */
89
    public function getProxies(): ?Proxies
90
    {
91
        return $this->proxies;
92
    }
93
94
95
    /**
96
     * Convert XML into a cas:authenticationSuccess-element
97
     *
98
     * @param \DOMElement $xml The XML element we should load
99
     * @return static
100
     *
101
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
102
     *   if the qualified name of the supplied element is wrong
103
     * @throws \SimpleSAML\XMLSchema\Exception\MissingAttributeException
104
     *   if the supplied element is missing one of the mandatory attributes
105
     */
106
    public static function fromXML(DOMElement $xml): static
107
    {
108
        Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
109
        Assert::same($xml->namespaceURI, static::getNamespaceURI(), InvalidDOMElementException::class);
110
111
        $user = User::getChildrenOfClass($xml);
112
        Assert::count(
113
            $user,
114
            1,
115
            'Exactly one <cas:user> must be specified.',
116
            MissingElementException::class,
117
        );
118
119
        $attributes = Attributes::getChildrenOfClass($xml);
120
        Assert::count(
121
            $attributes,
122
            1,
123
            'Exactly one <cas:attributes> must be specified.',
124
            MissingElementException::class,
125
        );
126
127
        $proxyGrantingTicket = ProxyGrantingTicket::getChildrenOfClass($xml);
128
        $proxies = Proxies::getChildrenOfClass($xml);
129
130
        $obj = new static(
131
            $user[0],
132
            $attributes[0],
133
            array_pop($proxyGrantingTicket),
134
            array_pop($proxies),
135
        );
136
137
        /*
138
         * Technolutions Slate’s SAMLValidate may emit vendor elements (e.g., slate:person, slate:round) directly under
139
         * cas:authenticationSuccess, not only inside cas:attributes. To interoperate without loosening CAS strictness,
140
         * we preserve and round‑trip only non‑CAS, namespaced children at that level and ignore unknown CAS‑namespace
141
         * elements.
142
         * This keeps vendor metadata intact for consumers (XPath, downstream mapping) while avoiding acceptance of
143
         * schema‑unknown CAS elements.
144
         */
145
        $metadata = [];
146
        foreach ($xml->childNodes as $child) {
147
            if (!$child instanceof DOMElement) {
148
                continue;
149
            }
150
151
            // Skip all known CAS elements in the CAS namespace
152
            if ($child->namespaceURI === static::getNamespaceURI()) {
153
                // Known, schema-defined children
154
                if (
155
                    $child->localName === 'user' ||
156
                    $child->localName === 'attributes' ||
157
                    $child->localName === 'proxyGrantingTicket' ||
158
                    $child->localName === 'proxies'
159
                ) {
160
                    continue;
161
                }
162
                // Unknown elements in the CAS namespace are ignored to preserve strictness
163
                continue;
164
            }
165
166
            // Only keep vendor elements with a non-null namespace (exclude local/no-namespace)
167
            if ($child->namespaceURI === null) {
168
                continue;
169
            }
170
171
            $metadata[] = $child;
172
        }
173
        $obj->authenticationSuccessMetadata = $metadata;
174
175
        return $obj;
176
    }
177
178
179
    /**
180
     * Convert this AuthenticationSuccess to XML.
181
     *
182
     * @param \DOMElement|null $parent The element we should append this AuthenticationSuccess to.
183
     * @return \DOMElement
184
     */
185
    public function toXML(?DOMElement $parent = null): DOMElement
186
    {
187
        $e = $this->instantiateParentElement($parent);
188
189
        $this->getUser()->toXML($e);
190
        $this->getAttributes()->toXML($e);
191
        $this->getProxyGrantingTicket()?->toXML($e);
192
        $this->getProxies()?->toXML($e);
193
194
        // Re-emit preserved non-CAS children (e.g., slate:* at top-level)
195
        foreach ($this->authenticationSuccessMetadata as $child) {
196
            $imported = $e->ownerDocument?->importNode($child, true);
197
            if ($imported !== null) {
198
                $e->appendChild($imported);
199
            }
200
        }
201
202
        return $e;
203
    }
204
}
205