1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Authentication
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * Authentication class, provides an interface for the Joomla authentication system
14 *
15 * @since 11.1
16 */
17 class JAuthentication extends JObject
18 {
19 // Shared success status
20 /**
21 * This is the status code returned when the authentication is success (permit login)
22 * @const STATUS_SUCCESS successful response
23 * @since 11.2
24 */
25 const STATUS_SUCCESS = 1;
26
27 // These are for authentication purposes (username and password is valid)
28 /**
29 * Status to indicate cancellation of authentication (unused)
30 * @const STATUS_CANCEL cancelled request (unused)
31 * @since 11.2
32 */
33 const STATUS_CANCEL = 2;
34
35 /**
36 * This is the status code returned when the authentication failed (prevent login if no success)
37 * @const STATUS_FAILURE failed request
38 * @since 11.2
39 */
40 const STATUS_FAILURE = 4;
41
42 // These are for authorisation purposes (can the user login)
43 /**
44 * This is the status code returned when the account has expired (prevent login)
45 * @const STATUS_EXPIRED an expired account (will prevent login)
46 * @since 11.2
47 */
48 const STATUS_EXPIRED = 8;
49
50 /**
51 * This is the status code returned when the account has been denied (prevent login)
52 * @const STATUS_DENIED denied request (will prevent login)
53 * @since 11.2
54 */
55 const STATUS_DENIED = 16;
56
57 /**
58 * This is the status code returned when the account doesn't exist (not an error)
59 * @const STATUS_UNKNOWN unknown account (won't permit or prevent login)
60 * @since 11.2
61 */
62 const STATUS_UNKNOWN = 32;
63
64 /**
65 * An array of Observer objects to notify
66 *
67 * @var array
68 * @since 12.1
69 */
70 protected $observers = array();
71
72 /**
73 * The state of the observable object
74 *
75 * @var mixed
76 * @since 12.1
77 */
78 protected $state = null;
79
80 /**
81 * A multi dimensional array of [function][] = key for observers
82 *
83 * @var array
84 * @since 12.1
85 */
86 protected $methods = array();
87
88 /**
89 * @var JAuthentication JAuthentication instances container.
90 * @since 11.3
91 */
92 protected static $instance;
93
94 /**
95 * Constructor
96 *
97 * @since 11.1
98 */
99 public function __construct()
100 {
101 $isLoaded = JPluginHelper::importPlugin('authentication');
102
103 if (!$isLoaded)
104 {
105 JLog::add(JText::_('JLIB_USER_ERROR_AUTHENTICATION_LIBRARIES'), JLog::WARNING, 'jerror');
106 }
107 }
108
109 /**
110 * Returns the global authentication object, only creating it
111 * if it doesn't already exist.
112 *
113 * @return JAuthentication The global JAuthentication object
114 *
115 * @since 11.1
116 */
117 public static function getInstance()
118 {
119 if (empty(self::$instance))
120 {
121 self::$instance = new JAuthentication;
122 }
123
124 return self::$instance;
125 }
126
127 /**
128 * Get the state of the JAuthentication object
129 *
130 * @return mixed The state of the object.
131 *
132 * @since 11.1
133 */
134 public function getState()
135 {
136 return $this->state;
137 }
138
139 /**
140 * Attach an observer object
141 *
142 * @param object $observer An observer object to attach
143 *
144 * @return void
145 *
146 * @since 11.1
147 */
148 public function attach($observer)
149 {
150 if (is_array($observer))
151 {
152 if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
153 {
154 return;
155 }
156
157 // Make sure we haven't already attached this array as an observer
158 foreach ($this->observers as $check)
159 {
160 if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
161 {
162 return;
163 }
164 }
165
166 $this->observers[] = $observer;
167 end($this->observers);
168 $methods = array($observer['event']);
169 }
170 else
171 {
172 if (!($observer instanceof JAuthentication))
173 {
174 return;
175 }
176
177 // Make sure we haven't already attached this object as an observer
178 $class = get_class($observer);
179
180 foreach ($this->observers as $check)
181 {
182 if ($check instanceof $class)
183 {
184 return;
185 }
186 }
187
188 $this->observers[] = $observer;
189 $methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
190 }
191
192 $key = key($this->observers);
193
194 foreach ($methods as $method)
195 {
196 $method = strtolower($method);
197
198 if (!isset($this->methods[$method]))
199 {
200 $this->methods[$method] = array();
201 }
202
203 $this->methods[$method][] = $key;
204 }
205 }
206
207 /**
208 * Detach an observer object
209 *
210 * @param object $observer An observer object to detach.
211 *
212 * @return boolean True if the observer object was detached.
213 *
214 * @since 11.1
215 */
216 public function detach($observer)
217 {
218 $retval = false;
219
220 $key = array_search($observer, $this->observers);
221
222 if ($key !== false)
223 {
224 unset($this->observers[$key]);
225 $retval = true;
226
227 foreach ($this->methods as &$method)
228 {
229 $k = array_search($key, $method);
230
231 if ($k !== false)
232 {
233 unset($method[$k]);
234 }
235 }
236 }
237
238 return $retval;
239 }
240
241 /**
242 * Finds out if a set of login credentials are valid by asking all observing
243 * objects to run their respective authentication routines.
244 *
245 * @param array $credentials Array holding the user credentials.
246 * @param array $options Array holding user options.
247 *
248 * @return JAuthenticationResponse Response object with status variable filled in for last plugin or first successful plugin.
249 *
250 * @see JAuthenticationResponse
251 * @since 11.1
252 */
253 public function authenticate($credentials, $options = array())
254 {
255 // Get plugins
256 $plugins = JPluginHelper::getPlugin('authentication');
257
258 // Create authentication response
259 $response = new JAuthenticationResponse;
260
261 /*
262 * Loop through the plugins and check if the credentials can be used to authenticate
263 * the user
264 *
265 * Any errors raised in the plugin should be returned via the JAuthenticationResponse
266 * and handled appropriately.
267 */
268 foreach ($plugins as $plugin)
269 {
270 $className = 'plg' . $plugin->type . $plugin->name;
271
272 if (class_exists($className))
273 {
274 $plugin = new $className($this, (array) $plugin);
275 }
276 else
277 {
278 // Bail here if the plugin can't be created
279 JLog::add(JText::sprintf('JLIB_USER_ERROR_AUTHENTICATION_FAILED_LOAD_PLUGIN', $className), JLog::WARNING, 'jerror');
280 continue;
281 }
282
283 // Try to authenticate
284 $plugin->onUserAuthenticate($credentials, $options, $response);
285
286 // If authentication is successful break out of the loop
287 if ($response->status === self::STATUS_SUCCESS)
288 {
289 if (empty($response->type))
290 {
291 $response->type = isset($plugin->_name) ? $plugin->_name : $plugin->name;
292 }
293
294 break;
295 }
296 }
297
298 if (empty($response->username))
299 {
300 $response->username = $credentials['username'];
301 }
302
303 if (empty($response->fullname))
304 {
305 $response->fullname = $credentials['username'];
306 }
307
308 if (empty($response->password) && isset($credentials['password']))
309 {
310 $response->password = $credentials['password'];
311 }
312
313 return $response;
314 }
315
316 /**
317 * Authorises that a particular user should be able to login
318 *
319 * @param JAuthenticationResponse $response response including username of the user to authorise
320 * @param array $options list of options
321 *
322 * @return JAuthenticationResponse[] Array of authentication response objects
323 *
324 * @since 11.2
325 */
326 public static function authorise($response, $options = array())
327 {
328 // Get plugins in case they haven't been imported already
329 JPluginHelper::importPlugin('user');
330
331 JPluginHelper::importPlugin('authentication');
332 $dispatcher = JEventDispatcher::getInstance();
333 $results = $dispatcher->trigger('onUserAuthorisation', array($response, $options));
334
335 return $results;
336 }
337 }
338