simplesamlphp /
saml2
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace SimpleSAML\SAML2\XML\saml; |
||
| 6 | |||
| 7 | use DOMElement; |
||
| 8 | use SimpleSAML\SAML2\Assert\Assert; |
||
| 9 | use SimpleSAML\SAML2\Constants as C; |
||
| 10 | use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException; |
||
| 11 | use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException; |
||
| 12 | use SimpleSAML\SAML2\Type\SAMLDateTimeValue; |
||
| 13 | use SimpleSAML\SAML2\Type\SAMLStringValue; |
||
| 14 | use SimpleSAML\SAML2\Utils\XPath; |
||
| 15 | use SimpleSAML\SAML2\XML\EncryptableElementTrait; |
||
| 16 | use SimpleSAML\SAML2\XML\SignableElementTrait; |
||
| 17 | use SimpleSAML\SAML2\XML\SignedElementTrait; |
||
| 18 | use SimpleSAML\XML\SchemaValidatableElementInterface; |
||
| 19 | use SimpleSAML\XML\SchemaValidatableElementTrait; |
||
| 20 | use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException; |
||
| 21 | use SimpleSAML\XMLSchema\Exception\MissingElementException; |
||
| 22 | use SimpleSAML\XMLSchema\Exception\TooManyElementsException; |
||
| 23 | use SimpleSAML\XMLSchema\Type\IDValue; |
||
| 24 | use SimpleSAML\XMLSecurity\Backend\EncryptionBackend; |
||
| 25 | use SimpleSAML\XMLSecurity\XML\ds\Signature; |
||
| 26 | use SimpleSAML\XMLSecurity\XML\EncryptableElementInterface; |
||
| 27 | use SimpleSAML\XMLSecurity\XML\SignableElementInterface; |
||
| 28 | use SimpleSAML\XMLSecurity\XML\SignedElementInterface; |
||
| 29 | |||
| 30 | use function array_filter; |
||
| 31 | use function array_merge; |
||
| 32 | use function array_pop; |
||
| 33 | use function array_values; |
||
| 34 | use function strval; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * Class representing a SAML 2 assertion. |
||
| 38 | * |
||
| 39 | * @package simplesamlphp/saml2 |
||
| 40 | */ |
||
| 41 | final class Assertion extends AbstractSamlElement implements |
||
| 42 | EncryptableElementInterface, |
||
| 43 | SchemaValidatableElementInterface, |
||
| 44 | SignableElementInterface, |
||
| 45 | SignedElementInterface |
||
| 46 | { |
||
| 47 | use EncryptableElementTrait { |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 48 | EncryptableElementTrait::getBlacklistedAlgorithms insteadof SignedElementTrait; |
||
| 49 | EncryptableElementTrait::getBlacklistedAlgorithms insteadof SignableElementTrait; |
||
| 50 | } |
||
| 51 | |||
| 52 | |||
| 53 | use SchemaValidatableElementTrait; |
||
| 54 | use SignableElementTrait; |
||
|
0 ignored issues
–
show
|
|||
| 55 | use SignedElementTrait; |
||
|
0 ignored issues
–
show
|
|||
| 56 | |||
| 57 | |||
| 58 | /** |
||
| 59 | * @var bool |
||
| 60 | */ |
||
| 61 | protected bool $wasSignedAtConstruction = false; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * The original signed XML |
||
| 65 | * |
||
| 66 | * @var \DOMElement |
||
| 67 | */ |
||
| 68 | protected DOMElement $xml; |
||
| 69 | |||
| 70 | |||
| 71 | /** |
||
| 72 | * Assertion constructor. |
||
| 73 | * |
||
| 74 | * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer |
||
| 75 | * @param \SimpleSAML\XMLSchema\Type\IDValue $id |
||
| 76 | * @param \SimpleSAML\SAML2\Type\SAMLDateTimeValue $issueInstant |
||
| 77 | * @param \SimpleSAML\SAML2\XML\saml\Subject|null $subject |
||
| 78 | * @param \SimpleSAML\SAML2\XML\saml\Conditions|null $conditions |
||
| 79 | * @param \SimpleSAML\SAML2\XML\saml\AbstractStatementType[] $statements |
||
| 80 | */ |
||
| 81 | public function __construct( |
||
| 82 | protected Issuer $issuer, |
||
| 83 | protected SAMLDateTimeValue $issueInstant, |
||
| 84 | protected IDValue $id, |
||
| 85 | protected ?Subject $subject = null, |
||
| 86 | protected ?Conditions $conditions = null, |
||
| 87 | protected array $statements = [], |
||
| 88 | ) { |
||
| 89 | Assert::true( |
||
| 90 | $subject || !empty($statements), |
||
| 91 | "Either a <saml:Subject> or some statement must be present in a <saml:Assertion>", |
||
| 92 | ); |
||
| 93 | Assert::maxCount($statements, C::UNBOUNDED_LIMIT); |
||
| 94 | Assert::allIsInstanceOf($statements, AbstractStatementType::class); |
||
| 95 | } |
||
| 96 | |||
| 97 | |||
| 98 | /** |
||
| 99 | * Collect the value of the subject |
||
| 100 | * |
||
| 101 | * @return \SimpleSAML\SAML2\XML\saml\Subject|null |
||
| 102 | */ |
||
| 103 | public function getSubject(): ?Subject |
||
| 104 | { |
||
| 105 | return $this->subject; |
||
| 106 | } |
||
| 107 | |||
| 108 | |||
| 109 | /** |
||
| 110 | * Collect the value of the conditions-property |
||
| 111 | * |
||
| 112 | * @return \SimpleSAML\SAML2\XML\saml\Conditions|null |
||
| 113 | */ |
||
| 114 | public function getConditions(): ?Conditions |
||
| 115 | { |
||
| 116 | return $this->conditions; |
||
| 117 | } |
||
| 118 | |||
| 119 | |||
| 120 | /** |
||
| 121 | * @return \SimpleSAML\SAML2\XML\saml\AttributeStatement[] |
||
| 122 | */ |
||
| 123 | public function getAttributeStatements(): array |
||
| 124 | { |
||
| 125 | return array_values(array_filter($this->statements, function ($statement) { |
||
| 126 | return $statement instanceof AttributeStatement; |
||
| 127 | })); |
||
| 128 | } |
||
| 129 | |||
| 130 | |||
| 131 | /** |
||
| 132 | * @return \SimpleSAML\SAML2\XML\saml\AuthnStatement[] |
||
| 133 | */ |
||
| 134 | public function getAuthnStatements(): array |
||
| 135 | { |
||
| 136 | return array_values(array_filter($this->statements, function ($statement) { |
||
| 137 | return $statement instanceof AuthnStatement; |
||
| 138 | })); |
||
| 139 | } |
||
| 140 | |||
| 141 | |||
| 142 | /** |
||
| 143 | * @return \SimpleSAML\SAML2\XML\saml\AbstractStatement[] |
||
| 144 | */ |
||
| 145 | public function getStatements(): array |
||
| 146 | { |
||
| 147 | return array_values(array_filter($this->statements, function ($statement) { |
||
| 148 | return $statement instanceof AbstractStatement; |
||
| 149 | })); |
||
| 150 | } |
||
| 151 | |||
| 152 | |||
| 153 | /** |
||
| 154 | * Retrieve the identifier of this assertion. |
||
| 155 | * |
||
| 156 | * @return \SimpleSAML\XMLSchema\Type\IDValue The identifier of this assertion. |
||
| 157 | */ |
||
| 158 | public function getId(): IDValue |
||
| 159 | { |
||
| 160 | return $this->id; |
||
| 161 | } |
||
| 162 | |||
| 163 | |||
| 164 | /** |
||
| 165 | * Retrieve the issue timestamp of this assertion. |
||
| 166 | * |
||
| 167 | * @return \SimpleSAML\SAML2\Type\SAMLDateTimeValue The issue timestamp of this assertion, as an UNIX timestamp. |
||
| 168 | */ |
||
| 169 | public function getIssueInstant(): SAMLDateTimeValue |
||
| 170 | { |
||
| 171 | return $this->issueInstant; |
||
| 172 | } |
||
| 173 | |||
| 174 | |||
| 175 | /** |
||
| 176 | * Retrieve the issuer if this assertion. |
||
| 177 | * |
||
| 178 | * @return \SimpleSAML\SAML2\XML\saml\Issuer The issuer of this assertion. |
||
| 179 | */ |
||
| 180 | public function getIssuer(): Issuer |
||
| 181 | { |
||
| 182 | return $this->issuer; |
||
| 183 | } |
||
| 184 | |||
| 185 | |||
| 186 | /** |
||
| 187 | * @return bool |
||
| 188 | */ |
||
| 189 | public function wasSignedAtConstruction(): bool |
||
| 190 | { |
||
| 191 | return $this->wasSignedAtConstruction; |
||
| 192 | } |
||
| 193 | |||
| 194 | |||
| 195 | /** |
||
| 196 | * Get the XML element. |
||
| 197 | * |
||
| 198 | * @return \DOMElement |
||
| 199 | */ |
||
| 200 | public function getXML(): DOMElement |
||
| 201 | { |
||
| 202 | return $this->xml; |
||
| 203 | } |
||
| 204 | |||
| 205 | |||
| 206 | /** |
||
| 207 | * Set the XML element. |
||
| 208 | * |
||
| 209 | * @param \DOMElement $xml |
||
| 210 | */ |
||
| 211 | private function setXML(DOMElement $xml): void |
||
| 212 | { |
||
| 213 | $this->xml = $xml; |
||
| 214 | } |
||
| 215 | |||
| 216 | |||
| 217 | /** |
||
| 218 | * @return \DOMElement |
||
| 219 | */ |
||
| 220 | protected function getOriginalXML(): DOMElement |
||
| 221 | { |
||
| 222 | return $this->xml ?? $this->toUnsignedXML(); |
||
| 223 | } |
||
| 224 | |||
| 225 | |||
| 226 | public function getEncryptionBackend(): ?EncryptionBackend |
||
| 227 | { |
||
| 228 | // return the encryption backend you want to use, |
||
| 229 | // or null if you are fine with the default |
||
| 230 | return null; |
||
| 231 | } |
||
| 232 | |||
| 233 | |||
| 234 | /** |
||
| 235 | * Convert XML into an Assertion |
||
| 236 | * |
||
| 237 | * @param \DOMElement $xml The XML element we should load |
||
| 238 | * @return static |
||
| 239 | * |
||
| 240 | * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false |
||
| 241 | * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException |
||
| 242 | * if the qualified name of the supplied element is wrong |
||
| 243 | * @throws \SimpleSAML\XMLSchema\Exception\MissingAttributeException |
||
| 244 | * if the supplied element is missing one of the mandatory attributes |
||
| 245 | * @throws \SimpleSAML\XMLSchema\Exception\MissingElementException |
||
| 246 | * if one of the mandatory child-elements is missing |
||
| 247 | * @throws \SimpleSAML\XMLSchema\Exception\TooManyElementsException |
||
| 248 | * if too many child-elements of a type are specified |
||
| 249 | * @throws \Exception |
||
| 250 | */ |
||
| 251 | public static function fromXML(DOMElement $xml): static |
||
| 252 | { |
||
| 253 | Assert::same($xml->localName, 'Assertion', InvalidDOMElementException::class); |
||
| 254 | Assert::same($xml->namespaceURI, Assertion::NS, InvalidDOMElementException::class); |
||
| 255 | |||
| 256 | $version = self::getAttribute($xml, 'Version', SAMLStringValue::class); |
||
| 257 | Assert::true(version_compare('2.0', strval($version), '<='), RequestVersionTooLowException::class); |
||
| 258 | Assert::true(version_compare('2.0', strval($version), '>='), RequestVersionTooHighException::class); |
||
| 259 | |||
| 260 | $issuer = Issuer::getChildrenOfClass($xml); |
||
| 261 | Assert::minCount($issuer, 1, 'Missing <saml:Issuer> in assertion.', MissingElementException::class); |
||
| 262 | Assert::maxCount($issuer, 1, 'More than one <saml:Issuer> in assertion.', TooManyElementsException::class); |
||
| 263 | |||
| 264 | $subject = Subject::getChildrenOfClass($xml); |
||
| 265 | Assert::maxCount( |
||
| 266 | $subject, |
||
| 267 | 1, |
||
| 268 | 'More than one <saml:Subject> in <saml:Assertion>', |
||
| 269 | TooManyElementsException::class, |
||
| 270 | ); |
||
| 271 | |||
| 272 | $conditions = Conditions::getChildrenOfClass($xml); |
||
| 273 | Assert::maxCount( |
||
| 274 | $conditions, |
||
| 275 | 1, |
||
| 276 | 'More than one <saml:Conditions> in <saml:Assertion>.', |
||
| 277 | TooManyElementsException::class, |
||
| 278 | ); |
||
| 279 | |||
| 280 | $signature = Signature::getChildrenOfClass($xml); |
||
| 281 | Assert::maxCount($signature, 1, 'Only one <ds:Signature> element is allowed.', TooManyElementsException::class); |
||
| 282 | |||
| 283 | $authnStatement = AuthnStatement::getChildrenOfClass($xml); |
||
| 284 | $attrStatement = AttributeStatement::getChildrenOfClass($xml); |
||
| 285 | $statements = AbstractStatement::getChildrenOfClass($xml); |
||
| 286 | |||
| 287 | $assertion = new static( |
||
| 288 | array_pop($issuer), |
||
| 289 | self::getAttribute($xml, 'IssueInstant', SAMLDateTimeValue::class), |
||
| 290 | self::getAttribute($xml, 'ID', IDValue::class), |
||
| 291 | array_pop($subject), |
||
| 292 | array_pop($conditions), |
||
| 293 | array_merge($authnStatement, $attrStatement, $statements), |
||
| 294 | ); |
||
| 295 | |||
| 296 | if (!empty($signature)) { |
||
| 297 | $assertion->setSignature($signature[0]); |
||
| 298 | $assertion->wasSignedAtConstruction = true; |
||
| 299 | $assertion->setXML($xml); |
||
| 300 | } |
||
| 301 | |||
| 302 | return $assertion; |
||
| 303 | } |
||
| 304 | |||
| 305 | |||
| 306 | /** |
||
| 307 | * Convert this assertion to an unsigned XML document. |
||
| 308 | * This method does not sign the resulting XML document. |
||
| 309 | * |
||
| 310 | * @return \DOMElement The root element of the DOM tree |
||
| 311 | */ |
||
| 312 | protected function toUnsignedXML(?DOMElement $parent = null): DOMElement |
||
| 313 | { |
||
| 314 | $e = $this->instantiateParentElement($parent); |
||
| 315 | |||
| 316 | $e->setAttribute('Version', '2.0'); |
||
| 317 | $e->setAttribute('ID', strval($this->getId())); |
||
| 318 | $e->setAttribute('IssueInstant', strval($this->getIssueInstant())); |
||
| 319 | |||
| 320 | $this->getIssuer()->toXML($e); |
||
| 321 | $this->getSubject()?->toXML($e); |
||
| 322 | $this->getConditions()?->toXML($e); |
||
| 323 | |||
| 324 | foreach ($this->statements as $statement) { |
||
| 325 | $statement->toXML($e); |
||
| 326 | } |
||
| 327 | |||
| 328 | return $e; |
||
| 329 | } |
||
| 330 | |||
| 331 | |||
| 332 | /** |
||
| 333 | * Convert this assertion to a signed XML element, if a signer was set. |
||
| 334 | * |
||
| 335 | * @param \DOMElement|null $parent The DOM node the assertion should be created in. |
||
| 336 | * |
||
| 337 | * @return \DOMElement This assertion. |
||
| 338 | * @throws \Exception |
||
| 339 | */ |
||
| 340 | public function toXML(?DOMElement $parent = null): DOMElement |
||
| 341 | { |
||
| 342 | if ($this->isSigned() === true && $this->signer === null) { |
||
| 343 | // We already have a signed document and no signer was set to re-sign it |
||
| 344 | if ($parent === null) { |
||
| 345 | return $this->getXML(); |
||
| 346 | } |
||
| 347 | |||
| 348 | $node = $parent->ownerDocument?->importNode($this->getXML(), true); |
||
| 349 | $parent->appendChild($node); |
||
| 350 | return $parent; |
||
| 351 | } |
||
| 352 | |||
| 353 | $e = $this->toUnsignedXML($parent); |
||
| 354 | |||
| 355 | if ($this->signer !== null) { |
||
| 356 | $signedXML = $this->doSign($e); |
||
| 357 | |||
| 358 | // Test for an Issuer |
||
| 359 | $messageElements = XPath::xpQuery($signedXML, './saml_assertion:Issuer', XPath::getXPath($signedXML)); |
||
| 360 | $issuer = array_pop($messageElements); |
||
| 361 | |||
| 362 | $signedXML->insertBefore($this->signature?->toXML($signedXML), $issuer->nextSibling); |
||
| 363 | return $signedXML; |
||
| 364 | } |
||
| 365 | |||
| 366 | return $e; |
||
| 367 | } |
||
| 368 | } |
||
| 369 |