1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace WsdlToPhp\WsSecurity; |
||
6 | |||
7 | use DOMDocument; |
||
8 | use DOMElement; |
||
9 | |||
10 | /** |
||
11 | * Base class to represent any element that must be included for a WS-Security header. |
||
12 | * Each element must be named with the actual targeted element tag name. |
||
13 | * The namespace is also mandatory. |
||
14 | * Finally the attributes are optional. |
||
15 | */ |
||
16 | class Element |
||
17 | { |
||
18 | public const NS_WSSE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'; |
||
19 | |||
20 | public const NS_WSSE_NAME = 'wsse'; |
||
21 | |||
22 | /** |
||
23 | * @deprecated |
||
24 | */ |
||
25 | public const NS_WSSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'; |
||
26 | |||
27 | public const NS_WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'; |
||
28 | |||
29 | /** |
||
30 | * @deprecated |
||
31 | */ |
||
32 | public const NS_WSSU_NAME = 'wssu'; |
||
33 | |||
34 | public const NS_WSU_NAME = 'wsu'; |
||
35 | |||
36 | protected string $name = ''; |
||
37 | |||
38 | /** |
||
39 | * Value of the element. |
||
40 | * It can either be a string value or a Element object. |
||
41 | * |
||
42 | * @var Element|string |
||
43 | */ |
||
44 | protected $value = ''; |
||
45 | |||
46 | /** |
||
47 | * Array of attributes that must contains the element. |
||
48 | * |
||
49 | * @var array<string, mixed> |
||
50 | */ |
||
51 | protected array $attributes = []; |
||
52 | |||
53 | /** |
||
54 | * The namespace the element belongs to. |
||
55 | */ |
||
56 | protected string $namespace = ''; |
||
57 | |||
58 | /** |
||
59 | * Nonce used to generate digest password. |
||
60 | */ |
||
61 | protected string $nonceValue; |
||
62 | |||
63 | /** |
||
64 | * Timestamp used to generate digest password. |
||
65 | */ |
||
66 | protected int $timestampValue; |
||
67 | |||
68 | /** |
||
69 | * Current DOMDocument used to generate XML content. |
||
70 | */ |
||
71 | protected static ?DOMDocument $dom = null; |
||
72 | |||
73 | /** |
||
74 | * @param mixed $value |
||
75 | * @param array<string, mixed> $attributes |
||
76 | */ |
||
77 | 18 | public function __construct(string $name, string $namespace, $value = null, array $attributes = []) |
|
78 | { |
||
79 | $this |
||
80 | 18 | ->setName($name) |
|
81 | 18 | ->setNamespace($namespace) |
|
82 | 18 | ->setValue($value) |
|
83 | 18 | ->setAttributes($attributes) |
|
84 | ; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Method called to generate the string XML request to be sent among the SOAP Header. |
||
89 | * |
||
90 | * @param bool $asDomElement returns elements as a \DOMElement or as a string |
||
91 | * |
||
92 | * @return DOMElement|false|string |
||
93 | */ |
||
94 | 18 | protected function __toSend(bool $asDomElement = false) |
|
95 | { |
||
96 | // Create element tag. |
||
97 | 18 | $element = self::getDom()->createElement($this->getNamespacedName()); |
|
98 | 18 | $element->setAttributeNS('http://www.w3.org/2000/xmlns/', sprintf('xmlns:%s', $this->getNamespacePrefix()), $this->getNamespace()); |
|
99 | |||
100 | // Define element value, add attributes if there are any |
||
101 | $this |
||
102 | 18 | ->appendValueToElementToSend($this->getValue(), $element) |
|
103 | 18 | ->appendAttributesToElementToSend($element) |
|
104 | ; |
||
105 | |||
106 | 18 | return $asDomElement ? $element : self::getDom()->saveXML($element); |
|
107 | } |
||
108 | |||
109 | 18 | public function getName(): string |
|
110 | { |
||
111 | 18 | return $this->name; |
|
112 | } |
||
113 | |||
114 | 18 | public function setName(string $name): self |
|
115 | { |
||
116 | 18 | $this->name = $name; |
|
117 | |||
118 | 18 | return $this; |
|
119 | } |
||
120 | |||
121 | /** |
||
122 | * @return array<string, mixed> |
||
123 | */ |
||
124 | 18 | public function getAttributes(): array |
|
125 | { |
||
126 | 18 | return $this->attributes; |
|
127 | } |
||
128 | |||
129 | /** |
||
130 | * @param array<string, mixed> $attributes |
||
131 | * |
||
132 | * @return Element |
||
133 | */ |
||
134 | 18 | public function setAttributes(array $attributes): self |
|
135 | { |
||
136 | 18 | $this->attributes = $attributes; |
|
137 | |||
138 | 18 | return $this; |
|
139 | } |
||
140 | |||
141 | /** |
||
142 | * @param mixed $value |
||
143 | * |
||
144 | * @return $this |
||
145 | */ |
||
146 | 14 | public function setAttribute(string $name, $value): self |
|
147 | { |
||
148 | 14 | $this->attributes[$name] = $value; |
|
149 | |||
150 | 14 | return $this; |
|
151 | } |
||
152 | |||
153 | 18 | public function hasAttributes(): bool |
|
154 | { |
||
155 | 18 | return 0 < count($this->attributes); |
|
156 | } |
||
157 | |||
158 | 18 | public function getNamespace(): string |
|
159 | { |
||
160 | 18 | return $this->namespace; |
|
161 | } |
||
162 | |||
163 | 18 | public function setNamespace(string $namespace): self |
|
164 | { |
||
165 | 18 | $this->namespace = $namespace; |
|
166 | |||
167 | 18 | return $this; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * @return Element|string |
||
172 | */ |
||
173 | 18 | public function getValue() |
|
174 | { |
||
175 | 18 | return $this->value; |
|
176 | } |
||
177 | |||
178 | /** |
||
179 | * @param mixed $value |
||
180 | * |
||
181 | * @return Element |
||
182 | */ |
||
183 | 18 | public function setValue($value): self |
|
184 | { |
||
185 | 18 | $this->value = $value; |
|
186 | |||
187 | 18 | return $this; |
|
188 | } |
||
189 | |||
190 | 16 | public function getNonceValue(): string |
|
191 | { |
||
192 | 16 | return $this->nonceValue; |
|
193 | } |
||
194 | |||
195 | 18 | public function setNonceValue(string $nonceValue): self |
|
196 | { |
||
197 | 18 | $this->nonceValue = $nonceValue; |
|
198 | |||
199 | 18 | return $this; |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * @return int|string |
||
204 | */ |
||
205 | 18 | public function getTimestampValue(bool $formatted = false) |
|
206 | { |
||
207 | 18 | return ($formatted && $this->timestampValue > 0) ? gmdate('Y-m-d\TH:i:s\Z', $this->timestampValue) : $this->timestampValue; |
|
208 | } |
||
209 | |||
210 | 18 | public function setTimestampValue(int $timestampValue): self |
|
211 | { |
||
212 | 18 | $this->timestampValue = $timestampValue; |
|
213 | |||
214 | 18 | return $this; |
|
215 | } |
||
216 | |||
217 | /** |
||
218 | * Returns the element to send as WS-Security header. |
||
219 | * |
||
220 | * @return DOMElement|false|string |
||
221 | */ |
||
222 | 18 | public function toSend() |
|
223 | { |
||
224 | 18 | self::setDom(new DOMDocument('1.0', 'UTF-8')); |
|
225 | |||
226 | 18 | return $this->__toSend(); |
|
227 | } |
||
228 | |||
229 | /** |
||
230 | * Handle adding value to element according to the value type. |
||
231 | * |
||
232 | * @param mixed $value |
||
233 | * |
||
234 | * @return Element |
||
235 | */ |
||
236 | 18 | protected function appendValueToElementToSend($value, DOMElement $element): self |
|
237 | { |
||
238 | 18 | if ($value instanceof Element) { |
|
239 | 18 | $this->appendElementToElementToSend($value, $element); |
|
240 | 18 | } elseif (is_array($value)) { |
|
241 | 18 | $this->appendValuesToElementToSend($value, $element); |
|
242 | } elseif (!empty($value)) { |
||
243 | 18 | $element->appendChild(self::getDom()->createTextNode($value)); |
|
244 | } |
||
245 | |||
246 | 18 | return $this; |
|
247 | } |
||
248 | |||
249 | 18 | protected function appendElementToElementToSend(Element $value, DOMElement $element): void |
|
250 | { |
||
251 | 18 | $toSend = $value->__toSend(true); |
|
252 | 18 | if ($toSend instanceof DOMElement) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
253 | 18 | $element->appendChild($toSend); |
|
254 | } |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * @param array<mixed> $values |
||
259 | */ |
||
260 | 18 | protected function appendValuesToElementToSend(array $values, DOMElement $element): void |
|
261 | { |
||
262 | 18 | foreach ($values as $value) { |
|
263 | 18 | $this->appendValueToElementToSend($value, $element); |
|
264 | } |
||
265 | } |
||
266 | |||
267 | 18 | protected function appendAttributesToElementToSend(DOMElement $element): self |
|
268 | { |
||
269 | 18 | if (!$this->hasAttributes()) { |
|
270 | 18 | return $this; |
|
271 | } |
||
272 | |||
273 | 18 | foreach ($this->getAttributes() as $attributeName => $attributeValue) { |
|
274 | 18 | $matches = []; |
|
275 | 18 | if (0 === preg_match(sprintf('/(%s|%s):/', self::NS_WSU_NAME, self::NS_WSSE_NAME), $attributeName, $matches)) { |
|
276 | 18 | $element->setAttribute($attributeName, (string) $attributeValue); |
|
277 | } else { |
||
278 | 4 | $element->setAttributeNS(self::NS_WSSE_NAME === $matches[1] ? self::NS_WSSE : self::NS_WSU, $attributeName, $attributeValue); |
|
279 | } |
||
280 | } |
||
281 | |||
282 | 18 | return $this; |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * Returns the name with its namespace. |
||
287 | */ |
||
288 | 18 | protected function getNamespacedName(): string |
|
289 | { |
||
290 | 18 | return sprintf('%s:%s', $this->getNamespacePrefix(), $this->getName()); |
|
291 | } |
||
292 | |||
293 | 18 | private function getNamespacePrefix(): string |
|
294 | { |
||
295 | 18 | $namespacePrefix = ''; |
|
296 | |||
297 | 18 | switch ($this->getNamespace()) { |
|
298 | 9 | case self::NS_WSSE: |
|
299 | 18 | $namespacePrefix = self::NS_WSSE_NAME; |
|
300 | |||
301 | 18 | break; |
|
302 | |||
303 | 9 | case self::NS_WSU: |
|
304 | 18 | $namespacePrefix = self::NS_WSU_NAME; |
|
305 | |||
306 | 18 | break; |
|
307 | } |
||
308 | |||
309 | 18 | return $namespacePrefix; |
|
310 | } |
||
311 | |||
312 | 18 | private static function getDom(): ?DOMDocument |
|
313 | { |
||
314 | 18 | return self::$dom; |
|
315 | } |
||
316 | |||
317 | 18 | private static function setDom(DOMDocument $dom): void |
|
318 | { |
||
319 | 18 | self::$dom = $dom; |
|
320 | } |
||
321 | } |
||
322 |