1<?php 2 3namespace SimpleSAML; 4 5use SAML2\Constants; 6use SimpleSAML\Error; 7use SimpleSAML\Utils; 8 9/** 10 * Configuration of SimpleSAMLphp 11 * 12 * @author Andreas Aakre Solberg, UNINETT AS. <andreas.solberg@uninett.no> 13 * @package SimpleSAMLphp 14 */ 15class Configuration implements Utils\ClearableState 16{ 17 /** 18 * A default value which means that the given option is required. 19 * 20 * @var string 21 */ 22 const REQUIRED_OPTION = '___REQUIRED_OPTION___'; 23 24 25 /** 26 * Associative array with mappings from instance-names to configuration objects. 27 * 28 * @var array 29 */ 30 private static $instance = []; 31 32 33 /** 34 * Configuration directories. 35 * 36 * This associative array contains the mappings from configuration sets to 37 * configuration directories. 38 * 39 * @var array 40 */ 41 private static $configDirs = []; 42 43 44 /** 45 * Cache of loaded configuration files. 46 * 47 * The index in the array is the full path to the file. 48 * 49 * @var array 50 */ 51 private static $loadedConfigs = []; 52 53 54 /** 55 * The configuration array. 56 * 57 * @var array 58 */ 59 private $configuration; 60 61 62 /** 63 * The location which will be given when an error occurs. 64 * 65 * @var string 66 */ 67 private $location; 68 69 70 /** 71 * The file this configuration was loaded from. 72 * 73 * @var string|null 74 */ 75 private $filename = null; 76 77 78 /** 79 * Temporary property that tells if the deprecated getBaseURL() method has been called or not. 80 * 81 * @var bool 82 */ 83 private $deprecated_base_url_used = false; 84 85 86 /** 87 * Initializes a configuration from the given array. 88 * 89 * @param array $config The configuration array. 90 * @param string $location The location which will be given when an error occurs. 91 */ 92 public function __construct($config, $location) 93 { 94 assert(is_array($config)); 95 assert(is_string($location)); 96 97 $this->configuration = $config; 98 $this->location = $location; 99 } 100 101 /** 102 * Load the given configuration file. 103 * 104 * @param string $filename The full path of the configuration file. 105 * @param bool $required Whether the file is required. 106 * 107 * @return \SimpleSAML\Configuration The configuration file. An exception will be thrown if the 108 * configuration file is missing. 109 * 110 * @throws \Exception If the configuration file is invalid or missing. 111 */ 112 private static function loadFromFile($filename, $required) 113 { 114 assert(is_string($filename)); 115 assert(is_bool($required)); 116 117 if (array_key_exists($filename, self::$loadedConfigs)) { 118 return self::$loadedConfigs[$filename]; 119 } 120 121 if (file_exists($filename)) { 122 $config = 'UNINITIALIZED'; 123 124 // the file initializes a variable named '$config' 125 ob_start(); 126 if (interface_exists('Throwable', false)) { 127 try { 128 require($filename); 129 } catch (\ParseError $e) { 130 self::$loadedConfigs[$filename] = self::loadFromArray([], '[ARRAY]', 'simplesaml'); 131 throw new Error\ConfigurationError($e->getMessage(), $filename, []); 132 } 133 } else { 134 require($filename); 135 } 136 137 $spurious_output = ob_get_length() > 0; 138 ob_end_clean(); 139 140 // check that $config exists 141 if (!isset($config)) { 142 throw new Error\ConfigurationError( 143 '$config is not defined in the configuration file.', 144 $filename 145 ); 146 } 147 148 // check that $config is initialized to an array 149 if (!is_array($config)) { 150 throw new Error\ConfigurationError( 151 '$config is not an array.', 152 $filename 153 ); 154 } 155 156 // check that $config is not empty 157 if (empty($config)) { 158 throw new Error\ConfigurationError( 159 '$config is empty.', 160 $filename 161 ); 162 } 163 } elseif ($required) { 164 // file does not exist, but is required 165 throw new Error\ConfigurationError('Missing configuration file', $filename); 166 } else { 167 // file does not exist, but is optional, so return an empty configuration object without saving it 168 $cfg = new Configuration([], $filename); 169 $cfg->filename = $filename; 170 return $cfg; 171 } 172 173 $cfg = new Configuration($config, $filename); 174 $cfg->filename = $filename; 175 176 self::$loadedConfigs[$filename] = $cfg; 177 178 if ($spurious_output) { 179 Logger::warning( 180 "The configuration file '$filename' generates output. Please review your configuration." 181 ); 182 } 183 184 return $cfg; 185 } 186 187 188 /** 189 * Set the directory for configuration files for the given configuration set. 190 * 191 * @param string $path The directory which contains the configuration files. 192 * @param string $configSet The configuration set. Defaults to 'simplesaml'. 193 * @return void 194 */ 195 public static function setConfigDir($path, $configSet = 'simplesaml') 196 { 197 assert(is_string($path)); 198 assert(is_string($configSet)); 199 200 self::$configDirs[$configSet] = $path; 201 } 202 203 /** 204 * Store a pre-initialized configuration. 205 * 206 * Allows consumers to create configuration objects without having them 207 * loaded from a file. 208 * 209 * @param \SimpleSAML\Configuration $config The configuration object to store 210 * @param string $filename The name of the configuration file. 211 * @param string $configSet The configuration set. Optional, defaults to 'simplesaml'. 212 * @return void 213 * @throws \Exception 214 */ 215 public static function setPreLoadedConfig( 216 Configuration $config, 217 $filename = 'config.php', 218 $configSet = 'simplesaml' 219 ) { 220 assert(is_string($filename)); 221 assert(is_string($configSet)); 222 223 if (!array_key_exists($configSet, self::$configDirs)) { 224 if ($configSet !== 'simplesaml') { 225 throw new \Exception('Configuration set \'' . $configSet . '\' not initialized.'); 226 } else { 227 self::$configDirs['simplesaml'] = dirname(dirname(dirname(__FILE__))) . '/config'; 228 } 229 } 230 231 $dir = self::$configDirs[$configSet]; 232 $filePath = $dir . '/' . $filename; 233 234 self::$loadedConfigs[$filePath] = $config; 235 } 236 237 238 /** 239 * Load a configuration file from a configuration set. 240 * 241 * @param string $filename The name of the configuration file. 242 * @param string $configSet The configuration set. Optional, defaults to 'simplesaml'. 243 * 244 * @return \SimpleSAML\Configuration The Configuration object. 245 * @throws \Exception If the configuration set is not initialized. 246 */ 247 public static function getConfig($filename = 'config.php', $configSet = 'simplesaml') 248 { 249 assert(is_string($filename)); 250 assert(is_string($configSet)); 251 252 if (!array_key_exists($configSet, self::$configDirs)) { 253 if ($configSet !== 'simplesaml') { 254 throw new \Exception('Configuration set \'' . $configSet . '\' not initialized.'); 255 } else { 256 self::$configDirs['simplesaml'] = Utils\Config::getConfigDir(); 257 } 258 } 259 260 $dir = self::$configDirs[$configSet]; 261 $filePath = $dir . '/' . $filename; 262 return self::loadFromFile($filePath, true); 263 } 264 265 266 /** 267 * Load a configuration file from a configuration set. 268 * 269 * This function will return a configuration object even if the file does not exist. 270 * 271 * @param string $filename The name of the configuration file. 272 * @param string $configSet The configuration set. Optional, defaults to 'simplesaml'. 273 * 274 * @return \SimpleSAML\Configuration A configuration object. 275 * @throws \Exception If the configuration set is not initialized. 276 */ 277 public static function getOptionalConfig($filename = 'config.php', $configSet = 'simplesaml') 278 { 279 assert(is_string($filename)); 280 assert(is_string($configSet)); 281 282 if (!array_key_exists($configSet, self::$configDirs)) { 283 if ($configSet !== 'simplesaml') { 284 throw new \Exception('Configuration set \'' . $configSet . '\' not initialized.'); 285 } else { 286 self::$configDirs['simplesaml'] = Utils\Config::getConfigDir(); 287 } 288 } 289 290 $dir = self::$configDirs[$configSet]; 291 $filePath = $dir . '/' . $filename; 292 return self::loadFromFile($filePath, false); 293 } 294 295 296 /** 297 * Loads a configuration from the given array. 298 * 299 * @param array $config The configuration array. 300 * @param string $location The location which will be given when an error occurs. Optional. 301 * @param string|null $instance The name of this instance. If specified, the configuration will be loaded and an 302 * instance with that name will be kept for it to be retrieved later with getInstance($instance). If null, the 303 * configuration will not be kept for later use. Defaults to null. 304 * 305 * @return \SimpleSAML\Configuration The configuration object. 306 */ 307 public static function loadFromArray($config, $location = '[ARRAY]', $instance = null) 308 { 309 assert(is_array($config)); 310 assert(is_string($location)); 311 312 $c = new Configuration($config, $location); 313 if ($instance !== null) { 314 self::$instance[$instance] = $c; 315 } 316 return $c; 317 } 318 319 320 /** 321 * Get a configuration file by its instance name. 322 * 323 * This function retrieves a configuration file by its instance name. The instance 324 * name is initialized by the init function, or by copyFromBase function. 325 * 326 * If no configuration file with the given instance name is found, an exception will 327 * be thrown. 328 * 329 * @param string $instancename The instance name of the configuration file. Deprecated. 330 * 331 * @return \SimpleSAML\Configuration The configuration object. 332 * 333 * @throws \Exception If the configuration with $instancename name is not initialized. 334 */ 335 public static function getInstance($instancename = 'simplesaml') 336 { 337 assert(is_string($instancename)); 338 339 // check if the instance exists already 340 if (array_key_exists($instancename, self::$instance)) { 341 return self::$instance[$instancename]; 342 } 343 344 if ($instancename === 'simplesaml') { 345 try { 346 return self::getConfig(); 347 } catch (Error\ConfigurationError $e) { 348 throw Error\CriticalConfigurationError::fromException($e); 349 } 350 } 351 352 throw new Error\CriticalConfigurationError( 353 'Configuration with name ' . $instancename . ' is not initialized.' 354 ); 355 } 356 357 358 /** 359 * Initialize a instance name with the given configuration file. 360 * 361 * TODO: remove. 362 * 363 * @param string $path 364 * @param string $instancename 365 * @param string $configfilename 366 * @return \SimpleSAML\Configuration 367 * 368 * @see setConfigDir() 369 * @deprecated This function is superseeded by the setConfigDir function. 370 */ 371 public static function init($path, $instancename = 'simplesaml', $configfilename = 'config.php') 372 { 373 assert(is_string($path)); 374 assert(is_string($instancename)); 375 assert(is_string($configfilename)); 376 377 if ($instancename === 'simplesaml') { 378 // for backwards compatibility 379 self::setConfigDir($path, 'simplesaml'); 380 } 381 382 // check if we already have loaded the given config - return the existing instance if we have 383 if (array_key_exists($instancename, self::$instance)) { 384 return self::$instance[$instancename]; 385 } 386 387 self::$instance[$instancename] = self::loadFromFile($path . '/' . $configfilename, true); 388 return self::$instance[$instancename]; 389 } 390 391 392 /** 393 * Load a configuration file which is located in the same directory as this configuration file. 394 * 395 * TODO: remove. 396 * 397 * @param string $instancename 398 * @param string $filename 399 * @return \SimpleSAML\Configuration 400 * 401 * @see getConfig() 402 * @deprecated This function is superseeded by the getConfig() function. 403 */ 404 public function copyFromBase($instancename, $filename) 405 { 406 assert(is_string($instancename)); 407 assert(is_string($filename)); 408 assert($this->filename !== null); 409 410 // check if we already have loaded the given config - return the existing instance if we have 411 if (array_key_exists($instancename, self::$instance)) { 412 return self::$instance[$instancename]; 413 } 414 415 $dir = dirname($this->filename); 416 417 self::$instance[$instancename] = self::loadFromFile($dir . '/' . $filename, true); 418 return self::$instance[$instancename]; 419 } 420 421 422 /** 423 * Retrieve the current version of SimpleSAMLphp. 424 * 425 * @return string 426 */ 427 public function getVersion() 428 { 429 return '1.18.7'; 430 } 431 432 433 /** 434 * Retrieve a configuration option set in config.php. 435 * 436 * @param string $name Name of the configuration option. 437 * @param mixed $default Default value of the configuration option. This parameter will default to null if not 438 * specified. This can be set to \SimpleSAML\Configuration::REQUIRED_OPTION, which will 439 * cause an exception to be thrown if the option isn't found. 440 * 441 * @return mixed The configuration option with name $name, or $default if the option was not found. 442 * 443 * @throws \Exception If the required option cannot be retrieved. 444 */ 445 public function getValue($name, $default = null) 446 { 447 // return the default value if the option is unset 448 if (!array_key_exists($name, $this->configuration)) { 449 if ($default === self::REQUIRED_OPTION) { 450 throw new \Exception( 451 $this->location . ': Could not retrieve the required option ' . 452 var_export($name, true) 453 ); 454 } 455 return $default; 456 } 457 458 return $this->configuration[$name]; 459 } 460 461 462 /** 463 * Check whether a key in the configuration exists or not. 464 * 465 * @param string $name The key in the configuration to look for. 466 * 467 * @return boolean If the value is set in this configuration. 468 */ 469 public function hasValue($name) 470 { 471 return array_key_exists($name, $this->configuration); 472 } 473 474 475 /** 476 * Check whether any key of the set given exists in the configuration. 477 * 478 * @param array $names An array of options to look for. 479 * 480 * @return boolean If any of the keys in $names exist in the configuration 481 */ 482 public function hasValueOneOf($names) 483 { 484 foreach ($names as $name) { 485 if ($this->hasValue($name)) { 486 return true; 487 } 488 } 489 return false; 490 } 491 492 493 /** 494 * Retrieve the absolute path of the SimpleSAMLphp installation, relative to the root of the website. 495 * 496 * For example: simplesaml/ 497 * 498 * The path will always end with a '/' and never have a leading slash. 499 * 500 * @return string The absolute path relative to the root of the website. 501 * 502 * @throws \SimpleSAML\Error\CriticalConfigurationError If the format of 'baseurlpath' is incorrect. 503 * 504 * @deprecated This method will be removed in SimpleSAMLphp 2.0. Please use getBasePath() instead. 505 */ 506 public function getBaseURL() 507 { 508 if (!$this->deprecated_base_url_used) { 509 $this->deprecated_base_url_used = true; 510 Logger::warning( 511 "\SimpleSAML\Configuration::getBaseURL() is deprecated, please use getBasePath() instead." 512 ); 513 } 514 if (preg_match('/^\*(.*)$/D', $this->getString('baseurlpath', 'simplesaml/'), $matches)) { 515 // deprecated behaviour, will be removed in the future 516 return Utils\HTTP::getFirstPathElement(false) . $matches[1]; 517 } 518 return ltrim($this->getBasePath(), '/'); 519 } 520 521 522 /** 523 * Retrieve the absolute path pointing to the SimpleSAMLphp installation. 524 * 525 * The path is guaranteed to start and end with a slash ('/'). E.g.: /simplesaml/ 526 * 527 * @return string The absolute path where SimpleSAMLphp can be reached in the web server. 528 * 529 * @throws \SimpleSAML\Error\CriticalConfigurationError If the format of 'baseurlpath' is incorrect. 530 */ 531 public function getBasePath() 532 { 533 $baseURL = $this->getString('baseurlpath', 'simplesaml/'); 534 535 if (preg_match('#^https?://[^/]*(?:/(.+/?)?)?$#', $baseURL, $matches)) { 536 // we have a full url, we need to strip the path 537 if (!array_key_exists(1, $matches)) { 538 // absolute URL without path 539 return '/'; 540 } 541 return '/' . rtrim($matches[1], '/') . '/'; 542 } elseif ($baseURL === '' || $baseURL === '/') { 543 // root directory of site 544 return '/'; 545 } elseif (preg_match('#^/?((?:[^/\s]+/?)+)#', $baseURL, $matches)) { 546 // local path only 547 return '/' . rtrim($matches[1], '/') . '/'; 548 } else { 549 /* 550 * Invalid 'baseurlpath'. We cannot recover from this, so throw a critical exception and try to be graceful 551 * with the configuration. Use a guessed base path instead of the one provided. 552 */ 553 $c = $this->toArray(); 554 $c['baseurlpath'] = Utils\HTTP::guessBasePath(); 555 throw new Error\CriticalConfigurationError( 556 'Incorrect format for option \'baseurlpath\'. Value is: "' . 557 $this->getString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' . 558 ' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].', 559 $this->filename, 560 $c 561 ); 562 } 563 } 564 565 566 /** 567 * This function resolves a path which may be relative to the SimpleSAMLphp base directory. 568 * 569 * The path will never end with a '/'. 570 * 571 * @param string|null $path The path we should resolve. This option may be null. 572 * 573 * @return string|null $path if $path is an absolute path, or $path prepended with the base directory of this 574 * SimpleSAMLphp installation. We will return NULL if $path is null. 575 */ 576 public function resolvePath($path) 577 { 578 if ($path === null) { 579 return null; 580 } 581 582 assert(is_string($path)); 583 584 return Utils\System::resolvePath($path, $this->getBaseDir()); 585 } 586 587 588 /** 589 * Retrieve a path configuration option set in config.php. 590 * 591 * The function will always return an absolute path unless the option is not set. It will then return the default 592 * value. 593 * 594 * It checks if the value starts with a slash, and prefixes it with the value from getBaseDir if it doesn't. 595 * 596 * @param string $name Name of the configuration option. 597 * @param string|null $default Default value of the configuration option. This parameter will default to null if 598 * not specified. 599 * 600 * @return string|null The path configuration option with name $name, or $default if the option was not found. 601 */ 602 public function getPathValue($name, $default = null) 603 { 604 // return the default value if the option is unset 605 if (!array_key_exists($name, $this->configuration)) { 606 $path = $default; 607 } else { 608 $path = $this->configuration[$name]; 609 } 610 611 $path = $this->resolvePath($path); 612 if ($path === null) { 613 return null; 614 } 615 616 return $path . '/'; 617 } 618 619 620 /** 621 * Retrieve the base directory for this SimpleSAMLphp installation. 622 * 623 * This function first checks the 'basedir' configuration option. If this option is undefined or null, then we 624 * fall back to looking at the current filename. 625 * 626 * @return string The absolute path to the base directory for this SimpleSAMLphp installation. This path will 627 * always end with a slash. 628 */ 629 public function getBaseDir() 630 { 631 // check if a directory is configured in the configuration file 632 $dir = $this->getString('basedir', null); 633 if ($dir !== null) { 634 // add trailing slash if it is missing 635 if (substr($dir, -1) !== DIRECTORY_SEPARATOR) { 636 $dir .= DIRECTORY_SEPARATOR; 637 } 638 639 return $dir; 640 } 641 642 // the directory wasn't set in the configuration file, path is <base directory>/lib/SimpleSAML/Configuration.php 643 $dir = __FILE__; 644 assert(basename($dir) === 'Configuration.php'); 645 646 $dir = dirname($dir); 647 assert(basename($dir) === 'SimpleSAML'); 648 649 $dir = dirname($dir); 650 assert(basename($dir) === 'lib'); 651 652 $dir = dirname($dir); 653 654 // Add trailing directory separator 655 $dir .= DIRECTORY_SEPARATOR; 656 657 return $dir; 658 } 659 660 661 /** 662 * This function retrieves a boolean configuration option. 663 * 664 * An exception will be thrown if this option isn't a boolean, or if this option isn't found, and no default value 665 * is given. 666 * 667 * @param string $name The name of the option. 668 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 669 * required if this parameter isn't given. The default value can be any value, including 670 * null. 671 * 672 * @return boolean|mixed The option with the given name, or $default if the option isn't found and $default is 673 * specified. 674 * 675 * @throws \Exception If the option is not boolean. 676 */ 677 public function getBoolean($name, $default = self::REQUIRED_OPTION) 678 { 679 assert(is_string($name)); 680 681 $ret = $this->getValue($name, $default); 682 683 if ($ret === $default) { 684 // the option wasn't found, or it matches the default value. In any case, return this value 685 return $ret; 686 } 687 688 if (!is_bool($ret)) { 689 throw new \Exception( 690 $this->location . ': The option ' . var_export($name, true) . 691 ' is not a valid boolean value.' 692 ); 693 } 694 695 return $ret; 696 } 697 698 699 /** 700 * This function retrieves a string configuration option. 701 * 702 * An exception will be thrown if this option isn't a string, or if this option isn't found, and no default value 703 * is given. 704 * 705 * @param string $name The name of the option. 706 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 707 * required if this parameter isn't given. The default value can be any value, including 708 * null. 709 * 710 * @return string|mixed The option with the given name, or $default if the option isn't found and $default is 711 * specified. 712 * 713 * @throws \Exception If the option is not a string. 714 */ 715 public function getString($name, $default = self::REQUIRED_OPTION) 716 { 717 assert(is_string($name)); 718 719 $ret = $this->getValue($name, $default); 720 721 if ($ret === $default) { 722 // the option wasn't found, or it matches the default value. In any case, return this value 723 return $ret; 724 } 725 726 if (!is_string($ret)) { 727 throw new \Exception( 728 $this->location . ': The option ' . var_export($name, true) . 729 ' is not a valid string value.' 730 ); 731 } 732 733 return $ret; 734 } 735 736 737 /** 738 * This function retrieves an integer configuration option. 739 * 740 * An exception will be thrown if this option isn't an integer, or if this option isn't found, and no default value 741 * is given. 742 * 743 * @param string $name The name of the option. 744 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 745 * required if this parameter isn't given. The default value can be any value, including 746 * null. 747 * 748 * @return int|mixed The option with the given name, or $default if the option isn't found and $default is 749 * specified. 750 * 751 * @throws \Exception If the option is not an integer. 752 */ 753 public function getInteger($name, $default = self::REQUIRED_OPTION) 754 { 755 assert(is_string($name)); 756 757 $ret = $this->getValue($name, $default); 758 759 if ($ret === $default) { 760 // the option wasn't found, or it matches the default value. In any case, return this value 761 return $ret; 762 } 763 764 if (!is_int($ret)) { 765 throw new \Exception( 766 $this->location . ': The option ' . var_export($name, true) . 767 ' is not a valid integer value.' 768 ); 769 } 770 771 return $ret; 772 } 773 774 775 /** 776 * This function retrieves an integer configuration option where the value must be in the specified range. 777 * 778 * An exception will be thrown if: 779 * - the option isn't an integer 780 * - the option isn't found, and no default value is given 781 * - the value is outside of the allowed range 782 * 783 * @param string $name The name of the option. 784 * @param int $minimum The smallest value which is allowed. 785 * @param int $maximum The largest value which is allowed. 786 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 787 * required if this parameter isn't given. The default value can be any value, including 788 * null. 789 * 790 * @return int|mixed The option with the given name, or $default if the option isn't found and $default is 791 * specified. 792 * 793 * @throws \Exception If the option is not in the range specified. 794 */ 795 public function getIntegerRange($name, $minimum, $maximum, $default = self::REQUIRED_OPTION) 796 { 797 assert(is_string($name)); 798 assert(is_int($minimum)); 799 assert(is_int($maximum)); 800 801 $ret = $this->getInteger($name, $default); 802 803 if ($ret === $default) { 804 // the option wasn't found, or it matches the default value. In any case, return this value 805 return $ret; 806 } 807 808 if ($ret < $minimum || $ret > $maximum) { 809 throw new \Exception( 810 $this->location . ': Value of option ' . var_export($name, true) . 811 ' is out of range. Value is ' . $ret . ', allowed range is [' 812 . $minimum . ' - ' . $maximum . ']' 813 ); 814 } 815 816 return $ret; 817 } 818 819 820 /** 821 * Retrieve a configuration option with one of the given values. 822 * 823 * This will check that the configuration option matches one of the given values. The match will use 824 * strict comparison. An exception will be thrown if it does not match. 825 * 826 * The option can be mandatory or optional. If no default value is given, it will be considered to be 827 * mandatory, and an exception will be thrown if it isn't provided. If a default value is given, it 828 * is considered to be optional, and the default value is returned. The default value is automatically 829 * included in the list of allowed values. 830 * 831 * @param string $name The name of the option. 832 * @param array $allowedValues The values the option is allowed to take, as an array. 833 * @param mixed $default The default value which will be returned if the option isn't found. If this parameter 834 * isn't given, the option will be considered to be mandatory. The default value can be 835 * any value, including null. 836 * 837 * @return mixed The option with the given name, or $default if the option isn't found and $default is given. 838 * 839 * @throws \Exception If the option does not have any of the allowed values. 840 */ 841 public function getValueValidate($name, $allowedValues, $default = self::REQUIRED_OPTION) 842 { 843 assert(is_string($name)); 844 assert(is_array($allowedValues)); 845 846 $ret = $this->getValue($name, $default); 847 if ($ret === $default) { 848 // the option wasn't found, or it matches the default value. In any case, return this value 849 return $ret; 850 } 851 852 if (!in_array($ret, $allowedValues, true)) { 853 $strValues = []; 854 foreach ($allowedValues as $av) { 855 $strValues[] = var_export($av, true); 856 } 857 $strValues = implode(', ', $strValues); 858 859 throw new \Exception( 860 $this->location . ': Invalid value given for the option ' . 861 var_export($name, true) . '. It should have one of the following values: ' . 862 $strValues . '; but it had the following value: ' . var_export($ret, true) 863 ); 864 } 865 866 return $ret; 867 } 868 869 870 /** 871 * This function retrieves an array configuration option. 872 * 873 * An exception will be thrown if this option isn't an array, or if this option isn't found, and no 874 * default value is given. 875 * 876 * @param string $name The name of the option. 877 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 878 * required if this parameter isn't given. The default value can be any value, including 879 * null. 880 * 881 * @return array|mixed The option with the given name, or $default if the option isn't found and $default is 882 * specified. 883 * 884 * @throws \Exception If the option is not an array. 885 */ 886 public function getArray($name, $default = self::REQUIRED_OPTION) 887 { 888 assert(is_string($name)); 889 890 $ret = $this->getValue($name, $default); 891 892 if ($ret === $default) { 893 // the option wasn't found, or it matches the default value. In any case, return this value 894 return $ret; 895 } 896 897 if (!is_array($ret)) { 898 throw new \Exception($this->location . ': The option ' . var_export($name, true) . ' is not an array.'); 899 } 900 901 return $ret; 902 } 903 904 905 /** 906 * This function retrieves an array configuration option. 907 * 908 * If the configuration option isn't an array, it will be converted to an array. 909 * 910 * @param string $name The name of the option. 911 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 912 * required if this parameter isn't given. The default value can be any value, including 913 * null. 914 * 915 * @return array The option with the given name, or $default if the option isn't found and $default is specified. 916 */ 917 public function getArrayize($name, $default = self::REQUIRED_OPTION) 918 { 919 assert(is_string($name)); 920 921 $ret = $this->getValue($name, $default); 922 923 if ($ret === $default) { 924 // the option wasn't found, or it matches the default value. In any case, return this value 925 return $ret; 926 } 927 928 if (!is_array($ret)) { 929 $ret = [$ret]; 930 } 931 932 return $ret; 933 } 934 935 936 /** 937 * This function retrieves a configuration option with a string or an array of strings. 938 * 939 * If the configuration option is a string, it will be converted to an array with a single string 940 * 941 * @param string $name The name of the option. 942 * @param mixed $default A default value which will be returned if the option isn't found. The option will be 943 * required if this parameter isn't given. The default value can be any value, including 944 * null. 945 * 946 * @return array The option with the given name, or $default if the option isn't found and $default is specified. 947 * 948 * @throws \Exception If the option is not a string or an array of strings. 949 */ 950 public function getArrayizeString($name, $default = self::REQUIRED_OPTION) 951 { 952 assert(is_string($name)); 953 954 $ret = $this->getArrayize($name, $default); 955 956 if ($ret === $default) { 957 // the option wasn't found, or it matches the default value. In any case, return this value 958 return $ret; 959 } 960 961 foreach ($ret as $value) { 962 if (!is_string($value)) { 963 throw new \Exception( 964 $this->location . ': The option ' . var_export($name, true) . 965 ' must be a string or an array of strings.' 966 ); 967 } 968 } 969 970 return $ret; 971 } 972 973 974 /** 975 * Retrieve an array as a \SimpleSAML\Configuration object. 976 * 977 * This function will load the value of an option into a \SimpleSAML\Configuration object. The option must contain 978 * an array. 979 * 980 * An exception will be thrown if this option isn't an array, or if this option isn't found, and no default value 981 * is given. 982 * 983 * @param string $name The name of the option. 984 * @param array|null $default A default value which will be used if the option isn't found. An empty Configuration 985 * object will be returned if this parameter isn't given and the option doesn't exist. 986 * This function will only return null if $default is set to null and the option 987 * doesn't exist. 988 * 989 * @return mixed The option with the given name, or $default if the option isn't found and $default is specified. 990 * 991 * @throws \Exception If the option is not an array. 992 */ 993 public function getConfigItem($name, $default = []) 994 { 995 assert(is_string($name)); 996 997 $ret = $this->getValue($name, $default); 998 999 if ($ret === null) { 1000 // the option wasn't found, or it is explicitly null 1001 // do not instantiate a new Configuration instance, but just return null 1002 return null; 1003 } 1004 1005 if (!is_array($ret)) { 1006 throw new \Exception( 1007 $this->location . ': The option ' . var_export($name, true) . 1008 ' is not an array.' 1009 ); 1010 } 1011 1012 return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']'); 1013 } 1014 1015 1016 /** 1017 * Retrieve an array of arrays as an array of \SimpleSAML\Configuration objects. 1018 * 1019 * This function will retrieve an option containing an array of arrays, and create an array of 1020 * \SimpleSAML\Configuration objects from that array. The indexes in the new array will be the same as the original 1021 * indexes, but the values will be \SimpleSAML\Configuration objects. 1022 * 1023 * An exception will be thrown if this option isn't an array of arrays, or if this option isn't found, and no 1024 * default value is given. 1025 * 1026 * @param string $name The name of the option. 1027 * 1028 * @return array The array of \SimpleSAML\Configuration objects. 1029 * 1030 * @throws \Exception If the value of this element is not an array. 1031 * 1032 * @deprecated Very specific function, will be removed in a future release; use getConfigItem or getArray instead 1033 */ 1034 public function getConfigList($name) 1035 { 1036 assert(is_string($name)); 1037 1038 $ret = $this->getValue($name, []); 1039 1040 if (!is_array($ret)) { 1041 throw new \Exception( 1042 $this->location . ': The option ' . var_export($name, true) . 1043 ' is not an array.' 1044 ); 1045 } 1046 1047 $out = []; 1048 foreach ($ret as $index => $config) { 1049 $newLoc = $this->location . '[' . var_export($name, true) . '][' . 1050 var_export($index, true) . ']'; 1051 if (!is_array($config)) { 1052 throw new \Exception($newLoc . ': The value of this element was expected to be an array.'); 1053 } 1054 $out[$index] = self::loadFromArray($config, $newLoc); 1055 } 1056 1057 return $out; 1058 } 1059 1060 1061 /** 1062 * Retrieve list of options. 1063 * 1064 * This function returns the name of all options which are defined in this 1065 * configuration file, as an array of strings. 1066 * 1067 * @return array Name of all options defined in this configuration file. 1068 */ 1069 public function getOptions() 1070 { 1071 return array_keys($this->configuration); 1072 } 1073 1074 1075 /** 1076 * Convert this configuration object back to an array. 1077 * 1078 * @return array An associative array with all configuration options and values. 1079 */ 1080 public function toArray() 1081 { 1082 return $this->configuration; 1083 } 1084 1085 1086 /** 1087 * Retrieve the default binding for the given endpoint type. 1088 * 1089 * This function combines the current metadata type (SAML 2 / SAML 1.1) 1090 * with the endpoint type to determine which binding is the default. 1091 * 1092 * @param string $endpointType The endpoint type. 1093 * 1094 * @return string The default binding. 1095 * 1096 * @throws \Exception If the default binding is missing for this endpoint type. 1097 */ 1098 private function getDefaultBinding($endpointType) 1099 { 1100 assert(is_string($endpointType)); 1101 1102 $set = $this->getString('metadata-set'); 1103 switch ($set . ':' . $endpointType) { 1104 case 'saml20-idp-remote:SingleSignOnService': 1105 case 'saml20-idp-remote:SingleLogoutService': 1106 case 'saml20-sp-remote:SingleLogoutService': 1107 return Constants::BINDING_HTTP_REDIRECT; 1108 case 'saml20-sp-remote:AssertionConsumerService': 1109 return Constants::BINDING_HTTP_POST; 1110 case 'saml20-idp-remote:ArtifactResolutionService': 1111 return Constants::BINDING_SOAP; 1112 case 'shib13-idp-remote:SingleSignOnService': 1113 return 'urn:mace:shibboleth:1.0:profiles:AuthnRequest'; 1114 case 'shib13-sp-remote:AssertionConsumerService': 1115 return 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post'; 1116 default: 1117 throw new \Exception('Missing default binding for ' . $endpointType . ' in ' . $set); 1118 } 1119 } 1120 1121 1122 /** 1123 * Helper function for dealing with metadata endpoints. 1124 * 1125 * @param string $endpointType The endpoint type. 1126 * 1127 * @return array Array of endpoints of the given type. 1128 * 1129 * @throws \Exception If any element of the configuration options for this endpoint type is incorrect. 1130 */ 1131 public function getEndpoints($endpointType) 1132 { 1133 assert(is_string($endpointType)); 1134 1135 $loc = $this->location . '[' . var_export($endpointType, true) . ']:'; 1136 1137 if (!array_key_exists($endpointType, $this->configuration)) { 1138 // no endpoints of the given type 1139 return []; 1140 } 1141 1142 1143 $eps = $this->configuration[$endpointType]; 1144 if (is_string($eps)) { 1145 // for backwards-compatibility 1146 $eps = [$eps]; 1147 } elseif (!is_array($eps)) { 1148 throw new \Exception($loc . ': Expected array or string.'); 1149 } 1150 1151 1152 foreach ($eps as $i => &$ep) { 1153 $iloc = $loc . '[' . var_export($i, true) . ']'; 1154 1155 if (is_string($ep)) { 1156 // for backwards-compatibility 1157 $ep = [ 1158 'Location' => $ep, 1159 'Binding' => $this->getDefaultBinding($endpointType), 1160 ]; 1161 $responseLocation = $this->getString($endpointType . 'Response', null); 1162 if ($responseLocation !== null) { 1163 $ep['ResponseLocation'] = $responseLocation; 1164 } 1165 } elseif (!is_array($ep)) { 1166 throw new \Exception($iloc . ': Expected a string or an array.'); 1167 } 1168 1169 if (!array_key_exists('Location', $ep)) { 1170 throw new \Exception($iloc . ': Missing Location.'); 1171 } 1172 if (!is_string($ep['Location'])) { 1173 throw new \Exception($iloc . ': Location must be a string.'); 1174 } 1175 1176 if (!array_key_exists('Binding', $ep)) { 1177 throw new \Exception($iloc . ': Missing Binding.'); 1178 } 1179 if (!is_string($ep['Binding'])) { 1180 throw new \Exception($iloc . ': Binding must be a string.'); 1181 } 1182 1183 if (array_key_exists('ResponseLocation', $ep)) { 1184 if (!is_string($ep['ResponseLocation'])) { 1185 throw new \Exception($iloc . ': ResponseLocation must be a string.'); 1186 } 1187 } 1188 1189 if (array_key_exists('index', $ep)) { 1190 if (!is_int($ep['index'])) { 1191 throw new \Exception($iloc . ': index must be an integer.'); 1192 } 1193 } 1194 } 1195 1196 return $eps; 1197 } 1198 1199 1200 /** 1201 * Find an endpoint of the given type, using a list of supported bindings as a way to prioritize. 1202 * 1203 * @param string $endpointType The endpoint type. 1204 * @param array $bindings Sorted array of acceptable bindings. 1205 * @param mixed $default The default value to return if no matching endpoint is found. If no default is provided, 1206 * an exception will be thrown. 1207 * 1208 * @return array|null The default endpoint, or null if no acceptable endpoints are used. 1209 * 1210 * @throws \Exception If no supported endpoint is found. 1211 */ 1212 public function getEndpointPrioritizedByBinding($endpointType, array $bindings, $default = self::REQUIRED_OPTION) 1213 { 1214 assert(is_string($endpointType)); 1215 1216 $endpoints = $this->getEndpoints($endpointType); 1217 1218 foreach ($bindings as $binding) { 1219 foreach ($endpoints as $ep) { 1220 if ($ep['Binding'] === $binding) { 1221 return $ep; 1222 } 1223 } 1224 } 1225 1226 if ($default === self::REQUIRED_OPTION) { 1227 $loc = $this->location . '[' . var_export($endpointType, true) . ']:'; 1228 throw new \Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.'); 1229 } 1230 1231 return $default; 1232 } 1233 1234 1235 /** 1236 * Find the default endpoint of the given type. 1237 * 1238 * @param string $endpointType The endpoint type. 1239 * @param array $bindings Array with acceptable bindings. Can be null if any binding is allowed. 1240 * @param mixed $default The default value to return if no matching endpoint is found. If no default is provided, 1241 * an exception will be thrown. 1242 * 1243 * @return mixed The default endpoint, or the $default parameter if no acceptable endpoints are used. 1244 * 1245 * @throws \Exception If no supported endpoint is found and no $default parameter is specified. 1246 */ 1247 public function getDefaultEndpoint($endpointType, array $bindings = null, $default = self::REQUIRED_OPTION) 1248 { 1249 assert(is_string($endpointType)); 1250 1251 $endpoints = $this->getEndpoints($endpointType); 1252 1253 $defaultEndpoint = Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings); 1254 if ($defaultEndpoint !== null) { 1255 return $defaultEndpoint; 1256 } 1257 1258 if ($default === self::REQUIRED_OPTION) { 1259 $loc = $this->location . '[' . var_export($endpointType, true) . ']:'; 1260 throw new \Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.'); 1261 } 1262 1263 return $default; 1264 } 1265 1266 1267 /** 1268 * Retrieve a string which may be localized into many languages. 1269 * 1270 * The default language returned is always 'en'. 1271 * 1272 * @param string $name The name of the option. 1273 * @param mixed $default The default value. If no default is given, and the option isn't found, an exception will 1274 * be thrown. 1275 * 1276 * @return mixed Associative array with language => string pairs, or the provided default value. 1277 * 1278 * @throws \Exception If the translation is not an array or a string, or its index or value are not strings. 1279 */ 1280 public function getLocalizedString($name, $default = self::REQUIRED_OPTION) 1281 { 1282 assert(is_string($name)); 1283 1284 $ret = $this->getValue($name, $default); 1285 if ($ret === $default) { 1286 // the option wasn't found, or it matches the default value. In any case, return this value 1287 return $ret; 1288 } 1289 1290 $loc = $this->location . '[' . var_export($name, true) . ']'; 1291 1292 if (is_string($ret)) { 1293 $ret = ['en' => $ret]; 1294 } 1295 1296 if (!is_array($ret)) { 1297 throw new \Exception($loc . ': Must be an array or a string.'); 1298 } 1299 1300 foreach ($ret as $k => $v) { 1301 if (!is_string($k)) { 1302 throw new \Exception($loc . ': Invalid language code: ' . var_export($k, true)); 1303 } 1304 if (!is_string($v)) { 1305 throw new \Exception($loc . '[' . var_export($v, true) . ']: Must be a string.'); 1306 } 1307 } 1308 1309 return $ret; 1310 } 1311 1312 1313 /** 1314 * Get public key from metadata. 1315 * 1316 * @param string|null $use The purpose this key can be used for. (encryption or signing). 1317 * @param bool $required Whether the public key is required. If this is true, a 1318 * missing key will cause an exception. Default is false. 1319 * @param string $prefix The prefix which should be used when reading from the metadata 1320 * array. Defaults to ''. 1321 * 1322 * @return array Public key data, or empty array if no public key or was found. 1323 * 1324 * @throws \Exception If the certificate or public key cannot be loaded from a file. 1325 * @throws \SimpleSAML\Error\Exception If the file does not contain a valid PEM-encoded certificate, or there is no 1326 * certificate in the metadata. 1327 */ 1328 public function getPublicKeys($use = null, $required = false, $prefix = '') 1329 { 1330 assert(is_bool($required)); 1331 assert(is_string($prefix)); 1332 1333 if ($this->hasValue($prefix . 'keys')) { 1334 $ret = []; 1335 foreach ($this->getArray($prefix . 'keys') as $key) { 1336 if ($use !== null && isset($key[$use]) && !$key[$use]) { 1337 continue; 1338 } 1339 if (isset($key['X509Certificate'])) { 1340 // Strip whitespace from key 1341 $key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']); 1342 } 1343 $ret[] = $key; 1344 } 1345 return $ret; 1346 } elseif ($this->hasValue($prefix . 'certData')) { 1347 $certData = $this->getString($prefix . 'certData'); 1348 $certData = preg_replace('/\s+/', '', $certData); 1349 return [ 1350 [ 1351 'encryption' => true, 1352 'signing' => true, 1353 'type' => 'X509Certificate', 1354 'X509Certificate' => $certData, 1355 ], 1356 ]; 1357 } elseif ($this->hasValue($prefix . 'certificate')) { 1358 $file = $this->getString($prefix . 'certificate'); 1359 $file = Utils\Config::getCertPath($file); 1360 $data = @file_get_contents($file); 1361 1362 if ($data === false) { 1363 throw new \Exception( 1364 $this->location . ': Unable to load certificate/public key from file "' . $file . '".' 1365 ); 1366 } 1367 1368 // extract certificate data (if this is a certificate) 1369 $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; 1370 if (!preg_match($pattern, $data, $matches)) { 1371 throw new \SimpleSAML\Error\Exception( 1372 $this->location . ': Could not find PEM encoded certificate in "' . $file . '".' 1373 ); 1374 } 1375 $certData = preg_replace('/\s+/', '', $matches[1]); 1376 1377 return [ 1378 [ 1379 'encryption' => true, 1380 'signing' => true, 1381 'type' => 'X509Certificate', 1382 'X509Certificate' => $certData, 1383 ], 1384 ]; 1385 } elseif ($required === true) { 1386 throw new \SimpleSAML\Error\Exception($this->location . ': Missing certificate in metadata.'); 1387 } else { 1388 return []; 1389 } 1390 } 1391 1392 /** 1393 * Clear any configuration information cached. 1394 * Allows for configuration files to be changed and reloaded during a given request. Most useful 1395 * when running phpunit tests and needing to alter config.php between test cases 1396 * 1397 * @return void 1398 */ 1399 public static function clearInternalState() 1400 { 1401 self::$configDirs = []; 1402 self::$instance = []; 1403 self::$loadedConfigs = []; 1404 } 1405} 1406