Newer
Older
<?php
require(INCLUDE_DIR.'class.ostsession.php');
require(INCLUDE_DIR.'class.usersession.php');
class AuthenticatedUser {
// How the user was authenticated
var $backend;
// Get basic information
function getId() {}
function getUsername() {}
}
interface AuthDirectorySearch {
/**
* Indicates if the backend can be used to search for user information.
* Lookup is performed to find user information based on a unique
* identifier.
*/
function lookup($id);
/**
* Indicates if the backend supports searching for usernames. This is
* distinct from information lookup in that lookup is intended to lookup
* information based on a unique identifier
*/
function search($query);
}
/**
* Authentication backend
*
* Authentication provides the basis of abstracting the link between the
* login page with a username and password and the staff member,
* administrator, or client using the system.
*
* The system works by allowing the AUTH_BACKENDS setting from
* ost-config.php to determine the list of authentication backends or
* providers and also specify the order they should be evaluated in.
*
* The authentication backend should define a authenticate() method which
* receives a username and optional password. If the authentication
* succeeds, an instance deriving from <User> should be returned.
*/
class AuthenticationBackend {
static private $registry = array();
static $name;
static $id;
/* static */
static function register($class) {
if (is_string($class))
$class = new $class();
// XXX: Raise error if $class::id is already in the registry
static::$registry[$class::$id] = $class;
}
static function allRegistered() {
return static::$registry;
}
static function getBackend($id) {
return static::$registry[$id];
}
function process($username, $password=null, &$errors) {
if (!$username)
return false;
$backend = static::_getAllowedBackends($username);
foreach (static::$registry as $bk) {
if ($backend && $bk->supportsAuthentication() && $bk::$id != $backend)
// User cannot be authenticated against this backend
continue;
// All backends are queried here, even if they don't support
// authentication so that extensions like lockouts and audits
// can be supported.
$result = $bk->authenticate($username, $password);
if ($result instanceof AuthenticatedUser) {
static::_login($result, $username, $bk);
$result->backend = $bk;
return $result;
}
// TODO: Handle permission denied, for instance
elseif ($result instanceof AccessDenied) {
$errors['err'] = $result->reason;
break;
}
}
$info = array('username'=>$username, 'password'=>$password);
Signal::send('auth.login.failed', null, $info);
}
function singleSignOn(&$errors) {
global $ost;
foreach (static::$registry as $bk) {
// All backends are queried here, even if they don't support
// authentication so that extensions like lockouts and audits
// can be supported.
$result = $bk->signOn();
if ($result instanceof AuthenticatedUser) {
// Ensure staff members are allowed to be authenticated
// against this backend
if ($result instanceof Staff
&& !static::_isBackendAllowed($result, $bk))
continue;
static::_login($result, $result->getUserName(), $bk);
$result->backend = $bk;
return $result;
}
// TODO: Handle permission denied, for instance
elseif ($result instanceof AccessDenied) {
$errors['err'] = $result->reason;
break;
}
}
}
static function searchUsers($query) {
$users = array();
foreach (static::$registry as $bk) {
if ($bk instanceof AuthDirectorySearch) {
$users += $bk->search($query);
}
}
return $users;
}
function _isBackendAllowed($staff, $bk) {
$sql = 'SELECT backend FROM '.STAFF_TABLE
.' WHERE staff_id='.db_input($staff->getId());
$backend = db_result(db_query($sql));
return !$backend || strcasecmp($bk::$id, $backend) === 0;
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
}
function _getAllowedBackends($username) {
$username = trim($_POST['userid']);
$sql = 'SELECT backend FROM '.STAFF_TABLE
.' WHERE username='.db_input($username)
.' OR email='.db_input($username);
return db_result(db_query($sql));
}
function _login($user, $username, $bk) {
global $ost;
if ($user instanceof Staff) {
//Log debug info.
$ost->logDebug('Staff login',
sprintf("%s logged in [%s], via %s", $user->getUserName(),
$_SERVER['REMOTE_ADDR'], get_class($bk))); //Debug.
$sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '
.' WHERE staff_id='.db_input($user->getId());
db_query($sql);
//Now set session crap and lets roll baby!
$_SESSION['_staff'] = array(); //clear.
$_SESSION['_staff']['userID'] = $username;
$user->refreshSession(); //set the hash.
$_SESSION['TZ_OFFSET'] = $user->getTZoffset();
$_SESSION['TZ_DST'] = $user->observeDaylight();
}
//Regenerate session id.
$sid = session_id(); //Current id
session_regenerate_id(true);
// Destroy old session ID - needed for PHP version < 5.1.0
// DELME: remove when we move to php 5.3 as min. requirement.
if(($session=$ost->getSession()) && is_object($session)
&& $sid!=session_id())
$session->destroy($sid);
Signal::send('auth.login.succeeded', $user);
$user->cancelResetTokens();
}
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
* Fetches the friendly name of the backend
*/
function getName() {
return static::$name;
}
/**
* Indicates if the backed supports authentication. Useful if the
* backend is used for logging or lockout only
*/
function supportsAuthentication() {
return true;
}
/**
* Indicates if the backend supports changing a user's password. This
* would be done in two fashions. Either the currently-logged in user
* want to change its own password or a user requests to have their
* password reset. This requires an administrative privilege which this
* backend might not possess, so it's defined in supportsPasswordReset()
*/
function supportsPasswordChange() {
return false;
}
function supportsPasswordReset() {
return false;
}
/* abstract */
function authenticate($username, $password) {
return false;
}
/* abstract */
function signOn() {
return false;
}
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
}
class RemoteAuthenticationBackend {
var $create_unknown_user = false;
}
/**
* This will be an exception in later versions of PHP
*/
class AccessDenied {
function AccessDenied() {
call_user_func_array(array($this, '__construct'), func_get_args());
}
function __construct($reason) {
$this->reason = $reason;
}
}
/**
* Simple authentication backend which will lock the login form after a
* configurable number of attempts
*/
class AuthLockoutBackend extends AuthenticationBackend {
function authenticate($username, $password=null) {
global $cfg, $ost;
if($_SESSION['_staff']['laststrike']) {
if((time()-$_SESSION['_staff']['laststrike'])<$cfg->getStaffLoginTimeout()) {
$_SESSION['_staff']['laststrike'] = time(); //reset timer.
return new AccessDenied('Max. failed login attempts reached');
} else { //Timeout is over.
//Reset the counter for next round of attempts after the timeout.
$_SESSION['_staff']['laststrike']=null;
$_SESSION['_staff']['strikes']=0;
}
}
$_SESSION['_staff']['strikes']+=1;
if($_SESSION['_staff']['strikes']>$cfg->getStaffMaxLogins()) {
$_SESSION['_staff']['laststrike']=time();
$alert='Excessive login attempts by a staff member?'."\n".
'Username: '.$username."\n"
.'IP: '.$_SERVER['REMOTE_ADDR']."\n"
.'TIME: '.date('M j, Y, g:i a T')."\n\n"
.'Attempts #'.$_SESSION['_staff']['strikes']."\n"
.'Timeout: '.($cfg->getStaffLoginTimeout()/60)." minutes \n\n";
$ost->logWarning('Excessive login attempts ('.$username.')', $alert,
$cfg->alertONLoginError());
return new AccessDenied('Forgot your login info? Contact Admin.');
//Log every other failed login attempt as a warning.
} elseif($_SESSION['_staff']['strikes']%2==0) {
$alert='Username: '.$username."\n"
.'IP: '.$_SERVER['REMOTE_ADDR']."\n"
.'TIME: '.date('M j, Y, g:i a T')."\n\n"
.'Attempts #'.$_SESSION['_staff']['strikes'];
$ost->logWarning('Failed staff login attempt ('.$username.')', $alert, false);
}
}
function supportsAuthentication() {
return false;
}
}
AuthenticationBackend::register(AuthLockoutBackend);
class osTicketAuthentication extends AuthenticationBackend {
static $name = "Local Authenication";
static $id = "local";
function authenticate($username, $password) {
if (($user = new StaffSession($username)) && $user->getId() &&
$user->check_passwd($password)) {
//update last login && password reset stuff.
$sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() ';
if($user->isPasswdResetDue() && !$user->isAdmin())
$sql.=',change_passwd=1';
$sql.=' WHERE staff_id='.db_input($user->getId());
db_query($sql);
return $user;
}
}
}
AuthenticationBackend::register(osTicketAuthentication);
?>