Restrict Users To Only Editing/deleting Their Own Entries

I’d like to enable users to being able to only delete/edit their own entries and not those of others.

I’ve looked around for this question as I’m having a hard time figuring it out through the Wiki and, while having found some snippets and possible solutions, couldn’t get it to work.

I’ve tried something among the lines of this (my model is called ‘Song’), but couldn’t get it to work:


        public function accessRules()

        {


                return array(

                        array('allow',  // allow admin to perform 'index' and 'view' actions

                                'actions'=>array('index','view'),

                                'users'=>array('@'),

                        ),

                        array('allow', // allow specific user to perform 'update' actions

                                'actions'=>array('update'),

                                //'users'=>array('@'),

                                'users'=>array(Yii::app()->user->name),

                                'expression' => '(Yii::app()->user->id == ($_GET["id"]))',

                        ),

                        array('allow', // allow admin user to perform 'admin' 'create' and 'delete' actions

                                'actions'=>array('admin','delete','create','update'),

                                'users'=>array('admin'),

                        ),

                        array('deny',  // deny all users

                                'users'=>array('*'),

                        ),

                );

        }

I’ve also tried this which didn’t work either:


	public function filters(){

		return array(

			'songowner + edit delete',

		);

	}

	public function filterSongowner($chain){

		$songmodel = Song::model()->findByPk(Yii::app()->request->getParam('id'));

		if(Yii::app()->user->getId() === $songmodel->userid) {

			$chain->run(); // allows access

		} else {

			new CHttpException(403, 'You cannot modify other authors songs.');

		}

	}

Who can help settle this issue once and for all step-by-step for the Yii newbies that can’t figure it out themselves? :lol:

in your model maybe something like this?

I could be way off, but i think in theory something like this could work? Assuming you store the creator of a file in your DB




public function validateUser($attr,$params) {

    // Allow the owner of a file to update a file

    $allowed = array(Yii::app()->user->getId());

    

    // Make sure the person is an allowed user

    if(!in_array($this->user, $allowed) {

 		$this->addError('user', 'Only the owner can edit a file');

    }

}

// and in your rules array

public function rules() {

    return array(

    // Other rules.

        array('user', 'validateUser'),

    );

}



for once yes bur for all not sure :P

I would try to address this with a scope:

http://www.yiiframework.com/doc/api/1.1/CActiveRecord#scopes-detail

In your Song model define an own scope:




public function scopes()

{

return array(

  'own'=>array(

    'condition'=>'userid=' . Yii::app()->user->id,

  ),

);

}



and then select your data like this:




Song::model()->own()->findAll();



Not tested but something like this should work! Hope it helps to get you at least started!

I like this approach, would it prevent someone guessing a url like

http://www.yiiframework.com/demos/blog/index.php/post/update?id=5

And actually prevent them editing a record they don’t necessarily own? Or do you need a rule also?

Cheers!

All you have to do is check before editing like this:




$song = Song::model()->own()->find(.....);

if($song === null)

throw new CExpection('You do not own this post!');


$song->parameter = 'bla bla'; // updating

$song->save(); // saving



OK Cool.

Thanks!

This seems like a good solution! I’m having a bit of trouble integrating it though. I tried this using the scope and putting the code in the beforeSave function but this doesn’t work.


class Song extends CActiveRecord

{

	//Owner scope

	public function scopes()

	{

	return array(

	'own'=>array(

	   'condition'=>'userid=' . Yii::app()->user->id,

	  ),

	);

	}	

	

	//BEFORE SAVING

	protected function beforeSave()

	{

	if(parent::beforeSave())

	{

	    if($this->isNewRecord)

	    {

	        $this->date=date("Y-m-d");

	        $this->userid=Yii::app()->user->id;

	    }

	    else

		$song = Song::model()->own()->findAll();

		if($song === null)

		throw new CExpection('You do not own this post!');

	       return true;

	}

	else

	return false;

	return parent::beforeSave();

	}


...

How would one correctly integrate this?

You could correctly integrate this by using if…else correctly ;) Try putting some curly braces after else such as:




else{

                $song = Song::model()->own()->findAll();

                if($song === null)

                throw new CExpection('You do not own this post!');

               return true;

}



I tried adding brackets but the result remains the same: users can still edit other people’s entries. The date does get set on new entries so beforeSave does work, just not the part where it checks if the user owns it.

In your index this will show only songs that belong to the current user.


<?php

	$current_user = Yii::app()->user->id;

	$criteria = new CDbCriteria();

	$criteria->together = TRUE;//for lazy loading leave out for eager loading

	$criteria->with = array('user', 'song');//these are your defined relations in your model

	$criteria->condition = 'songowner =:songowner';//if you get an error saying ....ambiguous then in front of song owner add t. all it means is something else is using songonwer so the condition would be 't.songowner=:songowner'. your condition can also be an array . i.e. $criteria->condition= array('songowner =:songowner' AND other condtions);

	$criteria->params = array(':songowner'=>$current_user);

	$dataProvider = new CActiveDataProvider(Song::model(), array('criteria'=>$criteria));

	$this->widget('zii.widgets.CListView', array(

		'dataProvider'=>$dataProvider,

		'itemView'=>'_view',

		'id'=>'downloadsListView',

		'pager'=> array(

			'header' => '',

			'footer' => '',

			'firstPageLabel' => 'First Page',

			'prevPageLabel'  => 'Prev. Page',

			'nextPageLabel'  => 'Next Page',

			'lastPageLabel'  => 'Last Page',

		),

		'template'=>'{sorter}{items}{pager}',

		'emptyText'=>'You do not have any songs at this time.',

	)); ?>



This renders the _view file like default. Change the styles around to make it something like you want.

The above code is what I use for users to view their own downloads in my ‘downloads’ view. here is what it looks like. If they don’t have any that they purchased it doesn’t show anything.

4169

downloads.jpg

as far as someone guessing the url and letting users update their own songs you could do something like this in the controller. Note i use yii user so you would have to user your way of checking if the current user is admin.




	public function loadModel($id)

	{

		$model=Downloads::model()->findByPk($id);

		if($model===null){

			$errormessage= 'The system is unable to find the requested license certificate.';

			Yii::app()->user->setFlash('errormessage', $errormessage);

			$this->redirect(array('../shop/downloads/'));

		}

			

		if(Yii::app()->getModule('user')->isAdmin() or Yii::app()->user->id == $model->user_id){

			return $model;

		}

		else {

			$errormessage= 'The system is unable to find the requested  license certificate.';

			Yii::app()->user->setFlash('errormessage', $errormessage);

			$this->redirect(array('../shop/downloads/'));

		}

	}



Your rules could be something like this




public function accessRules()

	{

		//calls before access rules so it is placed here

		if(Yii::app()->user->isGuest){

			$this->redirect(array('../shop/'));

		}//if users shouldn't be able to view anything in this then leave otherwise take out.

		return array(

			array('allow', 				'actions'=>array('create','update','index','view'),

				'users'=>array('@'),

			),

			array('allow', // allow admin user to perform 'admin' and 'delete' actions

				'actions'=>array('admin','delete'),

				'users'=>array('admin'),

			),

			array('deny',  // deny all users

				'users'=>array('*'),

			),

		);

	}



If the system can’t find say view/id/24 it just redirects. or if view/id/24 isn’t theirs or they are not admin it redirects and displays the same message ( for security reasons i like it to show the same messages so people don’t guess too hard).

As far as all of stuff in your before save can be taken out because the above controller changes will not let the user even look at it to update it if it isn’t theirs.




 protected function beforeSave()

        {

            if($this->isNewRecord)

            {

                $this->date=date("Y-m-d");

                $this->userid=Yii::app()->user->id;

            }

  

        return parent::beforeSave();

        }




Some of it is just off the top of my head. however i just tried it and it all works like you are requesting in your post.

When checking the record you should check the API documentation what it returns. For example if($song === null) works only if you use find() because find() returns null if no record is found. But findAll() returns an empty array if no results are found so you should check differently such as if(empty($song))…etc

There are many ways to do it, but some may turn very ugly in the future. For instance putting




   $song = Song::model()->own()->findAll();

   if($song === null)

      throw new CExpection('You do not own this post!');



may work on a specific action, but then what if the admin who should have the right to edit any entry? We might then have to put conditional statement to separate between admin and non-admin. Things can get very complicated and ugly if your rule becomes complicated and when you have many actions that you need to deal with. For instance, you have view and update actions. That mean the same code has to exist in both controllers.

In my opinion, a better approach is to make use of Yii controller accessRules method. Once applied successfully, it will work across the board in all the actions.

Here is what I do




    public function accessRules()

    {

	$admin = array(); 

	if(Yii::app()->user->isAdmin()) //always allow admin to access

		$admin[] = Yii::app()->user->getName();

	//here is the part where you check for self

	$id = Yii::app()->request->getParam('id');

	$self = $this->getSelfAccess($id); //only allow for admin or self 

        return array(

            array(

                'allow', // allow authenticated user to perform 'create' and 'update' actions

                'actions' => array(

                    'view',

                    'update',

		    'changePassword',

                ),

                'users' => $self

            ),

            array(

                'allow', // allow admin user to perform 'admin' and 'delete' actions

                'actions' => array(

                    'index',

                    'create',

                    'admin',

                    'delete',

                ),

                'users' => $admin

            ),

            array(

                'deny', // deny all users

                'users' => array(

                    '*'

                )

            )

        );

    }



Within my controller




	protected function getSelfAccess($id)

	{

		$allow = array();

		if(Yii::app()->user->isAdmin()) //always allow admin to access

			$allow[] = Yii::app()->user->getName();

		if(Yii::app()->user->isUser()) //if it is user, check if it is self

		{

			if($id == Yii::app()->user->id)

				$allow[] = Yii::app()->user->getName();

		}

		return $allow;

	}



This is a simple example where user will allow to view, update his own information and also change his own password. You can always modify the getSelfAccess for more complicated validation like editing own blog post, etc