Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like SAML2_Utils often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SAML2_Utils, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 8 | use SAML2\Compat\ContainerSingleton; |
||
| 9 | use SAML2\Exception\RuntimeException; |
||
| 10 | use SAML2\XML\ds\KeyInfo; |
||
| 11 | use SAML2\XML\ds\X509Certificate; |
||
| 12 | use SAML2\XML\ds\X509Data; |
||
| 13 | use SAML2\XML\md\KeyDescriptor; |
||
| 14 | |||
| 15 | /** |
||
| 16 | * Helper functions for the SAML2 library. |
||
| 17 | * |
||
| 18 | * @package SimpleSAMLphp |
||
| 19 | */ |
||
| 20 | class Utils |
||
| 21 | { |
||
| 22 | /** |
||
| 23 | * Check the Signature in a XML element. |
||
| 24 | * |
||
| 25 | * This function expects the XML element to contain a Signature-element |
||
| 26 | * which contains a reference to the XML-element. This is common for both |
||
| 27 | * messages and assertions. |
||
| 28 | * |
||
| 29 | * Note that this function only validates the element itself. It does not |
||
| 30 | * check this against any local keys. |
||
| 31 | * |
||
| 32 | * If no Signature-element is located, this function will return false. All |
||
| 33 | * other validation errors result in an exception. On successful validation |
||
| 34 | * an array will be returned. This array contains the information required to |
||
| 35 | * check the signature against a public key. |
||
| 36 | * |
||
| 37 | * @param \DOMElement $root The element which should be validated. |
||
| 38 | * @return array|bool An array with information about the Signature-element. |
||
| 39 | * @throws \Exception |
||
| 40 | */ |
||
| 41 | public static function validateElement(\DOMElement $root) |
||
| 101 | |||
| 102 | |||
| 103 | /** |
||
| 104 | * Helper function to convert a XMLSecurityKey to the correct algorithm. |
||
| 105 | * |
||
| 106 | * @param XMLSecurityKey $key The key. |
||
| 107 | * @param string $algorithm The desired algorithm. |
||
| 108 | * @param string $type Public or private key, defaults to public. |
||
| 109 | * @return XMLSecurityKey The new key. |
||
| 110 | * @throws \Exception |
||
| 111 | */ |
||
| 112 | public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public') |
||
| 135 | |||
| 136 | |||
| 137 | /** |
||
| 138 | * Check a signature against a key. |
||
| 139 | * |
||
| 140 | * An exception is thrown if we are unable to validate the signature. |
||
| 141 | * |
||
| 142 | * @param array $info The information returned by the validateElement()-function. |
||
| 143 | * @param XMLSecurityKey $key The publickey that should validate the Signature object. |
||
| 144 | * @throws \Exception |
||
| 145 | */ |
||
| 146 | public static function validateSignature(array $info, XMLSecurityKey $key) |
||
| 172 | |||
| 173 | |||
| 174 | /** |
||
| 175 | * Do an XPath query on an XML node. |
||
| 176 | * |
||
| 177 | * @param \DOMNode $node The XML node. |
||
| 178 | * @param string $query The query. |
||
| 179 | * @return \DOMElement[] Array with matching DOM nodes. |
||
| 180 | */ |
||
| 181 | public static function xpQuery(\DOMNode $node, $query) |
||
| 210 | |||
| 211 | |||
| 212 | /** |
||
| 213 | * Make an exact copy the specific \DOMElement. |
||
| 214 | * |
||
| 215 | * @param \DOMElement $element The element we should copy. |
||
| 216 | * @param \DOMElement|null $parent The target parent element. |
||
| 217 | * @return \DOMElement The copied element. |
||
| 218 | */ |
||
| 219 | public static function copyElement(\DOMElement $element, \DOMElement $parent = null) |
||
| 255 | |||
| 256 | |||
| 257 | /** |
||
| 258 | * Parse a boolean attribute. |
||
| 259 | * |
||
| 260 | * @param \DOMElement $node The element we should fetch the attribute from. |
||
| 261 | * @param string $attributeName The name of the attribute. |
||
| 262 | * @param mixed $default The value that should be returned if the attribute doesn't exist. |
||
| 263 | * @return bool|mixed The value of the attribute, or $default if the attribute doesn't exist. |
||
| 264 | * @throws \Exception |
||
| 265 | */ |
||
| 266 | public static function parseBoolean(\DOMElement $node, $attributeName, $default = null) |
||
| 285 | |||
| 286 | |||
| 287 | /** |
||
| 288 | * Create a NameID element. |
||
| 289 | * |
||
| 290 | * The NameId array can have the following elements: 'Value', 'Format', |
||
| 291 | * 'NameQualifier, 'SPNameQualifier' |
||
| 292 | * |
||
| 293 | * Only the 'Value'-element is required. |
||
| 294 | * |
||
| 295 | * @param \DOMElement $node The DOM node we should append the NameId to. |
||
| 296 | * @param array $nameId The name identifier. |
||
| 297 | */ |
||
| 298 | public static function addNameId(\DOMElement $node, array $nameId) |
||
| 314 | |||
| 315 | /** |
||
| 316 | * Parse a NameID element. |
||
| 317 | * |
||
| 318 | * @param \DOMElement $xml The DOM element we should parse. |
||
| 319 | * @return array The parsed name identifier. |
||
| 320 | */ |
||
| 321 | public static function parseNameId(\DOMElement $xml) |
||
| 333 | |||
| 334 | /** |
||
| 335 | * Insert a Signature-node. |
||
| 336 | * |
||
| 337 | * @param XMLSecurityKey $key The key we should use to sign the message. |
||
| 338 | * @param array $certificates The certificates we should add to the signature node. |
||
| 339 | * @param \DOMElement $root The XML node we should sign. |
||
| 340 | * @param \DOMNode $insertBefore The XML element we should insert the signature element before. |
||
| 341 | */ |
||
| 342 | public static function insertSignature( |
||
| 380 | |||
| 381 | /** |
||
| 382 | * Decrypt an encrypted element. |
||
| 383 | * |
||
| 384 | * This is an internal helper function. |
||
| 385 | * |
||
| 386 | * @param \DOMElement $encryptedData The encrypted data. |
||
| 387 | * @param XMLSecurityKey $inputKey The decryption key. |
||
| 388 | * @param array &$blacklist Blacklisted decryption algorithms. |
||
| 389 | * @return \DOMElement The decrypted element. |
||
| 390 | * @throws \Exception |
||
| 391 | */ |
||
| 392 | private static function doDecryptElement(\DOMElement $encryptedData, XMLSecurityKey $inputKey, array &$blacklist) |
||
| 526 | |||
| 527 | /** |
||
| 528 | * Decrypt an encrypted element. |
||
| 529 | * |
||
| 530 | * @param \DOMElement $encryptedData The encrypted data. |
||
| 531 | * @param XMLSecurityKey $inputKey The decryption key. |
||
| 532 | * @param array $blacklist Blacklisted decryption algorithms. |
||
| 533 | * @return \DOMElement The decrypted element. |
||
| 534 | * @throws \Exception |
||
| 535 | */ |
||
| 536 | public static function decryptElement(\DOMElement $encryptedData, XMLSecurityKey $inputKey, array $blacklist = array()) |
||
| 549 | |||
| 550 | /** |
||
| 551 | * Extract localized strings from a set of nodes. |
||
| 552 | * |
||
| 553 | * @param \DOMElement $parent The element that contains the localized strings. |
||
| 554 | * @param string $namespaceURI The namespace URI the localized strings should have. |
||
| 555 | * @param string $localName The localName of the localized strings. |
||
| 556 | * @return array Localized strings. |
||
| 557 | */ |
||
| 558 | public static function extractLocalizedStrings(\DOMElement $parent, $namespaceURI, $localName) |
||
| 579 | |||
| 580 | /** |
||
| 581 | * Extract strings from a set of nodes. |
||
| 582 | * |
||
| 583 | * @param \DOMElement $parent The element that contains the localized strings. |
||
| 584 | * @param string $namespaceURI The namespace URI the string elements should have. |
||
| 585 | * @param string $localName The localName of the string elements. |
||
| 586 | * @return array The string values of the various nodes. |
||
| 587 | */ |
||
| 588 | public static function extractStrings(\DOMElement $parent, $namespaceURI, $localName) |
||
| 603 | |||
| 604 | /** |
||
| 605 | * Append string element. |
||
| 606 | * |
||
| 607 | * @param \DOMElement $parent The parent element we should append the new nodes to. |
||
| 608 | * @param string $namespace The namespace of the created element. |
||
| 609 | * @param string $name The name of the created element. |
||
| 610 | * @param string $value The value of the element. |
||
| 611 | * @return \DOMElement The generated element. |
||
| 612 | */ |
||
| 613 | public static function addString(\DOMElement $parent, $namespace, $name, $value) |
||
| 627 | |||
| 628 | /** |
||
| 629 | * Append string elements. |
||
| 630 | * |
||
| 631 | * @param \DOMElement $parent The parent element we should append the new nodes to. |
||
| 632 | * @param string $namespace The namespace of the created elements |
||
| 633 | * @param string $name The name of the created elements |
||
| 634 | * @param bool $localized Whether the strings are localized, and should include the xml:lang attribute. |
||
| 635 | * @param array $values The values we should create the elements from. |
||
| 636 | */ |
||
| 637 | public static function addStrings(\DOMElement $parent, $namespace, $name, $localized, array $values) |
||
| 654 | |||
| 655 | /** |
||
| 656 | * Create a KeyDescriptor with the given certificate. |
||
| 657 | * |
||
| 658 | * @param string $x509Data The certificate, as a base64-encoded DER data. |
||
| 659 | * @return \SAML2\XML\md\KeyDescriptor The keydescriptor. |
||
| 660 | */ |
||
| 661 | public static function createKeyDescriptor($x509Data) |
||
| 679 | |||
| 680 | /** |
||
| 681 | * This function converts a SAML2 timestamp on the form |
||
| 682 | * yyyy-mm-ddThh:mm:ss(\.s+)?Z to a UNIX timestamp. The sub-second |
||
| 683 | * part is ignored. |
||
| 684 | * |
||
| 685 | * Andreas comments: |
||
| 686 | * I got this timestamp from Shibboleth 1.3 IdP: 2008-01-17T11:28:03.577Z |
||
| 687 | * Therefore I added to possibility to have microseconds to the format. |
||
| 688 | * Added: (\.\\d{1,3})? to the regex. |
||
| 689 | * |
||
| 690 | * Note that we always require a 'Z' timezone for the dateTime to be valid. |
||
| 691 | * This is not in the SAML spec but that's considered to be a bug in the |
||
| 692 | * spec. See https://github.com/simplesamlphp/saml2/pull/36 for some |
||
| 693 | * background. |
||
| 694 | * |
||
| 695 | * @param string $time The time we should convert. |
||
| 696 | * @return int Converted to a unix timestamp. |
||
| 697 | * @throws \Exception |
||
| 698 | */ |
||
| 699 | public static function xsDateTimeToTimestamp($time) |
||
| 700 | { |
||
| 701 | $matches = array(); |
||
| 702 | |||
| 703 | // We use a very strict regex to parse the timestamp. |
||
| 704 | $regex = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D'; |
||
| 705 | if (preg_match($regex, $time, $matches) == 0) { |
||
| 706 | throw new \Exception( |
||
| 707 | 'Invalid SAML2 timestamp passed to xsDateTimeToTimestamp: ' . $time |
||
| 708 | ); |
||
| 709 | } |
||
| 710 | |||
| 711 | // Extract the different components of the time from the matches in the regex. |
||
| 712 | // intval will ignore leading zeroes in the string. |
||
| 713 | $year = intval($matches[1]); |
||
| 714 | $month = intval($matches[2]); |
||
| 715 | $day = intval($matches[3]); |
||
| 716 | $hour = intval($matches[4]); |
||
| 717 | $minute = intval($matches[5]); |
||
| 718 | $second = intval($matches[6]); |
||
| 719 | |||
| 720 | // We use gmmktime because the timestamp will always be given |
||
| 721 | //in UTC. |
||
| 722 | $ts = gmmktime($hour, $minute, $second, $month, $day, $year); |
||
| 723 | |||
| 724 | return $ts; |
||
| 725 | } |
||
| 735 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.