1<?php 2 3/** 4 * DokuWiki Plugin authshibboleth (Auth Component). 5 * 6 * @author Ivan Novakov http://novakov.cz/ 7 * @license http://debug.cz/license/bsd-3-clause BSD 3 Clause 8 * @link https://github.com/ivan-novakov/dokuwiki-shibboleth-auth 9 */ 10 11// must be run within Dokuwiki 12if (! defined('DOKU_INC')) 13 die(); 14 15 16class auth_plugin_authshibboleth extends DokuWiki_Auth_Plugin 17{ 18 19 const CONF_VAR_REMOTE_USER = 'var_remote_user'; 20 21 const CONF_VAR_DISPLAY_NAME = 'var_display_name'; 22 23 const CONF_VAR_MAIL = 'var_mail'; 24 25 const CONF_VAR_SHIB_SESSION_ID = 'var_shib_session_id'; 26 27 const CONF_LOGOUT_HANDLER = 'logout_handler'; 28 29 const CONF_LOGOUT_HANDLER_LOCATION = 'logout_handler_location'; 30 31 const CONF_LOGOUT_RETURN_URL = 'logout_return_url'; 32 33 const CONF_SHIBBOLETH_HANDLER_BASE = 'shibboleth_handler_base'; 34 35 const CONF_DISPLAY_NAME_TPL = 'display_name_tpl'; 36 37 const CONF_USE_DOKUWIKI_SESSION = 'use_dokuwiki_session'; 38 39 const CONF_GROUP_SOURCE_CONFIG = 'group_source_config'; 40 41 const CONF_LOG_ENABLED = 'log_enabled'; 42 43 const CONF_LOG_FILE = 'log_file'; 44 45 const CONF_LOG_TO_PHP = 'log_to_php'; 46 47 const CONF_LOG_PRIORITY = 'log_priority'; 48 49 const CONF_AUTH_USERSFILE = 'auth_usersfile'; 50 51 const USER_UID = 'uid'; 52 53 const USER_NAME = 'name'; 54 55 const USER_MAIL = 'mail'; 56 57 const USER_GRPS = 'grps'; 58 59 const GROUP_SOURCE_TYPE_ENVIRONMENT = 'environment'; 60 61 const GROUP_SOURCE_TYPE_FILE = 'file'; 62 63 const LOG_DEBUG = 7; 64 65 const LOG_INFO = 6; 66 67 const LOG_ERR = 3; 68 69 /** 70 * Global configuration. 71 * @var array 72 */ 73 protected $globalConf = array(); 74 75 /** 76 * Environment variable values ($_SERVER). 77 * @var array 78 */ 79 protected $environment = array(); 80 81 /** 82 * User information as gathered from the environment. 83 * @var array 84 */ 85 protected $userInfo = array(); 86 87 88 /** 89 * Constructor. 90 */ 91 public function __construct() 92 { 93 $this->cando['external'] = true; 94 $this->cando['logoff'] = true; 95 96 if(@is_writable(DOKU_CONF . '/' . $this->getConf(self::CONF_AUTH_USERSFILE))) { 97 $this->cando['getUsers'] = true; 98 $this->cando['getUserCount'] = true; 99 } 100 101 $this->setEnvironment($_SERVER); 102 103 global $conf; 104 $this->setGlobalConfiguration($conf); 105 106 $this->success = true; 107 } 108 109 110 /** 111 * Load all user data 112 * 113 * loads the user file into a datastructure 114 * 115 * @author Andreas Gohr <andi@splitbrain.org> 116 */ 117 protected function _loadUserData() { 118 global $config_cascade; 119 $this->users = array(); 120 if(!file_exists(DOKU_CONF . '/' . $this->getConf(self::CONF_AUTH_USERSFILE))) return; 121 122 $lines = file(DOKU_CONF . '/' . $this->getConf(self::CONF_AUTH_USERSFILE)); 123 foreach($lines as $line) { 124 $line = preg_replace('/#.*$/', '', $line); //ignore comments 125 $line = trim($line); 126 if(empty($line)) continue; 127 128 /* NB: preg_split can be deprecated/replaced with str_getcsv once dokuwiki is min php 5.3 */ 129 $row = $this->_splitUserData($line); 130 $row = str_replace('\\:', ':', $row); 131 $row = str_replace('\\\\', '\\', $row); 132 $groups = array_values(array_filter(explode(",", $row[3]))); 133 $this->users[$row[0]]['name'] = urldecode($row[1]); 134 $this->users[$row[0]]['mail'] = $row[2]; 135 $this->users[$row[0]]['grps'] = $groups; 136 } 137 } 138 139 protected function _splitUserData($line){ 140 // due to a bug in PCRE 6.6, preg_split will fail with the regex we use here 141 // refer github issues 877 & 885 142 if ($this->_pregsplit_safe){ 143 return preg_split('/(?<![^\\\\]\\\\)\:/', $line, 5); // allow for : escaped as \: 144 } 145 $row = array(); 146 $piece = ''; 147 $len = strlen($line); 148 for($i=0; $i<$len; $i++){ 149 if ($line[$i]=='\\'){ 150 $piece .= $line[$i]; 151 $i++; 152 if ($i>=$len) break; 153 } else if ($line[$i]==':'){ 154 $row[] = $piece; 155 $piece = ''; 156 continue; 157 } 158 $piece .= $line[$i]; 159 } 160 $row[] = $piece; 161 return $row; 162 } 163 164 /** 165 * construct a filter pattern 166 * 167 * @param array $filter 168 */ 169 protected function _constructPattern($filter) { 170 $this->_pattern = array(); 171 foreach($filter as $item => $pattern) { 172 $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters 173 } 174 } 175 176 /** 177 * return true if $user + $info match $filter criteria, false otherwise 178 * 179 * @author Chris Smith <chris@jalakai.co.uk> 180 * 181 * @param string $user User login 182 * @param array $info User's userinfo array 183 * @return bool 184 */ 185 protected function _filter($user, $info) { 186 foreach($this->_pattern as $item => $pattern) { 187 if($item == 'user') { 188 if(!preg_match($pattern, $user)) return false; 189 } else if($item == 'grps') { 190 if(!count(preg_grep($pattern, $info['grps']))) return false; 191 } else { 192 if(!preg_match($pattern, $info[$item])) return false; 193 } 194 } 195 return true; 196 } 197 198 /** 199 * Sets the environment variables. 200 * 201 * @param array $environment 202 */ 203 public function setEnvironment(array $environment) 204 { 205 $this->environment = $environment; 206 } 207 208 209 /** 210 * Sets the global configuration variables. 211 * 212 * @param array $globalConf 213 */ 214 public function setGlobalConfiguration(array $globalConf) 215 { 216 $this->globalConf = $globalConf; 217 } 218 219 /** 220 * Return user info 221 * 222 * Returns info about the given user needs to contain 223 * at least these fields: 224 * 225 * name string full name of the user 226 * mail string email addres of the user 227 * grps array list of groups the user is in 228 * 229 * @author Andreas Gohr <andi@splitbrain.org> 230 * @param string $user 231 * @param bool $requireGroups (optional) ignored by this plugin, grps info always supplied 232 * @return array|false 233 */ 234 public function getUserData($user, $requireGroups=true) { 235 if($this->users === null) $this->_loadUserData(); 236 return isset($this->users[$user]) ? $this->users[$user] : false; 237 } 238 239 /** 240 * Return a count of the number of user which meet $filter criteria 241 * 242 * @author Chris Smith <chris@jalakai.co.uk> 243 * 244 * @param array $filter 245 * @return int 246 */ 247 public function getUserCount($filter = array()) { 248 if($this->users === null) $this->_loadUserData(); 249 if(!count($filter)) return count($this->users); 250 $count = 0; 251 $this->_constructPattern($filter); 252 foreach($this->users as $user => $info) { 253 $count += $this->_filter($user, $info); 254 } 255 return $count; 256 } 257 258 /** 259 * Bulk retrieval of user data 260 * 261 * @author Chris Smith <chris@jalakai.co.uk> 262 * 263 * @param int $start index of first user to be returned 264 * @param int $limit max number of users to be returned 265 * @param array $filter array of field/pattern pairs 266 * @return array userinfo (refer getUserData for internal userinfo details) 267 */ 268 public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { 269 if($this->users === null) $this->_loadUserData(); 270 ksort($this->users); 271 $i = 0; 272 $count = 0; 273 $out = array(); 274 $this->_constructPattern($filter); 275 foreach($this->users as $user => $info) { 276 if($this->_filter($user, $info)) { 277 if($i >= $start) { 278 $out[$user] = $info; 279 $count++; 280 if(($limit > 0) && ($count >= $limit)) break; 281 } 282 $i++; 283 } 284 } 285 return $out; 286 } 287 288 /** 289 * {@inheritdoc} 290 * @see DokuWiki_Auth_Plugin::trustExternal() 291 */ 292 public function trustExternal() 293 { 294 $this->debug('Checking for DokuWiki session...'); 295 if ($this->getConf(self::CONF_USE_DOKUWIKI_SESSION) && ($userInfo = $this->loadUserInfoFromSession()) !== null) { 296 $this->log('Loaded user from DokuWiki session'); 297 return; 298 } 299 300 $sessionVarName = $this->getConf(self::CONF_VAR_SHIB_SESSION_ID); 301 $this->debug(sprintf("Checking for Shibboleth session [%s] ...", $sessionVarName)); 302 if ($this->getShibVar($sessionVarName)) { 303 $this->log('Shibboleth session found, trying to authenticate user...'); 304 305 $userId = $this->getShibVar($this->getConf(self::CONF_VAR_REMOTE_USER)); 306 if ($userId) { 307 308 $this->setUserId($userId); 309 $this->setUserDisplayName($this->retrieveUserDisplayName()); 310 $this->setUserMail($this->retrieveUserMail()); 311 $this->setUserGroups($this->retrieveUserGroups()); 312 313 $this->saveUserInfoToSession(); 314 $this->saveGlobalUserInfo(); 315 316 $this->_saveUserInfo(); 317 318 $this->log('Loaded user from environment'); 319 320 return true; 321 } 322 } 323 324 auth_logoff(); 325 return false; 326 } 327 328 329 /** 330 * {@inheritdoc} 331 * @see DokuWiki_Auth_Plugin::logOff() 332 */ 333 public function logOff() 334 { 335 /* 336 * Initiate a logout sequence only, if there is a Shibboleth identity 337 */ 338 if ($this->retrieveUserId()) { 339 $url = $this->getConf(self::CONF_LOGOUT_HANDLER_LOCATION); 340 if (! $url) { 341 $url = $this->createLogoutHandlerLocation(); 342 } 343 344 $this->debug(sprintf("Logout redirect: %s", $url)); 345 346 header('Location: ' . $url); 347 exit(); 348 } 349 } 350 351 352 /** 353 * Saves user info into the session. 354 */ 355 protected function saveUserInfoToSession(array $userInfo = null) 356 { 357 if (! $userInfo) { 358 $userInfo = $this->getUserInfo(); 359 } 360 361 $_SESSION[DOKU_COOKIE]['auth']['user'] = $userInfo['uid']; 362 $_SESSION[DOKU_COOKIE]['auth']['info'] = $userInfo; 363 364 365 } 366 367 368 /** 369 * Loads user info from the session. 370 * 371 * @return array|null 372 */ 373 protected function loadUserInfoFromSession() 374 { 375 if (isset($_SESSION[DOKU_COOKIE]['auth']) && is_array($_SESSION[DOKU_COOKIE]['auth'])) { 376 $authInfo = $_SESSION[DOKU_COOKIE]['auth']; 377 378 if (isset($authInfo['user']) && isset($authInfo['info']) && is_array($authInfo['info'])) { 379 $userInfo = $authInfo['info']; 380 $username = $authInfo['user']; 381 382 $this->setUserInfo($userInfo); 383 $this->saveGlobalUserInfo(); 384 385 return $userInfo; 386 } 387 } 388 389 return null; 390 } 391 392 protected function _saveUserInfo() { 393 $userInfo = $this->getUserInfo(); 394 395 // user mustn't already exist 396 if ($this->getUserData($userInfo['uid']) === false) { 397 // prepare user line 398 $groups = join(',',$userInfo['grps']); 399 $userline = join(':',array($userInfo['uid'], $userInfo['name'], $userInfo['mail'], $groups))."\n"; 400 401 if (io_saveFile(DOKU_CONF . '/' . $this->getConf(self::CONF_AUTH_USERSFILE), $userline, true)) { 402 $this->users[$userInfo['uid']] = compact('name', 'mail', 'grps'); 403 } else { 404 msg('The ' . DOKU_CONF . '/' . $this->getConf(self::CONF_AUTH_USERSFILE) . ' file is not writable. Please inform the Wiki-Admin',-1); 405 } 406 } 407 } 408 409 /** 410 * Sets user info accordingly to the DokuWiki speifics. 411 * 412 * Sets the $USERINFO global variable. Sets the REMOTE_USER variable, if it is not populated with the 413 * username from the Shibboleth environment. Despite having the $USERINFO global array, it seems that 414 * DokuWiki still uses the REMOTE_USER value. 415 * 416 * @param array $userInfo 417 */ 418 protected function saveGlobalUserInfo(array $userInfo = null) 419 { 420 global $USERINFO; 421 422 if (! $userInfo) { 423 $userInfo = $this->getUserInfo(); 424 } 425 426 $USERINFO = $userInfo; 427 $_SERVER['REMOTE_USER'] = $userInfo['uid']; 428 } 429 430 431 /** 432 * Returns the value of global configuration variable. 433 * 434 * @param string $varName 435 * @return mixed|null 436 */ 437 protected function getGlobalConfVar($varName) 438 { 439 if (isset($this->globalConf[$varName])) { 440 return $this->globalConf[$varName]; 441 } 442 443 return null; 444 } 445 446 447 /** 448 * Returns a Shibboleth variable. 449 * 450 * @param string $varName 451 * @param boolean $multivalue 452 * @return string|array|null 453 */ 454 protected function getShibVar($varName, $multivalue = false) 455 { 456 $value = $this->getEnvVar($varName); 457 if ($value && $multivalue) { 458 $value = explode(';', $value); 459 } 460 461 return $value; 462 } 463 464 465 /** 466 * Returns the value of the required environment variable. 467 * 468 * @param string $varName 469 * @return string|null 470 */ 471 protected function getEnvVar($varName) 472 { 473 if (isset($this->environment[$varName])) { 474 return $this->environment[$varName]; 475 } 476 477 return null; 478 } 479 480 481 /** 482 * Extracts user's identity from the environment. 483 * 484 * @return string|null 485 */ 486 protected function retrieveUserId() 487 { 488 return $this->getShibVar($this->getConf(self::CONF_VAR_REMOTE_USER)); 489 } 490 491 492 /** 493 * Extracts user's mail from the environment. 494 * 495 * @return string|null 496 */ 497 protected function retrieveUserMail() 498 { 499 $mails = $this->getShibVar($this->getConf(self::CONF_VAR_MAIL), true); 500 if (count($mails)) { 501 return $mails[0]; 502 } 503 504 return null; 505 } 506 507 508 /** 509 * Extracts user's display name from the environment. 510 * 511 * @return string|null 512 */ 513 protected function retrieveUserDisplayName() 514 { 515 $userDisplayName = null; 516 $tplUserDisplayName = $this->getConf(self::CONF_DISPLAY_NAME_TPL); 517 $userDisplayNameVar = $this->getConf(self::CONF_VAR_DISPLAY_NAME); 518 519 if ($tplUserDisplayName) { 520 $userDisplayName = $this->retrieveUserDisplayNameFromTpl($tplUserDisplayName); 521 } 522 else if ($userDisplayNameVar) { 523 $userDisplayName = $this->getShibVar($userDisplayNameVar); 524 } 525 526 if (! $userDisplayName) { 527 $userDisplayName = $this->getUserId(); 528 } 529 530 return $userDisplayName; 531 } 532 533 534 /** 535 * Resolves the template for the user's real name and returns it. 536 * 537 * @param string $tplUserDisplayName 538 * @return string 539 */ 540 protected function retrieveUserDisplayNameFromTpl($tplUserDisplayName) 541 { 542 $matches = array(); 543 if (preg_match_all('/({([^{}]+)})/', $tplUserDisplayName, $matches)) { 544 $vars = $matches[2]; 545 546 $userName = $tplUserDisplayName; 547 foreach ($vars as $var) { 548 $value = $this->getShibVar($var); 549 if (! $value) { 550 return ''; 551 } 552 $userName = str_replace('{' . $var . '}', $value, $userName); 553 } 554 555 return $userName; 556 } 557 558 return ''; 559 } 560 561 562 /** 563 * Resolves the user's groups. 564 * 565 * @return array 566 */ 567 protected function retrieveUserGroups() 568 { 569 $groups = array(); 570 571 // default groups 572 if (($defaultGroup = $this->getGlobalConfVar('defaultgroup')) !== null) { 573 $groups[] = $defaultGroup; 574 } 575 576 $groupSourceConfig = $this->getConf(self::CONF_GROUP_SOURCE_CONFIG); 577 if (is_array($groupSourceConfig)) { 578 foreach ($groupSourceConfig as $sourceName => $config) { 579 if (! isset($config['type'])) { 580 $this->log(sprintf("Group source '%s' without a type", $sourceName)); 581 continue; 582 } 583 $sourceType = $config['type']; 584 $sourceOptions = array(); 585 if (isset($config['options'])) { 586 $sourceOptions = $config['options']; 587 } 588 589 $sourceGroups = $this->retrieveUserGroupsFromSource($sourceName, $sourceType, $sourceOptions); 590 if (is_array($sourceGroups)) { 591 $groups = array_merge($groups, $sourceGroups); 592 } 593 } 594 } else { 595 $this->log(sprintf("The value of '%s' must be an array", self::CONF_GROUP_SOURCE_CONFIG)); 596 } 597 598 $this->log(sprintf("Resolved groups: %s", implode(', ', $groups))); 599 600 return $groups; 601 } 602 603 604 /** 605 * Resolves the user's groups from different sources. 606 * 607 * @param string $sourceType 608 * @param array $sourceOptions 609 * @return array 610 */ 611 protected function retrieveUserGroupsFromSource($sourceName, $sourceType, array $sourceOptions) 612 { 613 $groups = array(); 614 615 $this->debug(sprintf("Resolving groups from source '%s' (%s)", $sourceName, $sourceType)); 616 617 $handler = 'retrieveUserGroupsFrom' . ucfirst($sourceType); 618 if (! method_exists($this, $handler)) { 619 $this->log(sprintf("Non-existent group source handler '%s'", $handler)); 620 return $groups; 621 } 622 623 try { 624 $sourceGroups = call_user_func_array(array( 625 $this, 626 $handler 627 ), array( 628 $sourceOptions 629 )); 630 } catch (Exception $e) { 631 $this->log(sprintf("Error retrieving groups from source '%s' (%s): %s", $sourceName, $sourceType, $e->getMessage())); 632 return $groups; 633 } 634 635 $this->debug(sprintf("Resolved groups from source '%s' (%s): %s", $sourceName, $sourceType, implode(', ', $sourceGroups))); 636 637 /* 638 * Groups "post-processing" 639 */ 640 foreach ($sourceGroups as $group) { 641 if (isset($sourceOptions['map'])) { 642 $map = $sourceOptions['map']; 643 if (isset($map[$group])) { 644 $group = $map[$group]; 645 } 646 } 647 648 if (isset($sourceOptions['prefix'])) { 649 $group = $sourceOptions['prefix'] . $group; 650 } 651 652 $groups[] = $group; 653 } 654 655 return $groups; 656 } 657 658 659 /** 660 * Resolves user's groups from the environment variables. 661 * 662 * @param array $options 663 * @throws RuntimeException 664 * @return array 665 */ 666 protected function retrieveUserGroupsFromEnvironment(array $options) 667 { 668 $groups = array(); 669 670 if (! isset($options['source_attribute'])) { 671 throw new RuntimeException('The required "source_attribute" option not set'); 672 } 673 $sourceAttributeName = $options['source_attribute']; 674 675 $values = $this->getShibVar($sourceAttributeName, true); 676 if (null !== $values) { 677 foreach ($values as $value) { 678 679 $groups[] = $value; 680 } 681 } 682 683 return $groups; 684 } 685 686 687 /** 688 * Resolves user's groups from a file. 689 * 690 * @param array $options 691 * @throws RuntimeException 692 * @return array 693 */ 694 protected function retrieveUserGroupsFromFile(array $options) 695 { 696 $groups = array(); 697 698 $userId = $this->getUserId(); 699 if (! $userId) { 700 throw new RuntimeException('No user identity'); 701 } 702 703 if (! isset($options['path'])) { 704 throw new RuntimeException('The required "path" option not set'); 705 } 706 707 $path = $options['path']; 708 if (! file_exists($path)) { 709 throw new RuntimeException(sprintf("Non-existent file '%s'", $path)); 710 } 711 712 if (! is_readable($path)) { 713 throw new RuntimeException(sprintf("File '%s' not readable", $path)); 714 } 715 716 $sourceGroups = require $path; 717 if (! is_array($sourceGroups)) { 718 throw new RuntimeException(sprintf("Invalid group format in file '%s'", $path)); 719 } 720 721 foreach ($sourceGroups as $groupName => $members) { 722 if (in_array($userId, $members)) { 723 $groups[] = $groupName; 724 } 725 } 726 727 return $groups; 728 } 729 730 731 /** 732 * Returns the user ID (user's identity value). 733 * 734 * @return string|null 735 */ 736 protected function getUserId() 737 { 738 return $this->getUserVar(self::USER_UID); 739 } 740 741 742 /** 743 * Sets the user ID. 744 * 745 * @param string $userId 746 */ 747 protected function setUserId($userId) 748 { 749 $this->setUserVar(self::USER_UID, $userId); 750 } 751 752 753 /** 754 * Returns the user's display name. 755 * 756 * @return string|null 757 */ 758 protected function getUserDisplayName() 759 { 760 return $this->getUserVar(self::USER_NAME); 761 } 762 763 764 /** 765 * Sets the user's display name. 766 * 767 * @param string $userDisplayName 768 */ 769 protected function setUserDisplayName($userDisplayName) 770 { 771 $this->setUserVar(self::USER_NAME, $userDisplayName); 772 } 773 774 775 /** 776 * Returns the user's mail. 777 * 778 * @return string|null 779 */ 780 protected function getUserMail() 781 { 782 return $this->getUserVar(self::USER_MAIL); 783 } 784 785 786 /** 787 * Sets the user's mail. 788 * 789 * @param string $mail 790 */ 791 protected function setUserMail($mail) 792 { 793 $this->setUserVar(self::USER_MAIL, $mail); 794 } 795 796 797 /** 798 * Returns the list of user's groups. 799 * 800 * @return array 801 */ 802 protected function getUserGroups() 803 { 804 return $this->getUserVar(self::USER_GRPS); 805 } 806 807 808 /** 809 * Sets the user's groups. 810 * 811 * @param array $groups 812 */ 813 protected function setUserGroups(array $groups) 814 { 815 $this->setUserVar(self::USER_GRPS, $groups); 816 } 817 818 819 /** 820 * Sets a specific user variable value. 821 * 822 * @param string $varName 823 * @param mixed $varValue 824 */ 825 protected function setUserVar($varName, $varValue) 826 { 827 $this->userInfo[$varName] = $varValue; 828 } 829 830 831 /** 832 * Returns a specific user variable value. 833 * 834 * @param string $varName 835 * @return mixed|null 836 */ 837 protected function getUserVar($varName) 838 { 839 if (isset($this->userInfo[$varName])) { 840 return $this->userInfo[$varName]; 841 } 842 843 return null; 844 } 845 846 847 /** 848 * Sets all the user info at once. 849 * 850 * @param array $userInfo 851 */ 852 protected function setUserInfo(array $userInfo) 853 { 854 $this->userInfo = $userInfo; 855 } 856 857 858 /** 859 * Returns all the user info. 860 * 861 * @return array 862 */ 863 protected function getUserInfo() 864 { 865 return $this->userInfo; 866 } 867 868 869 /** 870 * Build a logout handler URL. 871 * 872 * @param string $returnUrl 873 * @param string $handlerName 874 * @return string 875 */ 876 protected function createLogoutHandlerLocation($returnUrl = NULL, $handlerName = 'Logout') 877 { 878 if (! $returnUrl) { 879 if (isset($_SERVER['HTTP_REFERER']) && isset($_SERVER['HTTP_REFERER'])) { 880 $returnUrl = $_SERVER['HTTP_REFERER']; 881 } else { 882 $returnUrl = $this->getConf(self::CONF_LOGOUT_RETURN_URL); 883 } 884 } 885 886 if (! $handlerName) { 887 $handlerName = $this->getConf(self::CONF_LOGOUT_HANDLER); 888 } 889 890 return sprintf("https://%s%s%s?return=%s", $_SERVER['HTTP_HOST'], $this->getConf(self::CONF_SHIBBOLETH_HANDLER_BASE), $handlerName, $returnUrl); 891 } 892 893 894 /** 895 * Logs a debug message. 896 * 897 * @param string $message 898 */ 899 protected function debug($message) 900 { 901 $this->log($message, self::LOG_DEBUG); 902 } 903 904 905 /** 906 * Logs an error message. 907 * 908 * @param string $message 909 */ 910 protected function err($message) 911 { 912 $this->log($message, self::LOG_ERR); 913 } 914 915 916 /** 917 * Log a message. 918 * 919 * @param mixed $message 920 * @param integer $priority 921 */ 922 protected function log($message, $priority = self::LOG_INFO) 923 { 924 $message = $this->logFormatMessage($message, $priority); 925 926 if ($this->getConf(self::CONF_LOG_ENABLED) && $priority <= $this->getConf(self::CONF_LOG_PRIORITY)) { 927 if ($this->getConf(self::CONF_LOG_TO_PHP)) { 928 error_log($message); 929 } 930 931 $logFile = $this->getConf(self::CONF_LOG_FILE); 932 if ($logFile) { 933 $flags = null; 934 if (file_exists($logFile)) { 935 if (! is_writable($logFile)) { 936 $this->debug(sprintf("Log file '%s' not writable", $logFile)); 937 return; 938 } 939 $flags = FILE_APPEND; 940 } 941 942 $message = sprintf("[%s]: %s\n", date('c', time()), $message); 943 if (false === file_put_contents($logFile, $message, $flags)) { 944 $this->debug(sprintf("Error writing to log file '%s'", $logFile)); 945 } 946 } elseif (! $this->getConf(self::CONF_LOG_TO_PHP)) { 947 $this->debug('Log enabled, but log file not set'); 948 } 949 } 950 } 951 952 953 /** 954 * Formats a log message. 955 * 956 * @param mixed $message 957 * @param integer $priority 958 * @return string 959 */ 960 protected function logFormatMessage($message, $priority) 961 { 962 if (! is_scalar($message)) { 963 $message = print_r($message, true); 964 } 965 966 $userId = $this->getUserId(); 967 if (! $userId) { 968 $userId = 'unknown'; 969 } 970 return sprintf("(%d) [%s/%s] %s [%s]", $priority, $userId, $_SERVER['REMOTE_ADDR'], $message, $_SERVER['REQUEST_URI']); 971 } 972} 973