Skip to content

Commit 635db42

Browse files
author
Jan Was
committed
finished porting one time password feature after moving some UsrModule properties to behaviors; reverted adding a getter for user component; cleaned up
1 parent 5d55b84 commit 635db42

19 files changed

+207
-182
lines changed

README.md

+33-32
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ Remember to invalidate the email if it changes in the save() method from the Edi
8686

8787
This interface allows password reset with optional tracking of used passwords. This allows to detect expired passwords and avoid reusing old passwords by users.
8888

89+
See the ExpiredPasswordBehavior description below.
90+
8991
## Hybridauth
9092

9193
This interface allows finding local identity associated with a remote one (from an external social site) and creating such associations.
@@ -94,71 +96,70 @@ This interface allows finding local identity associated with a remote one (from
9496

9597
This interface allow saving and retrieving a secret used to generate one time passwords. Also, last used password and counter used to generate last password are saved and retrieve to protect against reply attacks.
9698

99+
See the OneTimePasswordFormBehavior description below.
100+
97101
## Profile Pictures
98102

99103
Allows users to upload a profile picture. The example identity uses [Gravatar](http://gravatar.com/) to provide a default picture.
100104

101-
# Custom behaviors
105+
## Managable
106+
107+
Allows to manage users:
108+
109+
* update their profiles (and pictures)
110+
* change passwords
111+
* assign authorization roles
112+
* activate/disable and mark email as verified
113+
* see details as timestamps of account creation, last profile update and last visit
114+
115+
# Custom login behaviors
116+
117+
The login action can be extended by attaching custom behaviors to the LoginForm. This is done by configuring the UsrModule.loginFormBehaviors property.
102118

103-
Do akcji logowania można podpinać dodatkowe Behaviory za pomocą zdefiniowania właściwości loginFormBehaviors w konfiguracji modułu. Pozwalają one na dodanie własnej logiki do operacji logowania uzytkowników.
119+
There are two such behaviors provided by yii-usr module:
104120

105-
## Moduł dostarcza 2 wbudowane behaviory:
106121
* ExpiredPasswordBehavior
107122
* OneTimePasswordFormBehavior
108123

109124
### ExpiredPasswordBehavior
110125

111-
Obsługuje zachowanie pozwalające na wymuszenie na użytkownikach zmiany hasła co pewien czas.
126+
Validates if current password has expired and forces the users to change it before logging in.
112127

113-
Dodatkowe parametry:
128+
Options:
114129

115-
* passwordTimeout - pozwala na zdefiniowanie czasu co jaki powinno zostać zmienione hasło
130+
* passwordTimeout - number of days after which user is requred to reset his password after logging in
116131

117132
### OneTimePasswordFormBehavior
118133

119-
Obsługuję obsługe jednorazowych haseł.
134+
Two step authentication using one time passwords.
120135

121-
Dodatkowe parametry:
136+
Options:
122137

123-
* authenticator -
124-
* required - boolean Should the user be allowed to log in even if a secret hasn't been generated yet. This only makes sense when mode is 'counter', secrets are generated when registering users and a code is sent via email.
125-
* timeout - int DEFAULT: -1 Number of seconds for how long is the last verified code valid
126-
* mode - one of otp mode values: 'otp', 'time', 'counter', 'none' DEFAULT: 'none'. If set to 'time' or 'counter' two step authentication is enabled using one time passwords
138+
* authenticator - if null, set to a new instance of GoogleAuthenticator class.
139+
* mode - if set to OneTimePasswordFormBehavior::OTP_TIME or OneTimePasswordFormBehavior::OTP_COUNTER, two step authentication is enabled using one time passwords. Time mode uses codes generated using current time and requires the user to use an external application, like Google Authenticator on Android. Counter mode uses codes generated using a sequence and sends them to user's email.
140+
* required - should the user be allowed to log in even if a secret hasn't been generated yet (is null). This only makes sense when mode is 'counter', secrets are generated when registering users and a code is sent via email.
141+
* timeout - Number of seconds for how long is the last verified code valid.
142+
143+
## Example usage
127144

128-
## Przykładowa instalacja behaviorów
129145
~~~php
130146
'loginFormBehaviors' => array(
131147
'expiredPasswordBehavior' => array(
132-
'class' => 'usr.components.ExpiredPasswordBehavior',
148+
'class' => 'ExpiredPasswordBehavior',
133149
'passwordTimeout' => 10,
134150
),
135151
'oneTimePasswordBehavior' => array(
136152
'class' => 'OneTimePasswordFormBehavior',
137153
'oneTimePasswordConfig' => array(
138-
'authenticator' => $this->googleAuthenticator,
139-
'mode' => 'time',
154+
'mode' => OneTimePasswordFormBehavior::OTP_TIME,
140155
'required' => true,
141156
'timeout' => 123,
142157
),
143-
'controller' => Yii::app()->controller,
144-
),
145-
'myCustomBehavior' => array(
146-
'class' => 'application.components.MyCustomBehavior',
147-
'customBehaviorConf' => 'some value',
148158
),
159+
// ... other behaviors
149160
),
150161
~~~
151162

152-
## Managable
153-
154-
Allows to manage users:
155-
156-
* update their profiles (and pictures)
157-
* change passwords
158-
* assign authorization roles
159-
* activate/disable and mark email as verified
160-
* see details as timestamps of account creation, last profile update and last visit
161-
162163
# User model example
163164

164165
A sample ExampleUserIdentity and corresponding ExampleUser and ExampleUserUsedPassword models along with database migrations are provided respectively in the 'components', 'models' and 'migrations' folders.
@@ -201,7 +202,7 @@ Feel free to send new and updated translations to the author.
201202

202203
# Usage scenarios
203204

204-
Varios scenarios can be created by enabling or disabling following features:
205+
Various scenarios can be created by enabling or disabling following features:
205206

206207
* registration
207208
* email verification

UsrModule.php

+20-31
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
*
3636
* # Usage scenarios
3737
*
38-
* Varios scenarios can be created by enabling or disabling following features:
38+
* Various scenarios can be created by enabling or disabling following features:
3939
*
4040
* * registration
4141
* * email verification
@@ -82,12 +82,6 @@
8282
*/
8383
class UsrModule extends CWebModule
8484
{
85-
const OTP_SECRET_PREFIX = 'UsrModule.oneTimePassword.';
86-
const OTP_COOKIE = 'otp';
87-
const OTP_NONE = 'none';
88-
const OTP_TIME = 'time';
89-
const OTP_COUNTER = 'counter';
90-
9185
/**
9286
* @var boolean Is new user registration enabled.
9387
*/
@@ -104,11 +98,6 @@ class UsrModule extends CWebModule
10498
* @var integer For how long the user will be logged in without any activity, in seconds. Defaults to 3600*24*30 seconds (30 days).
10599
*/
106100
public $rememberMeDuration = 2592000;
107-
/**
108-
* @var integer Timeout in days after which user is requred to reset his password after logging in.
109-
* If not null, the user identity class must implement IPasswordHistoryIdentity interface.
110-
*/
111-
public $passwordTimeout;
112101
/**
113102
* @var array Set of rules to measure the password strength when choosing new password in the registration or recovery forms.
114103
* Rules should NOT include attribute name, it will be added when they are used.
@@ -128,7 +117,6 @@ class UsrModule extends CWebModule
128117
public $pictureUploadRules;
129118
/**
130119
* @var string Class name of user identity object used to authenticate user.
131-
* Must implement the IPasswordHistoryIdentity interface if passwordTimeout is set.
132120
*/
133121
public $userIdentityClass = 'CUserIdentity';
134122
/**
@@ -156,7 +144,8 @@ class UsrModule extends CWebModule
156144
*/
157145
public $submitButtonCssClass = '';
158146
/**
159-
* @var array configuration for PHPMailer, values which are arrays will trigger methods for each value instead of setting properties.
147+
* @var array configuration for PHPMailer, values which are arrays will trigger methods
148+
* for each value instead of setting properties.
160149
* For a full reference, please resolve to PHPMailer documentation.
161150
*/
162151
public $mailerConfig = array(
@@ -191,20 +180,29 @@ class UsrModule extends CWebModule
191180
*/
192181
public $dicewareExtraChar = false;
193182
/**
194-
* @var array Available Hybridauth providers, indexed by name, defined as array('enabled'=>true|false, 'keys'=>array('id'=>string, 'key'=>string, 'secret'=>string), 'scope'=>string)
183+
* @var array Available Hybridauth providers, indexed by name, defined as
184+
* array(
185+
* 'enabled'=>true|false,
186+
* 'keys'=>array('id'=>string, 'key'=>string, 'secret'=>string),
187+
* 'scope'=>string,
188+
* )
195189
* @see http://hybridauth.sourceforge.net/userguide.html
196190
*/
197191
public $hybridauthProviders = array();
198192
/**
199-
* @var array list of identity attribute names that should be passed to UserIdentity::find() to find a local identity matching a remote one.
200-
* If one is found, user must authorize to associate it. If none has been found, a new local identity is automatically registered.
193+
* @var array list of identity attribute names that should be passed to UserIdentity::find()
194+
* to find a local identity matching a remote one.
195+
* If one is found, user must authorize to associate it. If none has been found,
196+
* a new local identity is automatically registered.
201197
* If the attribute list is empty a full pre-filled registration and login forms are displayed.
202198
*/
203199
public $associateByAttributes = array('email');
204200

205201
/**
206-
* @var array If not null, CAPTCHA will be enabled on the registration and recovery form and this will be passed as arguments to the CCaptcha widget.
207-
* Remember to include the 'captchaAction'=>'/usr/default/captcha' property. Adjust the module id.
202+
* @var array If not null, CAPTCHA will be enabled on the registration and recovery form
203+
* and this will be passed as arguments to the CCaptcha widget.
204+
* Remember to include the 'captchaAction'=>'/usr/default/captcha' property.
205+
* Adjust the module id.
208206
*/
209207
public $captcha;
210208
/**
@@ -220,12 +218,8 @@ class UsrModule extends CWebModule
220218
public $loginFormBehaviors;
221219

222220
/**
223-
* @var string Name of the user application component
224-
*/
225-
public $userComponent = 'user';
226-
227-
/**
228-
* @var array Scenarios configuration
221+
* @var array View params used in different LoginForm model scenarios.
222+
* View name can be changed by setting the 'view' key.
229223
*/
230224
public $scenarios;
231225
/**
@@ -339,7 +333,7 @@ public function createFormModel($class, $scenario='')
339333
{
340334
/** @var CFormModel */
341335
$form = new $class($scenario);
342-
$form->webUser = $this->getUser();
336+
$form->webUser = Yii::app()->user;
343337
$form->userIdentityClass = $this->userIdentityClass;
344338
if ($form instanceof BasePasswordForm) {
345339
$form->passwordStrengthRules = $this->passwordStrengthRules;
@@ -376,9 +370,4 @@ public function createFormModel($class, $scenario='')
376370
}
377371
return $form;
378372
}
379-
380-
public function getUser()
381-
{
382-
return Yii::app()->{$this->userComponent};
383-
}
384373
}

components/ExpiredPasswordBehavior.php

+9
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,28 @@
99
* ExpiredPasswordBehavior adds captcha validation to a form model component.
1010
* The model should extend from {@link CFormModel} or its child classes.
1111
*
12+
* The user identity class must implement IPasswordHistoryIdentity interface.
13+
*
1214
* @property CFormModel $owner The owner model that this behavior is attached to.
15+
* @property integer $passwordTimeout Number of days after which user is requred to reset his password after logging in.
1316
*
1417
* @author Jan Was <[email protected]>
1518
*/
1619
class ExpiredPasswordBehavior extends FormModelBehavior
1720
{
1821
private $_passwordTimeout;
1922

23+
/**
24+
* @return integer Number of days after which user is requred to reset his password after logging in.
25+
*/
2026
public function getPasswordTimeout()
2127
{
2228
return $this->_passwordTimeout;
2329
}
2430

31+
/**
32+
* @param $value integer Number of days after which user is requred to reset his password after logging in.
33+
*/
2534
public function setPasswordTimeout($value)
2635
{
2736
$this->_passwordTimeout = $value;

components/OneTimePasswordAction.php

+26-13
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@
66
*/
77
class OneTimePasswordAction extends CAction
88
{
9+
/**
10+
* @var array Same configuration as set for @see OneTimePasswordFormBehavior.
11+
*/
12+
public $configuration = array(
13+
'authenticator' => null,
14+
'mode' => null,
15+
'required' => null,
16+
'timeout' => null,
17+
);
18+
919
public function run() {
10-
if ($this->controller->module->getUser()->isGuest)
20+
if (Yii::app()->user->isGuest)
1121
$this->controller->redirect(array('login'));
1222
/** @var UsrModule */
13-
$module = $this->controller->module;
14-
if ($module->oneTimePasswordRequired)
23+
if ($this->configuration['required'])
1524
$this->controller->redirect(array('profile'));
1625

1726
$model = new OneTimePasswordForm;
@@ -22,22 +31,22 @@ public function run() {
2231
*/
2332
if ($identity->getOneTimePasswordSecret() !== null) {
2433
$identity->setOneTimePasswordSecret(null);
25-
Yii::app()->request->cookies->remove(UsrModule::OTP_COOKIE);
34+
Yii::app()->request->cookies->remove(OneTimePasswordFormBehavior::OTP_COOKIE);
2635
$this->controller->redirect('profile');
2736
return;
2837
}
2938

30-
$model->setMode($module->oneTimePasswordMode)->setAuthenticator($module->googleAuthenticator);
39+
$model->setMode($this->configuration['mode'])->setAuthenticator($this->configuration['authenticator']);
3140

3241
/**
3342
* When no secret has been set yet, generate a new secret and save it in session.
3443
* Do it if it hasn't been done yet.
3544
*/
36-
if (($secret=Yii::app()->session[UsrModule::OTP_SECRET_PREFIX.'newSecret']) === null) {
37-
$secret = Yii::app()->session[UsrModule::OTP_SECRET_PREFIX.'newSecret'] = $module->googleAuthenticator->generateSecret();
45+
if (($secret=Yii::app()->session[OneTimePasswordFormBehavior::OTP_SECRET_PREFIX.'newSecret']) === null) {
46+
$secret = Yii::app()->session[OneTimePasswordFormBehavior::OTP_SECRET_PREFIX.'newSecret'] = $this->configuration['authenticator']->generateSecret();
3847

3948
$model->setSecret($secret);
40-
if ($module->oneTimePasswordMode === UsrModule::OTP_COUNTER) {
49+
if ($this->configuration['mode'] === OneTimePasswordFormBehavior::OTP_COUNTER) {
4150
$this->controller->sendEmail($model, 'oneTimePassword');
4251
}
4352
}
@@ -48,23 +57,27 @@ public function run() {
4857
if ($model->validate()) {
4958
// save secret
5059
$identity->setOneTimePasswordSecret($secret);
51-
Yii::app()->session[UsrModule::OTP_SECRET_PREFIX.'newSecret'] = null;
60+
Yii::app()->session[OneTimePasswordFormBehavior::OTP_SECRET_PREFIX.'newSecret'] = null;
5261
// save current code as used
53-
$identity->setOneTimePassword($model->oneTimePassword, $module->oneTimePasswordMode === UsrModule::OTP_TIME ? floor(time() / 30) : $model->getPreviousCounter() + 1);
62+
$identity->setOneTimePassword($model->oneTimePassword, $this->configuration['mode'] === OneTimePasswordFormBehavior::OTP_TIME ? floor(time() / 30) : $model->getPreviousCounter() + 1);
5463
$this->controller->redirect('profile');
5564
}
5665
}
5766
if (YII_DEBUG) {
58-
$model->oneTimePassword = $module->googleAuthenticator->getCode($secret, $module->oneTimePasswordMode === UsrModule::OTP_TIME ? null : $model->getPreviousCounter());
67+
$model->oneTimePassword = $this->configuration['authenticator']->getCode($secret, $this->configuration['mode'] === OneTimePasswordFormBehavior::OTP_TIME ? null : $model->getPreviousCounter());
5968
}
6069

61-
if ($module->oneTimePasswordMode === UsrModule::OTP_TIME) {
70+
if ($this->configuration['mode'] === OneTimePasswordFormBehavior::OTP_TIME) {
6271
$hostInfo = Yii::app()->request->hostInfo;
6372
$url = $model->getUrl($identity->username, parse_url($hostInfo, PHP_URL_HOST), $secret);
6473
} else {
6574
$url = '';
6675
}
6776

68-
$this->controller->render('generateOTPSecret', array('model'=>$model, 'url'=>$url));
77+
$this->controller->render('generateOTPSecret', array(
78+
'model' => $model,
79+
'url' => $url,
80+
'mode' => $this->configuration['mode'],
81+
));
6982
}
7083
}

0 commit comments

Comments
 (0)