/
authentication.php
364 lines (320 loc) · 15.7 KB
/
authentication.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
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
212
213
214
215
216
217
218
219
220
221
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
<?php
// see: https://scrutinizer-ci.com/g/LimeSurvey/LimeSurvey/issues/master/files/application/controllers/admin/authentication.php?selectedSeverities[0]=10&orderField=path&order=asc&honorSelectedPaths=0
// use LimeSurvey\PluginManager\PluginEvent;
if (!defined('BASEPATH')) {
exit('No direct script access allowed');
}
/*
* LimeSurvey
* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*
*/
/**
* Authentication Controller
*
* This controller performs authentication
*
* @package LimeSurvey
* @subpackage Backend
*
* @method void redirect(string|array $url, boolean $terminate, integer $statusCode)
*/
class Authentication extends Survey_Common_Action
{
/**
* Show login screen and parse login data
* Will redirect or echo json depending on ajax call
* This function is called while accessing the login page: index.php/admin/authentication/sa/login
*/
public function index()
{
/* Set adminlang to the one set in dropdown */
if (Yii::app()->request->getPost('loginlang', 'default') != 'default') {
Yii::app()->session['adminlang'] = Yii::app()->request->getPost('loginlang', 'default');
Yii::app()->setLanguage(Yii::app()->session["adminlang"]);
}
// The page should be shown only for non logged in users
$this->_redirectIfLoggedIn();
// Result can be success, fail or data for template
$result = self::prepareLogin();
$isAjax = isset($_GET['ajax']) && $_GET['ajax'] == 1;
$succeeded = isset($result[0]) && $result[0] == 'success';
$failed = isset($result[0]) && $result[0] == 'failed';
// If Ajax, echo success or failure json
if ($isAjax) {
Yii::import('application.helpers.admin.ajax_helper', true);
if ($succeeded) {
ls\ajax\AjaxHelper::outputSuccess(gT('Successful login'));
return;
} else if ($failed) {
ls\ajax\AjaxHelper::outputError(gT('Incorrect username and/or password!'));
return;
}
}
// If not ajax, redirect to admin startpage or again to login form
else {
if ($succeeded) {
self::doRedirect();
} else if ($failed) {
$message = $result[1];
App()->user->setFlash('error', $message);
App()->getController()->redirect(array('/admin/authentication/sa/login'));
}
}
// Neither success nor failure, meaning no form submission - result = template data from plugin
$aData = $result;
// If for any reason, the plugin bugs, we can't let the user with a blank screen.
$this->_renderWrappedTemplate('authentication', 'login', $aData);
}
/**
* Prepare login and return result
* It checks if the authdb plugin is registered and active
* @return array Either success, failure or plugin data (used in login form)
*/
public static function prepareLogin()
{
$aData = array();
// Plugins, include core plugins, can't be activated by default.
// So after a fresh installation, core plugins are not activated
// They need to be manually loaded.
if (!class_exists('Authdb', false)) {
$plugin = Plugin::model()->findByAttributes(array('name'=>'Authdb'));
if (!$plugin) {
$plugin = new Plugin();
$plugin->name = 'Authdb';
$plugin->active = 1;
$plugin->save();
App()->getPluginManager()->loadPlugin('Authdb', $plugin->id);
} else {
$plugin->active = 1;
$plugin->save();
}
}
// In Authdb, the plugin event "beforeLogin" checks if the url param "onepass" is set
// if yes, it will call AuthPluginBase::setAuthPlugin to set to true the plugin private parameter "_stop", so the form will not be displayed
// @see: application/core/plugins/Authdb/Authdb.php: function beforeLogin()
$beforeLogin = new PluginEvent('beforeLogin');
$beforeLogin->set('identity', new LSUserIdentity('', ''));
App()->getPluginManager()->dispatchEvent($beforeLogin);
/* @var $identity LSUserIdentity */
$identity = $beforeLogin->get('identity'); // Why here?
// If the plugin private parameter "_stop" is false and the login form has not been submitted: render the login form
if (!$beforeLogin->isStopped() && is_null(App()->getRequest()->getPost('login_submit'))) {
// First step: set the value of $aData['defaultAuth']
// This variable will be used to select the default value of the Authentication method selector
// which is shown only if there is more than one plugin auth on...
// @see application/views/admin/authentication/login.php
// First it checks if the current plugin force the authentication default value...
// NB: A plugin SHOULD NOT be able to over pass the configuration file
// @see: http://img.memecdn.com/knees-weak-arms-are-heavy_c_3011277.jpg
if (!is_null($beforeLogin->get('default'))) {
$aData['defaultAuth'] = $beforeLogin->get('default');
} else {
// THen, it checks if the the user set a different default plugin auth in application/config/config.php
// eg: 'config'=>array()'debug'=>2,'debugsql'=>0, 'default_displayed_auth_method'=>'muh_auth_method')
if (App()->getPluginManager()->isPluginActive(Yii::app()->getConfig('default_displayed_auth_method'))) {
$aData['defaultAuth'] = Yii::app()->getConfig('default_displayed_auth_method');
} else {
$aData['defaultAuth'] = 'Authdb';
}
}
// Call the plugin method newLoginForm
// For Authdb: @see: application/core/plugins/Authdb/Authdb.php: function newLoginForm()
$newLoginForm = new PluginEvent('newLoginForm');
App()->getPluginManager()->dispatchEvent($newLoginForm); // inject the HTML of the form inside the private varibale "_content" of the plugin
$aData['summary'] = self::getSummary('logout');
$aData['pluginContent'] = $newLoginForm->getAllContent(); // Retreives the private varibale "_content" , and parse it to $aData['pluginContent'], which will be rendered in application/views/admin/authentication/login.php
} else {
// The form has been submited, or the plugin has been stoped (so normally, the value of login/password are available)
// Handle getting the post and populating the identity there
$authMethod = App()->getRequest()->getPost('authMethod', $identity->plugin); // If form has been submitted, $_POST['authMethod'] is set, else $identity->plugin should be set, ELSE: TODO error
$identity->plugin = $authMethod;
// Call the function afterLoginFormSubmit of the plugin.
// For Authdb, it calls AuthPluginBase::afterLoginFormSubmit()
// which set the plugin's private variables _username and _password with the POST informations if it's a POST request else it does nothing
$event = new PluginEvent('afterLoginFormSubmit');
$event->set('identity', $identity);
App()->getPluginManager()->dispatchEvent($event, array($authMethod));
$identity = $event->get('identity');
// Now authenticate
// This call LSUserIdentity::authenticate() (application/core/LSUserIdentity.php))
// which will call the plugin function newUserSession() (eg: Authdb::newUserSession() )
// TODO: for sake of clarity, the plugin function should be renamed to authenticate().
if ($identity->authenticate()) {
FailedLoginAttempt::model()->deleteAttempts();
App()->user->setState('plugin', $authMethod);
Yii::app()->session['just_logged_in'] = true;
Yii::app()->session['loginsummary'] = self::getSummary();
$event = new PluginEvent('afterSuccessfulLogin');
App()->getPluginManager()->dispatchEvent($event);
return array('success');
} else {
// Failed
$event = new PluginEvent('afterFailedLoginAttempt');
$event->set('identity', $identity);
App()->getPluginManager()->dispatchEvent($event);
$message = $identity->errorMessage;
if (empty($message)) {
// If no message, return a default message
$message = gT('Incorrect username and/or password!');
}
return array('failed', $message);
}
}
return $aData;
}
/**
* Logout user
* @return void
*/
public function logout()
{
/* Adding beforeLogout event */
$beforeLogout = new PluginEvent('beforeLogout');
App()->getPluginManager()->dispatchEvent($beforeLogout);
regenerateCSRFToken();
App()->user->logout();
App()->user->setFlash('loginmessage', gT('Logout successful.'));
/* Adding afterLogout event */
$event = new PluginEvent('afterLogout');
App()->getPluginManager()->dispatchEvent($event);
$this->getController()->redirect(array('/admin/authentication/sa/login'));
}
/**
* Forgot Password screen
* @return void
*/
public function forgotpassword()
{
$this->_redirectIfLoggedIn();
if (!Yii::app()->request->getPost('action')) {
$this->_renderWrappedTemplate('authentication', 'forgotpassword');
} else {
$sUserName = Yii::app()->request->getPost('user');
$sEmailAddr = Yii::app()->request->getPost('email');
$aFields = User::model()->findAllByAttributes(array('users_name' => $sUserName, 'email' => $sEmailAddr));
// Preventing attacker from easily knowing whether the user and email address are valid or not (and slowing down brute force attacks)
usleep(rand(Yii::app()->getConfig("minforgottenpasswordemaildelay"), Yii::app()->getConfig("maxforgottenpasswordemaildelay")));
$aData = [];
if (count($aFields) < 1 || ($aFields[0]['uid'] != 1 && !Permission::model()->hasGlobalPermission('auth_db', 'read', $aFields[0]['uid']))) {
// Wrong or unknown username and/or email. For security reasons, we don't show a fail message
$aData['message'] = '<br>'.gT('If the username and email address is valid and you are allowed to use the internal database authentication a new password has been sent to you.').'<br>';
} else {
$aData['message'] = '<br>'.$this->_sendPasswordEmail($aFields[0]).'</br>';
}
$this->_renderWrappedTemplate('authentication', 'message', $aData);
}
}
public static function runDbUpgrade()
{
// Check if the DB is up to date
if (Yii::app()->db->schema->getTable('{{surveys}}')) {
$sDBVersion = getGlobalSetting('DBVersion');
if ((int) $sDBVersion < Yii::app()->getConfig('dbversionnumber')) {
// Try a silent update first
Yii::app()->loadHelper('update/updatedb');
if (!db_upgrade_all(intval($sDBVersion), true)) {
Yii::app()->getController()->redirect(array('/admin/databaseupdate/sa/db'));
}
}
}
}
/**
* Send the forgot password email
*
* @param CActiveRecord User
*/
private function _sendPasswordEmail( $arUser)
{
$sFrom = Yii::app()->getConfig("siteadminname")." <".Yii::app()->getConfig("siteadminemail").">";
$sTo = $arUser->email;
$sSubject = gT('User data');
$sNewPass = createPassword();
$sSiteName = Yii::app()->getConfig('sitename');
$sSiteAdminBounce = Yii::app()->getConfig('siteadminbounce');
$username = sprintf(gT('Username: %s'), $arUser['users_name']);
$password = sprintf(gT('New password: %s'), $sNewPass);
$body = array();
$body[] = sprintf(gT('Your user data for accessing %s'), Yii::app()->getConfig('sitename'));
$body[] = $username;
$body[] = $password;
$body = implode("\n", $body);
if (SendEmailMessage($body, $sSubject, $sTo, $sFrom, $sSiteName, false, $sSiteAdminBounce)) {
User::updatePassword($arUser['uid'], $sNewPass);
// For security reasons, we don't show a successful message
$sMessage = gT('If the username and email address is valid and you are allowed to use the internal database authentication a new password has been sent to you.');
} else {
$sMessage = gT('Email failed');
}
return $sMessage;
}
/**
* Get's the summary
* @param string $sMethod login|logout
* @param string $sSummary Default summary
* @return string Summary
*/
private static function getSummary($sMethod = 'login', $sSummary = '')
{
if (!empty($sSummary)) {
return $sSummary;
}
switch ($sMethod) {
case 'logout' :
$sSummary = gT('Please log in first.');
break;
case 'login' :
default :
$sSummary = '<br />'.sprintf(gT('Welcome %s!'), Yii::app()->session['full_name']).'<br /> ';
if (!empty(Yii::app()->session['redirect_after_login']) && strpos(Yii::app()->session['redirect_after_login'], 'logout') === false) {
Yii::app()->session['metaHeader'] = '<meta http-equiv="refresh"'
. ' content="1;URL='.Yii::app()->session['redirect_after_login'].'" />';
$sSummary = '<p><font size="1"><i>'.gT('Reloading screen. Please wait.').'</i></font>';
unset(Yii::app()->session['redirect_after_login']);
}
break;
}
return $sSummary;
}
/**
* Redirects a logged in user to the administration page
*/
private function _redirectIfLoggedIn()
{
if (!Yii::app()->user->getIsGuest()) {
$this->runDbUpgrade();
Yii::app()->getController()->redirect(array('/admin'));
}
}
/**
* Redirect after login
* @return void
*/
private static function doRedirect()
{
self::runDbUpgrade();
$returnUrl = App()->user->getReturnUrl(array('/admin'));
Yii::app()->getController()->redirect($returnUrl);
}
/**
* Renders template(s) wrapped in header and footer
*
* @param string $sAction Current action, the folder to fetch views from
* @param string $aViewUrls View url(s)
* @param array $aData Data to be passed on. Optional.
* @return void
*/
protected function _renderWrappedTemplate($sAction = 'authentication', $aViewUrls = array(), $aData = array(), $sRenderFile = false)
{
$aData['display']['menu_bars'] = false;
$aData['language'] = Yii::app()->getLanguage() != Yii::app()->getConfig("defaultlang") ? Yii::app()->getLanguage() : 'default';
parent::_renderWrappedTemplate($sAction, $aViewUrls, $aData, $sRenderFile);
}
}