-
Notifications
You must be signed in to change notification settings - Fork 19
/
LilyModule.php
executable file
·495 lines (456 loc) · 19.1 KB
/
LilyModule.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
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
<?php
/**
* LilyModule class file.
*
* @author George Agapov <[email protected]>
* @link https://github.com/georgeee/yii-lily
* @license http://www.opensource.org/licenses/bsd-license.php
*/
/**
* Lily Module
*
*
* Lily is an Yii module, that provides you user managment funtionalities.
* But not like others, it allows you to authenticate using various authentivation services
* (e.g. google or twitter, it uses EAuth extension (https://github.com/Nodge/yii-eauth) for this purpose)
* and, as usual email-password pair.
*
* Out-of-box it provides only a sceleton for managimg user data (through active realtions),
* it doesn't contain any fields as name or birthday, so you are free to create exactly what you want to.
* It supports binding two or more accounts (there are no restrictions, you can bind even a thousand
* email or google accounts to one user) and also it supports merging two users in one
* (this action will be suggested if you try to bind an account, that was already bound to another user).
* Of course, you will be able to set a handler on user merge event in order to update your tables (e.g. change the owner of content).
*
* And, two words about the name - module was called in tribute of one beautiful russian poem, written
* by Vladimir Mayakovsky, Lilechka (russian: Лилечка). If you speak russian, I really suggest you to read it.
*
*
*
* @property LAccountManager $accountManager account manager instance
* @property LilyModule $instance module main class current instance
* @property LUserIniter $userIniter component for user init process (when
* user is just registered through this component module initializes relations (if onRegister is set)
*
* @property array $relations User table relations, in the format:
* array(
* ..
* $relationName => array(
* 'relation' => $relation,
* 'onUserMerge' => $onUserMerge,
* 'onRegister' => $onRegister,
* 'callback' => $callback,
* )
* ..
* )
* $relationName - name of relations, as in ActiveRecord relations()
* $relation - ActiveRecord relation in format, specified by relations()
* $onUserMerge - behavoiur on user merging. Can be: auto (just updates indexies from old uid to new one),
* event (raises an event accross the relation model object (or objects)), callback (executes the callback,
* specified by 'callback' property of relation)
* $callback - see above
* @property array $userRelations Array of relations for putting it in realtions() method of LUser (generated by $relations property)
* @property LUser $user current user object, null if user isn't authenticated
* @property LAccount $account current account object (through one current user was authenticated), null if user isn't authenticated
* @property LSession $session current session object, null if user isn't authenticated
* @property stdClass $sessionData curent session data, empty object (new stdClass) if user isn't authenticated
* @property string $assetsUrl url to assets folder, where assests of the module are published
* @property array $services EAuth services settings (for all except hidden)
* @property array $allServices EAuth services settings (for all services including hidden)
*
* @package application.modules.lily
*/
class LilyModule extends CWebModule {
/**
* @static
* php callback, that initialises lily
*/
public static function initModule() {
Yii::app()->getModule('lily');
}
/**
* @var string hash function name (e.g. md5)
*/
public $hashFunction = 'md5';
/**
* @var string hash Salt, string that will be appended to hashing value before hashing
*/
public $hashSalt = "ePGFxh7JeNL1AlaWCDfv";
/**
* @var integer lengths of random keys, generated by application (e.g. activation key)
*/
public $randomKeyLength = 20;
/**
* @var string regular expression for password checking
* Default rule: lowercase and uppercase latin letters, characters (excluding brackets) "-.,;=+~/\[]{}!@#$%^*&()_|" and simple whitespace
*/
public $passwordRegexp = '~^[a-zA-Z0-9\\-\\_\\|\\.\\,\\;\\=\\+\\~/\\\\\\[\\]\\{\\}\\!\\@\\#\\$\\%\\^\\*\\&\\(\\)\\ ]{8,32}$~';
/**
* @var integer timeout, after that session will be classified as expired
* Defaults to one week
*/
public $sessionTimeout = 604800;
/**
* @var boolean Whether to allow user merging
*/
public $enableUserMerge = true;
/**
* @var mixed callback, that takes LUser object as argument and return user's name
*/
public $userNameFunction = null;
/**
* @var array routes, that are allowed during any init step
*/
public $allowedRoutes = array();
/**
* @var string prefix to module uri (e.g. lily prefix means all actions of the module have uris like 'lily/<controllerId>/<actionId>)
*/
public $routePrefix = 'lily';
protected $_relations = null;
protected $_userRelations = array();
protected $_session = null;
protected static $_instance;
/**
* Return absolute route to module's route (e.g. for putting it in Yii::app()->urlManager->createUrl() method)
* @static
* @param string $name Route (relative to module path)
* @return string Absolute route
*/
public static function route($name) {
return self::instance()->routePrefix . '/' . $name;
}
/**
* This function returns current instance of LilyModule class
*
* @return LilyModule instance
*/
public static function instance() {
return self::$_instance;
}
/**
* Getter for relations property of this class
* (check out the class header for more information)
* @return array relations property
*/
public function getRelations() {
return $this->_relations;
}
/**
* Getter for userRelations property of this class
* (check out the class header for more information)
* @return array userRelations property
*/
public function getUserRelations() {
return $this->_userRelations;
}
/**
* Setter for relations property of this class
* (check out the class header for more information)
* @param array $relations relations array
*/
public function setRelations(array $relations) {
$this->_relations = array_merge($relations, array(
'accounts' => array(
'relation' => array(CActiveRecord::HAS_MANY, 'LAccount', 'uid'),
'onUserMerge' => 'auto'
),
'emailActivations' => array(
'relation' => array(CActiveRecord::HAS_MANY, 'LEmailAccountActivation', 'uid'),
'onUserMerge' => 'auto'
),
)
);
$userRelations = array();
foreach ($relations as $name => $relation) {
$userRelations[$name] = $relation['relation'];
}
$this->_userRelations = $userRelations;
}
/**
* Method, that raises user merge event accros the class
* (and processes needed actions, like specified in onUserMerge, see $relations property of this class)
* @param LMergeEvent $event User merge event instance, with $oldUid and $newUid properties specified
* @throws LException
*/
public function onUserMerge(LMergeEvent $event) {
foreach ($this->relations as $name => $relation) {
if (isset($relation['onUserMerge'])) {
$type = $relation['onUserMerge'];
if ($type == 'auto') {
switch ($relation['relation'][0]) {
case CActiveRecord::HAS_MANY:
Yii::app()->db->createCommand()->update(CActiveRecord::model($relation['relation'][1])->tableName(), array($relation['relation'][2] => $event->newUid), $relation['relation'][2] . '=:oldUserId', array(':oldUserId' => $event->oldUid));
break;
case CActiveRecord::HAS_ONE:
Yii::app()->db->createCommand()->delete(CActiveRecord::model($relation['relation'][1])->tableName(), $relation['relation'][2] . '=:oldUserId', array(':oldUserId' => $event->oldUid));
break;
case CActiveRecord::BELONGS_TO:
throw new LException("Lily doesn't support userMerge auto on BELONGS_TO relationship");
break;
case CActiveRecord::MANY_MANY:
if (!preg_match('/^\s*(.*?)\((.*)\)\s*$/', $relation['relation'][2], $matches))
throw new LException("Wrong data for MANY_MANY");
$table = $matches[1];
$keys = preg_split('/\s*,\s*/', $matches[2], -1, PREG_SPLIT_NO_EMPTY);
if (substr($keys[0], 0, 2) == 't.')
$keys[0] = substr($keys[0], 2);
Yii::app()->db->createCommand()->update($table, array($keys[0] => $event->newUid), $keys[0] . '=:oldUserId', array(':oldUserId' => $event->oldUid));
break;
}
}else if ($type == 'event') {
$oldUser = LUser::model()->findByPk($event->oldUid);
switch ($relation['relation'][0]) {
case CActiveRecord::HAS_MANY:
if (isset($oldUser->$name))
foreach ($oldUser->$name as $v)
$v->onUserMerge($event);
break;
case CActiveRecord::HAS_ONE:
if (isset($oldUser->$name))
$oldUser->$name->onUserMerge($event);
break;
case CActiveRecord::BELONGS_TO:
if (isset($oldUser->$name))
$oldUser->$name->onUserMerge($event);
break;
case CActiveRecord::MANY_MANY:
if (isset($oldUser->$name))
foreach ($oldUser->$name as $v)
$v->onUserMerge($event);
break;
}
}else if ($type == 'callback') {
call_user_func($relation['callback'], $event);
} else {
throw new LException("Invalid onUserMerge value '$type'");
}
}
}
$this->raiseEvent('onUserMerge', $event);
}
/**
* Raises onAfterLilyLoad event
* @param CEvent $event event with sender - module instance
*/
public function onAfterLilyLoad($event) {
$this->raiseEvent('onAfterLilyLoad', $event);
}
/**
* Raises onBeforeLilyLoad event
* @param CEvent $event event with sender - module instance
*/
public function onBeforeLilyLoad($event) {
$this->raiseEvent('onBeforeLilyLoad', $event);
}
/**
* Getter for $session property
* @return LSession
*/
public function getSession() {
return $this->_session;
}
/**
* Getter for $account property
* @return LAccount
*/
public function getAccount() {
return isset($this->_session->account) ? $this->_session->account : null;
}
/**
* Getter for $user property
* @return LUser
*/
public function getUser() {
return isset($this->_session->user) ? $this->_session->user : null;
}
/**
* Getter for $sessionData property
* @return stdClass
*/
public function getSessionData() {
return isset($this->_session->data) ? $this->_session->data : new stdClass;
}
/**
* Returns services settings declared in the authorization classes.
* For perfomance reasons it uses Yii::app()->cache to store settings array.
*
* It returns all services except hidden
* @return array services settings.
*/
public function getServices() {
if (Yii::app()->hasComponent('cache'))
$services = Yii::app()->cache->get('Lily.services');
if (!isset($services) || !is_array($services)) {
$_services = $this->allServices;
$services = array();
foreach ($_services as $k => $service) {
if ($service->type != 'hidden')
$services[$k] = $service;
}
if (Yii::app()->hasComponent('cache'))
Yii::app()->cache->set('Lily.services', $services);
}
return $services;
}
/**
* Returns services settings declared in the authorization classes.
* For perfomance reasons it uses Yii::app()->cache to store settings array.
*
* It returns absolutely all services (including hidden)
* @return array services settings.
*/
public function getAllServices() {
if (Yii::app()->hasComponent('cache'))
$services = Yii::app()->cache->get('Lily.allServices');
if (!isset($services) || !is_array($services)) {
$services = Yii::app()->eauth->getServices();
if (Yii::app()->hasComponent('cache'))
Yii::app()->cache->set('Lily.allServices', $services);
}
return $services;
}
/**
* This function inits LilyModule instance
*/
public function init() {
parent::init();
self::$_instance = $this;
$this->setImport(array(
'lily.*',
'lily.components.*',
'lily.services.*',
'lily.models.*',
));
$this->onBeforeLilyLoad(new CEvent($this));
if (!isset($this->_relations))
$this->relations = array();
if (!$this->hasComponent('accountManager'))
$this->accountManager = array();
if (!$this->hasComponent('userIniter'))
$this->userIniter = array();
if (!Yii::app()->user->isGuest) {
$logout = true;
$sid = Yii::app()->user->getState('sid');
$ssid = Yii::app()->user->hasState('ssid');
if (isset($sid) && isset($ssid)) {
$session = LSession::model()->findByPk($sid);
if (isset($session) && $session->ssid == $ssid) {
if ($session->created + $this->sessionTimeout >= time()) {
$this->_session = $session;
Yii::app()->user->name = $this->user->getName($this->userNameFunction);
if ($this->user->state == LUser::BANNED_STATE && Yii::app()->authManager->checkAccess('unbanUser', $this->user->uid, array('uid' => $this->user->uid))) {
$this->user->state = LUser::ACTIVE_STATE;
if (!$this->user->save())
throw new CDbException("can't save user");
}
if ($this->user->state == LUser::BANNED_STATE || ($this->user->state == LUser::DELETED_STATE && !Yii::app()->user->checkAccess('restoreUser', array('user' => $this->user)))) {
$logout = true;
if (!$session->delete())
throw new CDbException("can't delete session");
} else {
if ($this->user->state == LUser::DELETED_STATE) {
$route = Yii::app()->urlManager->parseUrl(Yii::app()->request);
if (!($route == LilyModule::route("user/switch_state")
&& Yii::app()->request->getParam('uid') == $this->user->uid
&& Yii::app()->request->getParam('mode') == LUser::ACTIVE_STATE) && $route != LilyModule::route("user/logout")) {
Yii::app()->request->redirect(Yii::app()->createUrl('/'.LilyModule::route("user/switch_state"), array('uid' => $this->user->uid, 'mode' => LUser::ACTIVE_STATE)));
}
} else {
if (!$this->user->inited)
$this->userIniter->start();
}
$logout = false;
}
}else if (!$session->delete())
throw new CDbException("can't delete session");
}
}
if ($logout) {
Yii::app()->user->logout();
}
}
$this->onAfterLilyLoad(new CEvent($this));
}
/**
* Sets userIniter component configurations
* @param array $settings
*/
public function setUserIniter($settings) {
$this->setComponents(
array(
'userIniter' => array_merge(array('class' => 'LUserIniter'), $settings),
)
);
}
/**
* User initer component instance
* @return LUserIniter
*/
public function getUserIniter() {
return $this->getComponent('userIniter');
}
/**
* Sets accountManager component configurations
* @param array $settings
*/
public function setAccountManager($settings) {
$this->setComponents(
array(
'accountManager' => array_merge(array('class' => 'LAccountManager'), $settings),
)
);
}
/**
* Email account manager component instance
* @return LAccountManager
*/
public function getAccountManager() {
return $this->getComponent('accountManager');
}
/**
* This function simply hashes the string, using function from $hashFunction property
* and salt from $hashSalt
* @param string $str string to hash
* @return string hash of string
*/
public function hash($str) {
$hashFunction = $this->hashFunction;
return $hashFunction($str . $this->hashSalt);
}
/**
* This method generates random string f specified length
* @param integer $length Length of generated string
* @return string random string
*/
public function generateRandomString($length = -1) {
if ($length == -1)
$length = $this->randomKeyLength;
$result = '';
$possible_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$pc_length = strlen($possible_chars);
for ($i = 0; $i < $length; $i++) {
$result .= $possible_chars[mt_rand(0, $pc_length - 1)];
}
return $result;
}
/**
* Alias for Yii::t()
* @param string $str string to translate
* @param array $params params for translation
* @param string $dic dictionary to use in string translation
* @return string translated string
*/
public static function t($str = '', $params = array(), $dic = 'default') {
return Yii::t("LilyModule." . $dic, $str, $params);
}
/**
* Getter for assetsUrl property
* It publishes mdule asserts and returns the path to assets folder
* @return string assets url
*/
public function getAssetsUrl() {
$assets_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'assets';
return Yii::app()->assetManager->publish($assets_path, false, -1, YII_DEBUG);
}
}
?>