1<?php 2 3/** 4 * Pluggable Authentication Module 5 */ 6class ElggPAM { 7 8 /** 9 * @var array 10 * @internal 11 * @todo move state into a PAM service 12 */ 13 public static $_handlers = []; 14 15 /** 16 * @var string PAM policy type: user, api or plugin-defined policies 17 */ 18 protected $policy; 19 20 /** 21 * @var array Failure mesages 22 */ 23 protected $messages; 24 25 /** 26 * \ElggPAM constructor 27 * 28 * @param string $policy PAM policy type: user, api, or plugin-defined policies 29 */ 30 public function __construct($policy) { 31 $this->policy = $policy; 32 $this->messages = ['sufficient' => [], 'required' => []]; 33 } 34 35 /** 36 * Authenticate a set of credentials against a policy 37 * This function will process all registered PAM handlers or stop when the first 38 * handler fails. A handler fails by either returning false or throwing an 39 * exception. The advantage of throwing an exception is that it returns a message 40 * that can be passed to the user. The processing order of the handlers is 41 * determined by the order that they were registered. 42 * 43 * If $credentials are provided, the PAM handler should authenticate using the 44 * provided credentials. If not, then credentials should be prompted for or 45 * otherwise retrieved (eg from the HTTP header or $_SESSION). 46 * 47 * @param array $credentials Credentials array dependant on policy type 48 * @return bool 49 */ 50 public function authenticate($credentials = []) { 51 if (!isset(self::$_handlers[$this->policy]) || 52 !is_array(self::$_handlers[$this->policy])) { 53 return false; 54 } 55 56 $authenticated = false; 57 58 foreach (self::$_handlers[$this->policy] as $v) { 59 $handler = $v->handler; 60 if (!is_callable($handler)) { 61 continue; 62 } 63 /* @var callable $handler */ 64 65 $importance = $v->importance; 66 67 try { 68 // Execute the handler 69 // @todo don't assume $handler is a global function 70 $result = call_user_func($handler, $credentials); 71 if ($result) { 72 $authenticated = true; 73 } elseif ($result === false) { 74 if ($importance == 'required') { 75 $this->messages['required'][] = "$handler:failed"; 76 return false; 77 } else { 78 $this->messages['sufficient'][] = "$handler:failed"; 79 } 80 } 81 } catch (Exception $e) { 82 if ($importance == 'required') { 83 $this->messages['required'][] = $e->getMessage(); 84 return false; 85 } else { 86 $this->messages['sufficient'][] = $e->getMessage(); 87 } 88 } 89 } 90 91 return $authenticated; 92 } 93 94 /** 95 * Get a failure message to display to user 96 * 97 * @return string 98 */ 99 public function getFailureMessage() { 100 $message = _elgg_services()->translator->translate('auth:nopams'); 101 if (!empty($this->messages['required'])) { 102 $message = $this->messages['required'][0]; 103 } elseif (!empty($this->messages['sufficient'])) { 104 $message = $this->messages['sufficient'][0]; 105 } 106 107 return _elgg_services()->hooks->trigger('fail', 'auth', $this->messages, $message); 108 } 109} 110