I have some question about my app. How can I prevent a user from viewing another user profile?
I mean, a user can only view their own profile. If user 123 is logged in, he is allowed in 'view&id=123' but denied in 'view&id=234' etc.
I know how to do this from the view page, but, am I able to deny it from my Controller? I'm thinking to use expression but I can't get it right. Here is my code
array('deny',
'actions'=>array('view'),
'expression'=>'$user->getName()!=??',
),
What is the right expression to replace the '??' part?
I tried to use $id (the actionView parameter) but it showed me an error..
I usually do this at controller level, I do something like this:
public function actionView($id){
if($id != Yii::app()->user->id) {
throw new CHttpException('403','You are not allowed to see this page');
}
//Continue with the code
}
Assumming that you've stored logged user id at the Yii:app()->user variable.
Also this might work:
array('deny',
'actions'=>array('view'),
'expression'=>'$_GET["id"] != Yii::app()->user->id',
),
Use option allow make it easier to get what you want.
'actions' => ['view', 'list-post', 'unsubscribe'],
'allow' => $_GET['id'] == Yii::$app->user->id,
The best practice is to create a custom filter (in application.components.controller, if you use it more than once):
public function filterCheckOwner($filterChain)
{
if ($id=Yii::app()->getRequest()->getParam('id'))
{
if($id != Yii::app()->user->id)
throw new CHttpException('403','You are not allowed to see this page');
else
$filterChain->run();
}
}
And then just add the actions that must run it in the filters() method with the same name (without 'filter'):
public function filters()
{
return array(
...
'checkOwner + view, update, whatever',
...
);
}
Related
I'm running into some difficulty in using the added componenet requirelogin.php . This is the component that I've added
<?php
class RequireLogin extends CBehavior
{
public function attach($owner)
{
$owner->attachEventHandler('onBeginRequest', array($this, 'handleBeginRequest'));
}
public function handleBeginRequest($event)
{
if (Yii::app()->user->isGuest && !in_array($_GET['r'],array('site/login', 'site/index'))) {
Yii::app()->user->loginRequired();
}
}
}
?>
Note how 'site/index' is allowed in this as a page that I can visit.
Now, in my main.php I added the following
'behaviors' => array(
'onBeginRequest' => array(
'class' => 'application.components.RequireLogin'
)
),
Now, these two force me to go to site/login everytime - even though I have done as other stackoverflow answers have told me to and added
// sets the default controller action as the index page
'defaultController' => 'site/index',
Could anyone explain why this hasn't made my start page site/index?
_____________ ADDITION
I've also figured out that when I am going to the base action (i.e. mywebsite.com) it is NOT going to site/index. rather it is directly redirecting to site/login. This, however, does not occur, when I comment out the behaviors .
So, thank you darkheir for all of your work on this.
Ultimately, what happened was the $_GET['r] in the base path was simply '' because r was not point to anything in particular. As a result, when ever I looked in_array($_GET['r']) what I was getting in the very bae path was in_array('', array('site/login', 'site/account')) Now, unfortunately, '' is neither site/login or site/account so the page redirected using
Yii::app()->user->loginRequired();
The fix to this problem was
public function handleBeginRequest($event)
{
// note that '' is now one of the options in the array
if (Yii::app()->user->isGuest && !in_array($_GET['r'],array('site/login', 'site/index', '' ))) {
Yii::app()->user->loginRequired();
}
}
So, i am new in Zend Framework, i installed ZfcUser and i want when the user is not logged to didn't access to some routes for example : /blog/article/add,
actually i use <?php if($this->zfcUserIdentity()) :?> to check if the user is logged but how can i redirect the user to my login route/user if is try to access to /blog/article/add,
so plz if someone has any idea i will be very appreciative :)
I disagree with the selected answer because it's not really practical to do these kind of things inside every action of every controller that you want to deny access to.
There are two big modules out there that are used for this task. The first one being BjyAuthorize and the other big one being ZfcRbac. Please check them out. ZfcRbac is my favorite (because i wrote documentation for it) but it requires PHP 5.4+
Simple Way:
in controller:
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute('route_to_login',array('param' => $param));
}
Some more:
(after auth zfcuset will redirect you to previous controller)
in module.config.php
'controllers' => array(
'invokables' => array(
'zfcuser' => 'Application\Controller\UserController',
),
...
),
next copy file vendor\zf-commons\zfc-user\src\ZfcUser\Controller\UserController.php
to module Application (same folder as in module.config.php)
delete all function except authenticateAction
add:
use Zend\Session\Container;
and change
return $this->redirect()->toRoute($this->getOptions()->getLoginRedirectRoute());
to
$redirect_session = new Container('redirect');
$redirect = $redirect_session->redirect;
if($redirect!='')
{
unset($_SESSION['redirect']);
return $this->redirect()->toRoute($redirect, array('lang' => $lang));
}
return $this->redirect()->toRoute($this->getOptions()->getLoginRedirectRoute(),array('param' => $param));
and at last add in controller
use Zend\Session\Container;
...
if (!$this->zfcUserAuthentication()->hasIdentity()) {
$redirect_session = new Container('redirect');
$redirect_session->redirect = 'route_to_this_controller_action';
return $this->redirect()->toRoute('route_to_login',array('param' => $param));
}
I'm currently busy with a project that needs users to go to a specific page to create a profile when they log in for the first time (and haven't created one yet). Honestly, I don't know where to start. I would like to do it in a good way.
So in short:
User signs up -> logs in -> needs to fill in form before anything else is allowed -> continue to rest of application
Question: What is a neat way to do this? A solution that isn't going to give me problems in the future development of the application.
I suggest you to use filters. In every controller where the completed profile is neeeded add this code:
public function filters() {
return array(
'completedProfile + method1, method2, method3', // Replace your actions here
);
}
In your base controller (if you don't use base controller, in any controllers) you need to create the filter named completedProfile with the simular code:
public function filterCompletedProfile($filterChain) {
$criteria = new CDBCriteria(array(
'condition' => 'id = :id AND firstname IS NOT NULL AND lastname IS NOT NULL',
'params' => array(':id' => Yii::app()->user->getId())
));
$count = User::model()->count($criteria);
if ($count == 1) {
$filterChain->run();
} else {
$this->redirect(array('user/profile'));
}
}
Possibly add a field to the user profile database table which denotes if they have filled out their profile information. Something like profile_complete. Then you can do a test on pages to see if profile_complete is true and display the page if so, and display the profile page if not.
Q : How can I show display different menu(s) by user role?
Description : the app has many roles. e.g HR manager, Account Manager, Operating Manager, Employee, Operator, ...., etc. I used rights and yii-user modules to create those roles. Those roles have different functions. So the app will show different menu for different user's role after logged in. Now, I can lock the function for different user. e.g when HR manger logged in, he/she can't route to other function of user role. But I don't know how to show the HR Menu for Hr Manager, only.
I am not a newbie for yii. but I'm a newbie for those modules (rihgts and yii-user).
If you're using RBAC you can set the 'visible' param of CMenu items depending on the users privileges, for example;
$this->widget('zii.widgets.CMenu',array(
'items'=>array(
array(
'label'=>'Home',
'url'=>array('site/index'),
),
array(
'label'=>'HR',
'url'=>array('/hr/index'),
'visible'=>Yii::app()->user->checkAccess('hr')
),
array(
'label'=>'Accounts',
'url'=>array('/account/index'),
'visible'=>Yii::app()->user->checkAccess('account')
),
array(
'label'=>'Operations',
'url'=>array('/operations/index'),
'visible'=>Yii::app()->user->checkAccess('operations')
),
),
);
This way users will only be able to see the items in the menu if they have access privileges for that area.
[EDIT]
As per simaremare's comment below, you can force caching of this query beyond the current request by extending CWebUser. Firstly, set your user to run through your new class (we'll call it TWebUser), so in your main.php config file;
'components'=>array(
'user'=>array(
...
'class'=>'TWebUser',
...
),
...
),
Now we need to create TWebUser to cache these beyond the current request (which is what CWebUser does (source code):
class TWebUser extends CWebUser
{
private $_access=array();
public function checkAccess($operation,$params=array(),$allowCaching=true)
{
if($allowCaching && $params===array() && isset($this->_access[$operation]))
return $this->_access[$operation];
$cache = Yii::app()->session['checkAccess'];
if($allowCaching && !$this->getIsGuest() && isset($cache[$operation]) && time() - $cache[$operation]['t'] < 1800)
{
$checkAccess = $cache[$operation]['p'];
} else {
$checkAccess = Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params);
if($allowCaching && !$this->getIsGuest())
{
$access = isset($cache) ? $cache : array();
$access[$operation] = array('p'=>$checkAccess, 't'=>time());
Yii::app()->session['checkAccess'] = $access;
}
}
return $this->_access[$operation] = $checkAccess;
}
}
Now your access results will be set for the whole session. This does mean that if you edit the RBAC permissions for a given account, they'll have to log out and log in again to see the new changes reflected in the browser.
I hope that helps! I'm sure I found this workaround from someone else (probably on SO), but I can't find the original post to give them credit.
You would wrap an if-statement around the menu-items that you may or may not want to show.
In the if-statement, you would have to test whether the user qualifies to see your menu-items.
For example:
<?php if (Yii::app()->user->isGuest): ?>
<?php $this->widget('zii.widgets.CMenu',array(
'items'=>array(
array(
'label'=>'this menu item visible only for Guests (not logged in)',
'url'=>array('/site/index')),
),
)); ?>
<?php endif; ?>
I am working on an Open Source Role Based Access Control Library for PHP called PHP-Bouncer. PHP-Bouncer allows the user to define a list of roles, which pages each role provides access to, and each role may also define a list of pages which override other pages (so going to the overridden page will redirect you to the overriding page). Here is an example of how this would work (From the Access Managed Example in the documentation):
$bouncer = new Bouncer();
// Add a role Name, Array of pages role provides
$bouncer->addRole("Public", array("index.php", "about.php", "fail.php"));
// Add a role Name, Array of pages role provides
$bouncer->addRole("Registered User", array("myaccount.php", "editaccount.php", "viewusers.php"));
// Add a role Name, Array of pages role provides List of pages that are overridden by other pages
$bouncer->addRole("Admin", array("stats.php", "manageusers.php"), array("viewusers.php" => "manageusers.php"));
// Here we add some users. The user class here extends the BouncerUser class, so it can still do whatever you
// would normally create a user class to do..
$publicUser = new User();
$registeredUser = new User();
$adminUser = new User();
$registeredAndAdmin = new User();
$publicUser->addRole("Public");
$registeredUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
$registeredUser->addRole("Registered User");
$adminUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
$adminUser->addRole("Admin");
$registeredAndAdmin->addRole("Public"); // We add the public group to all users since they need it to see index.php
$registeredAndAdmin->addRole("Registered User");
$registeredAndAdmin->addRole("Admin");
$bouncer->manageAccess($publicUser->getRoles(), substr($_SERVER["PHP_SELF"], 1), "fail.php");
Here's the problem I am having: In the manageAccess function you see above, everything works well as long as the roles are defined sanely, and all users have access to fail.php (or fail.php does not implement the $bouncer object). As soon as someone creates a role which has an inherent conflict (such as overriding a page with itself) or fails to give all users access to the failure page, the manageAccess function results in an infinite loop. Since this is bad, I would like to fix it. I am not sure however, what would be the best approach for allowing a few redirects (it is feasible that redirecting up to two or three times could be desired behaviour) while preventing an infinite loop. Here is the manageAccess function:
/**
* #param array $roleList
* #param string $url
* #param string $failPage
*/
public function manageAccess($roleList, $url, $failPage = "index.php"){
$granted = false;
foreach($roleList as $role){
if(array_key_exists($role, $this->roles)){
$obj = $this->roles[$role];
/** #var $obj BouncerRole */
$response = $obj->verifyAccess($url);
if($response->getIsOverridden()){ // If access to the page is overridden forward the user to the overriding page
$loc = ($obj->getOverridingPage($url) !== false) ? $obj->getOverridingPage($url) : $failPage;
$locationString = "Location: ".$loc;
header($locationString);
// I broke something in the last commit, perhaps this comment will help?
}
if($response->getIsAccessible()){ // If this particular role contains access to the page set granted to true
$granted = true; // We don't return yet in case another role overrides.
}
}
}
// If we are here, we know that the page has not been overridden
// so let's check to see if access has been granted by any of our roles.
// If not, the user doesn't have access so we'll forward them on to the failure page.
if(!$granted){
$locationString = "Location: ".$failPage."?url=".urlencode($url)."&roles=".urlencode(serialize($roleList));
header($locationString);
}
}
Any Suggestions?
Since your desired functionality is to allow "a few redirects", the best way I can think of approaching this is creating either a $_SESSION (or $_GET I suppose would work) parameter that you increment every time you issue a redirect. I should point out that from a user standpoint this would be pretty annoying to deal with, but it is your website.
Let's assume we named the $_SESSION parameter num_redirects:
Before we redirect, check to see if $_SESSION['num_redirects'] is set. If it is not, set it to 0.
Get the current value of $_SESSION['num_redirects'], and see if it is above the redirect threshold.
If it is above the threshold, do not redirect, simply die();
If it is not above the threshold, increment $_SESSION['num_redirects'], store it back, and redirect.
So, that code would look like this (I've also improved your URL query string with a call to http_build_query():
Somewhere, we need to define the threshold:
define( 'MAX_NUM_REDIRECTS', 3);
Then, we can do:
// Assuming session_start(); has already occurred
if(!$granted) {
if( !isset( $_SESSION['num_redirects'])) {
$_SESSION['num_redirects'] = 0;
}
// Check if too many redirects have occurred
if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
die( "Severe Error: Misconfigured roles - Maximum number of redirects reached\n");
}
// If we get here, we can redirect the user, just add 1 to the redirect count
$_SESSION['num_redirects'] += 1;
$query_string = http_build_query( array(
'url' => $url,
'roles' => serialize($roleList)
));
$locationString = "Location: " . $failPage . '?' . $query_string;
header($locationString);
exit(); // Probably also want to kill the script here
}
That's it! Note that you'll have to add the same logic for the first call to header(). If you're going to repeat this functionality elsewhere, it might be worthwhile to encapsulate redirection within a helper function:
function redirect( $url, array $params = array()) {
if( !isset( $_SESSION['num_redirects'])) {
$_SESSION['num_redirects'] = 0;
}
// Check if too many redirects have occurred
if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
die( "Severe Error: Maximum number of redirects reached\n");
}
// If we get here, we can redirect the user, just add 1 to the redirect count
$_SESSION['num_redirects'] += 1;
$query_string = http_build_query( $params);
$locationString = "Location: " . $url . ( !empty( $query_string) ? ('?' . $query_string) : '');
header($locationString);
exit(); // Probably also want to kill the script here
}
Then, you can call it like this:
if(!$granted){
redirect( $failPage, array(
'url' => $url,
'roles' => serialize($roleList)
));
}
This way you can encapsulate all of the logic necessary to redirecting within that one function.