diff --git a/src/User/Controller/AdminController.php b/src/User/Controller/AdminController.php index 0cd1013..90343b4 100644 --- a/src/User/Controller/AdminController.php +++ b/src/User/Controller/AdminController.php @@ -255,7 +255,7 @@ class AdminController extends Controller $this->trigger(ActiveRecord::EVENT_BEFORE_DELETE, $event); if ($user->delete()) { - Yii::$app->getSession()->setFlash('success', \Yii::t('usuario', 'User has been deleted')); + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'User has been deleted')); $this->trigger(ActiveRecord::EVENT_AFTER_DELETE, $event); } else { Yii::$app->getSession()->setFlash( diff --git a/src/User/Controller/RuleController.php b/src/User/Controller/RuleController.php new file mode 100644 index 0000000..dbce6c2 --- /dev/null +++ b/src/User/Controller/RuleController.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Controller; + +use Da\User\Model\Rule; +use Da\User\Search\RuleSearch; +use Da\User\Service\AuthRuleEditionService; +use Da\User\Traits\AuthManagerAwareTrait; +use Da\User\Traits\ContainerAwareTrait; +use Da\User\Validator\AjaxRequestModelValidator; +use Yii; +use yii\filters\VerbFilter; +use yii\web\Controller; +use yii\web\NotFoundHttpException; + +class RuleController extends Controller +{ + use AuthManagerAwareTrait; + use ContainerAwareTrait; + + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ] + ]; + } + + public function actionIndex() + { + /** @var RuleSearch $searchModel */ + $searchModel = $this->make(RuleSearch::class); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render( + 'index', + [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ] + ); + } + + public function actionCreate() + { + $model = $this->make(Rule::class, [], ['scenario' => 'create', 'className' => \yii\rbac\Rule::class]); + + $this->make(AjaxRequestModelValidator::class, [$model])->validate(); + + if ($model->load(Yii::$app->request->post())) { + if ($this->make(AuthRuleEditionService::class, [$model])->run()) { + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Authorization rule has been added.')); + + return $this->redirect(['index']); + } + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Unable to create new authorization rule.')); + } + + return $this->render( + 'create', + [ + 'model' => $model + ] + ); + } + + public function actionUpdate($name) + { + /** @var Rule $model */ + $model = $this->make(Rule::class, [], ['scenario' => 'update']); + $rule = $this->findRule($name); + + $model->setAttributes( + [ + 'previousName' => $name, + 'name' => $rule->name, + 'className' => get_class($rule) + ] + ); + + $this->make(AjaxRequestModelValidator::class, [$model])->validate(); + + if ($model->load(Yii::$app->request->post())) { + if ($this->make(AuthRuleEditionService::class, [$model])->run()) { + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Authorization rule has been updated.')); + + return $this->redirect(['index']); + } + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Unable to update authorization rule.')); + } + + return $this->render( + 'update', + [ + 'model' => $model, + ] + ); + } + + public function actionDelete($name) + { + $rule = $this->findRule($name); + + $this->getAuthManager()->remove($rule); + $this->getAuthManager()->invalidateCache(); + + Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Authorization rule has been removed.')); + } + + /** + * @param $name + * + * @throws NotFoundHttpException + * @return mixed|null|\yii\rbac\Rule + */ + protected function findRule($name) + { + $rule = $this->getAuthManager()->getRule($name); + + if (!($rule instanceof \yii\rbac\Rule)) { + throw new NotFoundHttpException(Yii::t('usuario', 'Rule {0} not found.', $name)); + } + + return $rule; + } +} diff --git a/src/User/Model/AbstractAuthItem.php b/src/User/Model/AbstractAuthItem.php index 04c197d..dbee40b 100644 --- a/src/User/Model/AbstractAuthItem.php +++ b/src/User/Model/AbstractAuthItem.php @@ -13,6 +13,7 @@ namespace Da\User\Model; use Da\User\Traits\AuthManagerAwareTrait; use Da\User\Validator\RbacItemsValidator; +use Da\User\Validator\RbacRuleExistsValidator; use Da\User\Validator\RbacRuleValidator; use Yii; use yii\base\Model; @@ -41,7 +42,7 @@ abstract class AbstractAuthItem extends Model /** * @var string[] */ - public $children; + public $children = []; /** * @var \yii\rbac\Role|\yii\rbac\Permission */ @@ -60,7 +61,7 @@ abstract class AbstractAuthItem extends Model $this->description = $this->item->description; $this->children = array_keys($this->getAuthManager()->getChildren($this->item->name)); if ($this->item->ruleName !== null) { - $this->rule = get_class($this->getAuthManager()->getRule($this->item->ruleName)); + $this->rule = $this->item->ruleName; } } } @@ -111,7 +112,7 @@ abstract class AbstractAuthItem extends Model }, ], ['children', RbacItemsValidator::class], - ['rule', RbacRuleValidator::class], + ['rule', RbacRuleExistsValidator::class], ]; } diff --git a/src/User/Model/Rule.php b/src/User/Model/Rule.php new file mode 100644 index 0000000..f993368 --- /dev/null +++ b/src/User/Model/Rule.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Model; + +use Da\User\Traits\AuthManagerAwareTrait; +use Da\User\Validator\RbacRuleNameValidator; +use Da\User\Validator\RbacRuleValidator; +use yii\base\Model; + +class Rule extends Model +{ + use AuthManagerAwareTrait; + + /** + * @var string + */ + public $name; + /** + * @var string fully qualified class name. Not to be confused with className() method + */ + public $className; + /** + * @var string holds the name of the rule previous update + */ + public $previousName; + + /** + * @inheritdoc + */ + public function scenarios() + { + return [ + 'create' => ['name', 'className'], + 'update' => ['name', 'className', 'previousName'], + ]; + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['name', 'className'], 'trim'], + [['name', 'className'], 'required'], + [['name', 'previousName'], 'match', 'pattern' => '/^[\w][\w-.:]+[\w]$/'], + [['name'], RbacRuleNameValidator::class, 'previousName' => $this->previousName], + [['className'], RbacRuleValidator::class], + ]; + } +} diff --git a/src/User/Search/RuleSearch.php b/src/User/Search/RuleSearch.php new file mode 100644 index 0000000..af460e3 --- /dev/null +++ b/src/User/Search/RuleSearch.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Search; + +use Da\User\Model\Rule; +use Da\User\Traits\ContainerAwareTrait; +use yii\base\Model; +use yii\data\ActiveDataProvider; +use yii\db\Query; + +class RuleSearch extends Rule +{ + use ContainerAwareTrait; + + /** + * @var string + */ + public $created_at; + + /** + * @inheritdoc + */ + public function scenarios() + { + return Model::scenarios(); + } + + /** + * @inheritdoc + */ + public function rules() + { + return [ + ['name', 'string'], + ]; + } + + /** + * @param array $params + * + * @return ActiveDataProvider + */ + public function search(array $params = []) + { + $query = (new Query()) + ->select(['name', 'data', 'created_at', 'updated_at']) + ->from($this->getAuthManager()->ruleTable) + ->orderBy(['name' => SORT_ASC]); + + if ($this->load($params)) { + $query->andFilterWhere(['name' => $this->name]); + } + + if (!$this->validate()) { + $query->where('0=1'); + var_dump($this->load($params)); + die(); + } + + return $this->make( + ActiveDataProvider::class, + [], + [ + 'query' => $query, + 'db' => $this->getAuthManager()->db, + 'sort' => [ + 'attributes' => ['name', 'created_at', 'updated_at'] + ] + ] + ); + } +} diff --git a/src/User/Service/AuthItemEditionService.php b/src/User/Service/AuthItemEditionService.php index fe6a20e..3ba8bf4 100644 --- a/src/User/Service/AuthItemEditionService.php +++ b/src/User/Service/AuthItemEditionService.php @@ -46,11 +46,9 @@ class AuthItemEditionService implements ServiceInterface $item->description = $this->model->description; if (!empty($this->model->rule)) { - $rule = $this->make($this->model->rule); - if (null === $this->getAuthManager()->getRule($rule->name)) { - $this->getAuthManager()->add($rule); + if (null !== $this->getAuthManager()->getRule($this->model->rule)) { + $item->ruleName = $this->model->rule; } - $item->ruleName = $rule->name; } else { $item->ruleName = null; } diff --git a/src/User/Service/AuthRuleEditionService.php b/src/User/Service/AuthRuleEditionService.php new file mode 100644 index 0000000..8ff4254 --- /dev/null +++ b/src/User/Service/AuthRuleEditionService.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Service; + +use Da\User\Contracts\ServiceInterface; +use Da\User\Model\Rule; +use Da\User\Traits\AuthManagerAwareTrait; +use Da\User\Traits\ContainerAwareTrait; +use Exception; + +class AuthRuleEditionService implements ServiceInterface +{ + use AuthManagerAwareTrait; + use ContainerAwareTrait; + + protected $model; + + public function __construct(Rule $model) + { + $this->model = $model; + } + + public function run() + { + if (!$this->model->validate() || (!in_array($this->model->scenario, ['create', 'update']))) { + return false; + } + + $rule = $this->make($this->model->className, [], ['name' => $this->model->name]); + + try { + if ($this->model->scenario == 'create') { + $this->getAuthManager()->add($rule); + } else { + $this->getAuthManager()->update($this->model->previousName, $rule); + } + $this->getAuthManager()->invalidateCache(); + } catch (Exception $e) { + return false; + } + + return true; + } +} diff --git a/src/User/Validator/RbacRuleExistsValidator.php b/src/User/Validator/RbacRuleExistsValidator.php new file mode 100644 index 0000000..c48ecc3 --- /dev/null +++ b/src/User/Validator/RbacRuleExistsValidator.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Validator; + +use Da\User\Traits\AuthManagerAwareTrait; +use Yii; +use yii\validators\Validator; + +class RbacRuleExistsValidator extends Validator +{ + use AuthManagerAwareTrait; + + protected function validateValue($value) + { + $rule = $this->getAuthManager()->getRule($value); + + if (!$rule) { + return [Yii::t('usuario', 'Rule {0} does not exists', $value), []]; + } + } +} diff --git a/src/User/Validator/RbacRuleNameValidator.php b/src/User/Validator/RbacRuleNameValidator.php new file mode 100644 index 0000000..0089f82 --- /dev/null +++ b/src/User/Validator/RbacRuleNameValidator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Da\User\Validator; + +use Da\User\Traits\AuthManagerAwareTrait; +use Yii; +use yii\rbac\Rule; +use yii\validators\Validator; + +class RbacRuleNameValidator extends Validator +{ + use AuthManagerAwareTrait; + + /** + * @var + */ + public $previousName; + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if ($this->previousName != $value) { + $rule = $this->getAuthManager()->getRule($value); + + if ($rule instanceof Rule) { + return [Yii::t('usuario', 'Rule name {0} is already in use', $value), []]; + } + } + return null; + } +} diff --git a/src/User/Validator/RbacRuleValidator.php b/src/User/Validator/RbacRuleValidator.php index 96bd778..2591bb3 100644 --- a/src/User/Validator/RbacRuleValidator.php +++ b/src/User/Validator/RbacRuleValidator.php @@ -11,26 +11,26 @@ namespace Da\User\Validator; +use Da\User\Traits\ContainerAwareTrait; use Exception; -use ReflectionClass; use Yii; +use yii\rbac\Rule; use yii\validators\Validator; class RbacRuleValidator extends Validator { + use ContainerAwareTrait; + protected function validateValue($value) { try { - $class = new ReflectionClass($value); - } catch (Exception $e) { - return [Yii::t('usuario', 'Class "{0}" does not exist', $value), []]; - } + $rule = $this->make($value); - if ($class->isInstantiable() == false) { - return [Yii::t('usuario', 'Rule class can not be instantiated'), []]; - } - if ($class->isSubclassOf('\yii\rbac\Rule') == false) { - return [Yii::t('usuario', 'Rule class must extend "yii\\rbac\\Rule"'), []]; + if (!($rule instanceof Rule)) { + return [Yii::t('usuario', 'Rule class must extend "yii\\rbac\\Rule".'), []]; + } + } catch (Exception $e) { + return [Yii::t('usuario', 'Authentication rule class {0} can not be instantiated', $value), []]; } } } diff --git a/src/User/resources/views/permission/_form.php b/src/User/resources/views/permission/_form.php index 1e79706..21c4274 100644 --- a/src/User/resources/views/permission/_form.php +++ b/src/User/resources/views/permission/_form.php @@ -10,6 +10,7 @@ */ use dosamigos\selectize\SelectizeDropDownList; +use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\widgets\ActiveForm; @@ -32,7 +33,13 @@ use yii\widgets\ActiveForm; field($model, 'description') ?> -field($model, 'rule') ?> +field($model, 'rule')->widget(SelectizeDropDownList::class, [ + 'items' => ArrayHelper::map(Yii::$app->getAuthManager()->getRules(), 'name', 'name'), + 'options' => [ + 'prompt' => 'Select rule...' + ] +]) ?> + field($model, 'children')->widget( SelectizeDropDownList::class, diff --git a/src/User/resources/views/role/_form.php b/src/User/resources/views/role/_form.php index 4e2cc7f..69403e2 100644 --- a/src/User/resources/views/role/_form.php +++ b/src/User/resources/views/role/_form.php @@ -16,6 +16,7 @@ use Da\User\Helper\AuthHelper; use dosamigos\selectize\SelectizeDropDownList; +use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\widgets\ActiveForm; @@ -33,7 +34,12 @@ $unassignedItems = Yii::$container->get(AuthHelper::class)->getUnassignedItems($ field($model, 'description') ?> -field($model, 'rule') ?> +field($model, 'rule')->widget(SelectizeDropDownList::class, [ + 'items' => ArrayHelper::map(Yii::$app->getAuthManager()->getRules(), 'name', 'name'), + 'options' => [ + 'prompt' => 'Select rule...' + ] +]) ?> field($model, 'children')->widget( SelectizeDropDownList::class, diff --git a/src/User/resources/views/rule/_form.php b/src/User/resources/views/rule/_form.php new file mode 100644 index 0000000..37fc54a --- /dev/null +++ b/src/User/resources/views/rule/_form.php @@ -0,0 +1,27 @@ + + + + false, + 'enableAjaxValidation' => true, + ] +) ?> + +field($model, 'name') ?> + +field($model, 'className') ?> + + 'btn btn-success btn-block']) ?> + + diff --git a/src/User/resources/views/rule/create.php b/src/User/resources/views/rule/create.php new file mode 100644 index 0000000..386a9c0 --- /dev/null +++ b/src/User/resources/views/rule/create.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +/** + * @var \Da\User\Model\Role $model + * @var $this yii\web\View + * @var $unassignedItems string[] + */ +$this->title = Yii::t('usuario', 'Create new rule'); +$this->params['breadcrumbs'][] = $this->title; + +?> + +beginContent('@Da/User/resources/views/shared/admin_layout.php') ?> + +render( + '_form', + [ + 'model' => $model, + ] +) ?> + +endContent() ?> diff --git a/src/User/resources/views/rule/index.php b/src/User/resources/views/rule/index.php new file mode 100644 index 0000000..fe47bf3 --- /dev/null +++ b/src/User/resources/views/rule/index.php @@ -0,0 +1,76 @@ +title = Yii::t('usuario', 'Rules'); +$this->params['breadcrumbs'][] = $this->title; + +?> + +beginContent('@Da/User/resources/views/shared/admin_layout.php') ?> + + $dataProvider, + 'filterModel' => $searchModel, + 'layout' => "{items}\n{pager}", + 'columns' => [ + [ + 'attribute' => 'name', + 'label' => Yii::t('usuario', 'Name'), + 'options' => [ + 'style' => 'width: 20%' + ], + ], + [ + 'attribute' => 'className', + 'label' => Yii::t('usuario', 'Class'), + 'value' => function ($row) { + $rule = unserialize($row['data']); + + return get_class($rule); + }, + 'options' => [ + 'style' => 'width: 20%' + ], + ], + [ + 'attribute' => 'created_at', + 'label' => Yii::t('usuario', 'Created at'), + 'format' => 'datetime', + 'options' => [ + 'style' => 'width: 20%' + ], + ], + [ + 'attribute' => 'updated_at', + 'label' => Yii::t('usuario', 'Updated at'), + 'format' => 'datetime', + 'options' => [ + 'style' => 'width: 20%' + ], + ], + [ + 'class' => ActionColumn::className(), + 'template' => '{update} {delete}', + 'urlCreator' => function ($action, $model) { + return Url::to(['/user/rule/' . $action, 'name' => $model['name']]); + }, + 'options' => [ + 'style' => 'width: 5%' + ], + ] + ], + ] +) ?> + +endContent() ?> diff --git a/src/User/resources/views/rule/update.php b/src/User/resources/views/rule/update.php new file mode 100644 index 0000000..df4fce4 --- /dev/null +++ b/src/User/resources/views/rule/update.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +/** + * @var \Da\User\Model\Role $model + * @var $this yii\web\View + * @var $unassignedItems string[] + */ +$this->title = Yii::t('usuario', 'Update rule'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('usuario', 'Rules'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; + +?> + +beginContent('@Da/User/resources/views/shared/admin_layout.php') ?> + +render( + '_form', + [ + 'model' => $model, + ] +) ?> + +endContent() ?> diff --git a/src/User/resources/views/shared/_menu.php b/src/User/resources/views/shared/_menu.php index d9e1eb6..af7d660 100644 --- a/src/User/resources/views/shared/_menu.php +++ b/src/User/resources/views/shared/_menu.php @@ -32,6 +32,10 @@ use yii\bootstrap\Nav; 'label' => Yii::t('usuario', 'Permissions'), 'url' => ['/user/permission/index'], ], + [ + 'label' => Yii::t('usuario', 'Rules'), + 'url' => ['/user/rule/index'], + ], [ 'label' => Yii::t('usuario', 'Create'), 'items' => [ @@ -47,6 +51,10 @@ use yii\bootstrap\Nav; 'label' => Yii::t('usuario', 'New permission'), 'url' => ['/user/permission/create'], ], + [ + 'label' => Yii::t('usuario', 'New rule'), + 'url' => ['/user/rule/create'], + ], ], ], ],