I am doing an app that requires authentication. In the index page of the app, I specified access rules like this
public function accessRules() {
return array(
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
),
array('allow',
'users'=>array('*')
),
);
}
At the first rule, the actions 'index','register','login' and 'password' are made unaccessible to authenticated users. However, I do not want to show this message
Unauthorized
You are not authorized to perform this action.
You do not have the proper credential to access this page.
If you think this is a server error, please contact the webmaster.
...to the authenticated users when they try to access those actions. Instead, I want to redirect them to another page. It would be useful if i could do something like this at the first rule
array('redirect',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'url'=>array('home/index'),
),
Since Yii v1.1.11 you can do the same thing with a callback and a closure, and just the default classes:
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'deniedCallback' => function() { Yii::app()->controller->redirect(array ('/home/index')); }
),
They 'll make you an offer you can't refuse
Starting with Yii v1.1.11 CAccessRule defines the deniedCallback property that easily allows you to define a redirect when access is denied. I don't want to steal Iain Gray's thunder, so go upvote his answer (thanks to the commenter who alerted me to this as well).
The original answer follows.
Option 1: extend Yii to enable this functionality (correct)
To do this we will need to write our own classes to be used instead of CAccessRule and CAccessControlFilter. For CAccessRule we just need to add one additional property:
class MyAccessRule extends CAccessRule {
public $redirect; // just add this property
}
For CAccessControlFilter we want to make it recognize the value of this property and act upon it. To do this, we need to override the preFilter method. Starting from the stock implementation, make a few changes:
class MyAccessControlFilter extends CAccessControlFilter {
protected function preFilter($filterChain)
{
$app=Yii::app();
$request=$app->getRequest();
$user=$app->getUser();
$verb=$request->getRequestType();
$ip=$request->getUserHostAddress();
foreach($this->getRules() as $rule)
{
if(($allow=$rule->isUserAllowed($user,
$filterChain->controller,
$filterChain->action,
$ip,
$verb))>0) // allowed
break;
else if($allow<0) // denied
{
// CODE CHANGED HERE
$request->redirect($app->createUrl($rule->redirect));
return false;
}
}
return true;
}
}
Then we also need to override the setRules method, to instruct the filter to use the MyAccessRule class instead of the standard CAccessRule. Again, we modify the stock implementation by changing the line
$r=new CAccessRule;
to read
$r=new MyAccessRule;
After creating these classes, we have to also inject them into Yii's pipeline. To do this, override filterAccessControl on the base controller class; again, taking the stock implementation as reference and making a small change:
public function filterAccessControl($filterChain)
{
$filter=new MyAccessControlFilter; // CHANGED THIS
$filter->setRules($this->accessRules());
$filter->filter($filterChain);
}
That's it! You can now take advantage of the extra functionality in any controller by supplying the new redirect parameter to the access control filters like this:
public function accessRules() {
return array(
array('deny',
'actions'=>array('index','register','login','password'),
'users'=>array('#'),
'redirect'=>array('home/index'),
),
);
}
Option 2: implement access control inside each action (to be avoided)
If you are not comfortable with subclassing Yii's core components, another option that I do not recommend is to embed both access control and redirection logic inside each controller action that you want to protect, or overriding the beforeAction method on your controllers to cover multiple actions from one location.
this one worked for me since yii 1.1.11:
array('deny', // deny all users
'users'=>array('*'),
'deniedCallback' => $this->redirect('/')
),
or use a static method in a Class:
'deniedCallback' => array('ClassName', 'staticMethodName'),
$request->redirect($app->createUrl($rule->redirect));
Should be:
if(is_array($rule->redirect) && isset ($rule->redirect[0])){
$request->redirect($app->createUrl($rule->redirect[0]));
}
Related
While I type localhost/yiitest in the address bar I want to view the login page. I searched the web, but wasn't able to find out how to do this.
Can anybody help me?
If youre using yii1, add a filter to your controller:
public function filters()
{
return array('accessControl'); // perform access control for CRUD operations
}
public function accessRules()
{
return array(
array(
'allow', // allow authenticated users to access all actions
'users'=>array('#'),
),
array('deny'),
);
}
If not logged user try to access any page, he will be redirected to site/login (you can change it in config). Of course if u add this accessRules in SiteController you have to add additional rule to allow not logged in users to access actionLogin().
I'm using ZfcUser with ht-user-registration and scn-social-auth and there are 2 fairly minor enhancements I want to achieve to extend my implementation, but I'm not having much success so far:
ht-user-registration sends new users an email after registering and denies them access until they've activated their account, this is working fine. What I'd like to do to extend this is redirect the user after registration so that they are sent to a page telling them to check their e-mail rather than to the login page, which is the default behaviour of ZfcUser. I have tried adding an event listener to the module bootstrap that looks like this:
$response = $e->getResponse();
// indicate that we intend to redirect after register action
// set the redirection location to the home page
$response->getHeaders()->addHeaderLine('Location', 'home');
$response->setStatusCode(302);
$response->sendHeaders();
$em = \Zend\EventManager\StaticEventManager::getInstance();
$em->attach('ZfcUser\Service\User', 'register', function($event) use ($response) {
// don't allow anything else to process this event
$event->stopPropagation();
// return the redirect response
return $response;
});
This is called correctly but the redirect never happens and the user still ends up at the login page. Is there something else I need to do to execute the redirect? Or maybe there's a better way entirely to achieve this?
I'd like to add layout variables so that I can modify page titles and navigation in my layout template for the ZfcUser pages. In order to do this I made an override of the UserController from ZfcUser like this :
class UserController extends \ZfcUser\Controller\UserController
{
public function loginAction()
{
$this->layout()->setVariables(array(
'view_title' => 'Reports Login',
));
return parent::loginAction();
}
}
And then overrode the invokable for zfcuser in the config like this:
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController',
'zfcuser' => 'Application\Controller\UserController',
),
),
The framework tries to instantiate my UserController at the right point but fails with an InvalidArgumentException: 'You must supply a callable redirectCallback' which I can see is required to construct the base controller but doesn't get passed to my overridden version - any clues why not? I can't find a working example of this to help.
Maybe there's a much easier way to inject layout variables into another module's controller actions?
[EDIT] I have found a simple but not very elegant way of doing this. Since my module overrides the views for both login and registration then it's possible to set layout variables within the view, thus I was able to add a one liner to the top of each view to set the layout variable, e.g.:
<?php $this->layout()->setVariable('view_title', 'Register for an account'); ?>
This doesn't feel correct, but works. If there's a better solution I'd like to know.
I want to force all users to log in before accessing pages of my site. I have followed Larry Ullman's tutorial Forcing Login for All Pages in Yii.
According to the tutorial you can make an exception for some pages to avoid redirecting to the log in page. In order to check the current controller it has checked $_GET value. My problem is that I have used urlManager to rewrite the URL and $_GET gives me a null value. Is there any method I can use to get the current controller and action in the score of my class?
I tried the following but it is not accessible in the scope of my component class:
Yii::app()->controller->getId
Did you try:
Yii::app()->controller->id
and:
Yii::app()->controller->action->id
?
Yes you can get the current controller/action route, by reversing urlManager rule:
Yii::app()->urlManager->parseUrl(Yii::app()->request)
As now in Yii2
get current controller name
Yii::$app->controller->id
current controller object
Yii::$app->controller
current action name:
Yii::$app->controller->action->id
current route:
Yii::$app->requestedRoute
Using Yii2, obtain the current controller object with:
Yii::$app->controller
From the controller, obtain the current action as a string using:
Yii::$app->controller->action->id
In Yii2:
The problem of calling Yii::$app->controller->id is that when you call it somewhere (example: in one of your top-level abstract controller), Yii::$app->controller might not be instantiated yet, so it will return error.
Just directly call urlManager to map request to route:
var_dump(Yii::$app->urlManager->parseRequest(Yii::$app->request))
Try Yii::app()->controller->getRoute()
If I get you question correctly, you are basically trying to stop access to certain actions in the controller from being accessed without being logged in right?
If this is what you are after, the correct method to do it is this :
Make a actionMethod() in the controller like so :
class SomeController extends CController{
public function actionSomeAction(){
... More code...
}
After that, you can access the site using : path/to/application/controllerName/actionName
Now if you want to force the user to log in before accessing the action, do this :
Make an access control like so :
/**
* #return array action filters
*/
public function filters()
{
return array(
'accessControl', // perform access control for CRUD operations
);
}
/**
* Specifies the access control rules.
* This method is used by the 'accessControl' filter.
* #return array access control rules
*/
public function accessRules()
{
return array(
array('allow', // allow authenticated user to perform 'create' and 'update' actions
'actions' => array('**yourActionMethodName**'),
'users' => array('#'),
),
array('deny', // deny all users
'users' => array('*'),
),
);
}
Now only authenticated users would be able to access the URL.
I hope it solved your problem.
If you simply want to check if the user is a guest and if he is, send him to the login page everytime:
In the config/main.php, add the following :
'defaultController' => 'controllerName/actionMethod',
And in that controller just add the above access rule. Now, by default you are opening the site to an access controlled method. So it would automatically redirect you to the login page.
Even another method :
Just add this in the views/layouts/main.php
<?php
if(Yii::app()->user->isGuest)
{
$this->redirect('/site/login');
}
?>
if (Yii::$app->requestedAction->id == "index") {
//do something
}
i just read about creating operation for defining the authorization rules...there was a code in the book which is as follows
# protected/controllers/SiteController.php::actionSetup()
$auth = Yii::app()->authManager;
$auth->createOperation('createPage',"create a new page");
$auth->createOperation('updatePage',"update a page");
The first argument is the identifier that specifies the operation uniquely...second is the description of the operation....
NOW my QUESTION is
where is the ACTION attached to the operation..it just consists of the name and the description...?? Even if i assign this operation to a user....how will the rule identify that which action has been assigned to it???
You use the auth manager assign function to give users access to an auth item.
See http://www.yiiframework.com/doc/api/1.1/IAuthManager#assign-detail
E.g.
Yii::app()->authManager->assign('createPage', $idOfMyUser);
You can then use the access rules in a controller to control who can perform an action
Like:
public function accessRules()
{
array('allow',
'actions' => array('create'),
'roles' => array('createPage')),
array('deny'),
);
}
Note that Roles, Operations and Tasks are essentially the same thing, they are just a ways of seperating or groping auth items
I don't know the best approach for access rules of the creator of model in the controller. I usually using like this :
public function accessRules() {
return array(
...
array('allow', // allow authenticated user to perform 'create' and 'update' actions
'actions' => array('docrop', 'cropimages','upload','setting','updateprivacy','updateuser','changepassword'),
'expression' => array($this,'isCreator'),
),
...
);
}
And then in that controller I'm using this function to check the correct access rules
public function isCreator(){
$hasil=false;
if(isset($_GET['id'])){
$idUser=$_GET['id'];
$hasil=$idUser==Yii::app()->user->id?true:false;
}
return $hasil;
}
And then If I want to create the url I always use the id parameter in that url. Is this the best approach? Or there is an alternative ways that better than this?
Your current approach would allow users to change the id in the url, giving them access to all actions. If you really would like to keep this method, I suggest using some kind of hashing method to make it less brute-forceable in combination with e.g. his ip address for more security: $hashFromUrl == md5(Yii::app()->user->id . CHttpRequest::getUserHostAddress()). Nonetheless I discourage this approach.
As the method is called isCreator(), I assume that you want to check whether the current user is the creator/author of an existing model in the database. Can't you use a creatorId field for this model to compare against the current user's id? No client side hacks are required then.