I'm using CakePHP's Auth component and I'm wondering whats the best way to allow the user to decide whether or not they give public access to their own page, ie. /user/username/public. So i'd have a checkbox in their profile page and save a 1 or a 0 in the DB for a "Public" field. However, whats the best way to allow the Auth component to conditionally allow access to this page?
That's not really a job for the Auth component, but for custom code.
public function userProfile($username) {
$profile = $this->Profile->find('first', array('conditions' => array(
'username' => $username,
'public' => true
)));
if (!$profile) {
$this->cakeError('error404');
}
...
}
You could use an isAuthenticated callback, but passing around the condition would become somewhat messy. The Auth component is handling general authentication, this case is too specialized to cleanly handle it using Auth.
Related
Iam writing an application with cakephp where i will have admin and agents where they can login to the system. Admin will have different layout from the agents. I have already create the the users table where i added a role field (admin,agent) ,i added the prefixes in core.php
Configure::write('Routing.prefixes', array('admin','agent'));
I managed to create the login and the logout for admin, but still iam confused how i should proceed with the rest. For Example i dont understand how beforeFilter() and isAuthorized() functions works. How i can check if user has access to that function or not. Also the redirections if a someone try to access this page domain.com/admin to be redirected to admin/login page .
Thanks.
Use the beforeFilter() to control access to each action, the below example will only allow access to the view and index action - any other action will be blocked :
$this->Auth->allow('view', 'index');
if you want to allow access to all the actions in your controller , try this in your before filter:
$this->Auth->allow();
To control who has access to what you could use a simple function in your app controller like so:
protected function _isAuthorized($role_required) {
if ($this->Auth->user('role') != $role_required) {
$this->Session->setFlash("your message here...");
$this->redirect("wherever you want the user to go to...");
}
}
In your controller action, eg. admin_delete on the first line you would do the following:
$this->_isAuthorized('admin');
Finally the redirect works like so:
$this->redirect(array('controller' => 'home', 'action' => 'dashboard'));
if you are redirecting within the same controller simply do the following:
$this->redirect('dashboard');
Hope this helps.
What i usually do is extend my App controller into an AdminAppController and SiteController , in the AdminAppController I have the following code in my beforeFilter:
$controller = strtolower($this->params["controller"]);
$action = strtolower($this->params["action"]);
$crole = $this->Auth->user("role");
$allowed = false;
$roles = array(
"all"=>array("user#login","user#register","user#forgot"),
"admin"=>array("pages#index","pages#view")
);
if(in_array($controller."#".$action,$roles["all"])){
$allowed = true;
}else{
if(in_array($controller."#".$action,$roles[$crole])){
$allowed = true;
}
}
if($allowed==false){
$this->setFlash("Access denied message...");
$this->redirect("...");
}
Don't know if this is the best practice but it works just fine. I normally hate CakePHP's built in Authorization system.
To check for allowance per role, I think it's best to use the Auth->allow([...]) in a per controller basis.
I find it best to check in Controller::beforeFilter() with a:
switch ($role) {
case 'admin':
$this->Auth->allow(...); //Allow delete
//notice no break; statement, so next case will execute too if admin
case 'manager':
$this->Auth->allow(...); //Allow edit
case default:
$this->Auth->allow(...); //Allow index
}
While you can check in AppController, I don't want to remember to change two files when I edit just one.
Is there a way to bind an auth error message to the scope condition of the auth component?
For instance say I have:
'Auth' => array('authenticate' => array('Blowfish' => array('scope' => array('User.activated' => 1))));
I would like set an error if the scope condition fails. I need to be able to differentiate that from the error that is presented if the user/pass is incorrect.
Is this possible?
Right, I've had a root around CakePHP's source code and I've come to the conclusion that it doesn't do anything fancy when using scoped conditions, it simply appends them as additional query conditions. It will either find the user which matches the username/password combination and any scope conditions, or it doesn't.
One possible solution would be to manually log users in and check for the activated field like so:
public function login()
{
if ($this->request->is('post')) {
if ($this->Auth->login($this->data['User'])) {
// check activated field
if ($this->Auth->user('activated') == 1) {
// user is activated
$this->redirect(...);
} else {
// user is not activated
// log the user out
$this->Auth->logout();
// redirect to an error page for inactive users
$this->redirect(..);
}
}
// redirect to an error page for wrong username/password
$this->redirect(..);
}
}
I should clarify that you should not specify scope conditions in configuring the authentication component.
I hope this helps!
In my design, an action has a rank, 0-10 required to access it (0 being a guest [not logged in], 10 being an admin). Each user has a rank, 0-10. If you have the rank or higher, you can access the action, otherwise you can't. Nice and simple, it's all I need.
The problem is that CakePHP wants me to treat actions with two separate concepts. I have to mark them Auth->allow/deny to determine if the auth system even bothers with them, and then I control access to them with isAuthorized().
isAuthorized works great for my needs... except that any action I want to have access of rank 0 to has to be Auth->allow()... which then ignores my isAuthorized method completely. If I deny all the pages, the login triggers on the pages that should be rank 0, before it checks isAuthorized, so even if I grant authorization through it, the person has to log in first.
Is there any way to merge the two systems together, or a simple way to replace it? Most of the auth system is great, and takes care of business for me without me having to mess with it... but this is just awkward and is going to cause problems when I don't notice a mixed up allow/deny or something.
Thanks!
As far as I see it, the only way to do this is to create a guest user. This is because the Auth component checks the existence of a user before ever getting to isAuthorized() like you explained.
You can do this by writing directly to the session. This will tell the Auth component that someone is logged in, so your isAuthorized() method will be called.
AppController
public function beforeFilter() {
// if no one is logged in, log in a guest
if (!$this->Auth->user()) {
$this->Session->write(AuthComponent::$sessionKey, array(
'User' => array(
'id' => 0
)
));
}
}
public function isAuthorized($user) {
$authorized = false;
if ($this->Auth->user('id') == 0) {
// public guest user access
}
// other logic
return $authorized;
}
A possibly better way to do this is to use a custom authentication object, which basically tells Cake to use that class to help authenticate. This splits the logic into a separate class, making it easier to test and even disable.
app/Controller/Component/Auth/GuestAuthenticate.php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class GuestAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
// no real authentication logic, just return a guest user
return array('User' => array('id' => 0));
}
}
AppController
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form',
'Guest' // tell Cake to try Form authentication then Guest authentication
)
)
);
public function beforeFilter() {
if (!$this->Auth->user()) {
// no user? log in a guest (this will fail form authentication
// then try guest authentication)
$this->Auth->login();
}
}
public function isAuthorized($user) {
$authorized = false;
if ($this->Auth->user('id') == 0) {
// public guest user access
}
// other logic
return $authorized;
}
You can find more information on custom authentication objects here: http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
Maybe this has already been dealt with satisfactorily but I have a solution.
In my AppController I have the following:
public function isAuthorized($user = null) {
if (in_array($this->action, $this->Auth->allowedActions)) {
return true;
}
return false;
}
As you've found, if you don't explicitly authorize actions, they are denied to authenticated users, even if they are allowed to the public. This snippet of code just makes the isAuthorized() method honour the settings in the Auth->allow() list.
It seems to work for me, so I hope it helps.
Please bear with me, as I'm a programming/cakephp noob, but I do not know how to restrict the user from access to other user data. When a user logs in, they get a dashboard of their listings (which come from several models....restaurants, hotels, golf courses, images for each listing, location information, coupons, etc..).
Depending on the model/controller, I could have user '3' (who has hotel listings) type into the browser bar /restaurants/edit/1 and edit the restaurant information of user '17' who has a restaurant with ID='1'. Worse, they can even access /users/dashboard/17. How do I limit a user to only access their own data? I was hoping there was some sort of 'beforeAllow()' part of the AuthComponent I could use in the AppController that checks user id beforehand and kicks them back out to their dashboard if they try to perform a CRUD action on other users' data.
Even if I was using ACL (I know I should but it's frankly a little too over my head at this stage of learning), I'd still have to know the right code to limit user access, correct?
Below is my AppController:
class AppController extends Controller {
public $components = array(
'Session',
'Auth' => array(
'loginRedirect' => array('controller' => 'users', 'action' => 'view'),
'logoutRedirect' => array('controller' => 'docs', 'action' => 'index'),
'authError' => 'Sorry, you are not authorized to view this page.'
)
);
function beforeFilter() {
$this->Auth->userModel = 'User';
$this->Auth->allow('join_now','debug','index', 'condos', 'houses', 'hotels_and_motels', 'print_all_coupons', 'print_coupon', 'search', 'golf', 'charters', 'events', 'nightlife', 'shopping', 'visitors_info', 'contact_us', 'view', 'results');
}
}
and here is a sample edit function (the edit function from my UnitsController):
function edit($id) {
$this->set('title', 'Edit your property');
$this->Unit->id = $id;
if (empty($this->request->data)) {
$this->request->data = $this->Unit->read();
} else {
if ($this->Unit->saveAll($this->request->data)) {
$this->Session->setFlash('Your property has been updated.', 'success');
}
}
}
I will say that every one of my db tables has a user_id field so the logged in user can be matched with the user_id of each model.
I thought that this SO question was what I was looking for but they ended up getting off on a tangent in it and never answered the original question the user asked.
If you where using cakes acl and auth, then no, you wouldn't have to write a bunch of code, checking the user ids for each action, but you'd have to write the code to tie together the acl's. You'd tell the Component that your controller and actions require acl privelages. And it doesnt the look ups in the aros and acos tables to make sure that your object requesting the content has the proper permissions.
I HIGHLY recommend you take a look at the tutorial and figure out how to get it to work
If you don't go that route, then you will have to add in the checking to every action that loads dependent content. Basically you'll, when a action is requested, you'll feature the object, then get the user associated to that object and check to see if the id of the user the same as the id as the requesting the object.
if ( $this->Unit->User->uid != $this->Session->User->uid ) {
throw new NotFoundException('Could not find that Unit');
} else {
...
}
The other thing you can do, for pages that are the same, but customized, is not use the url /user/dashboard/17 and instead just use /user/dashboard then in the dashboard action, pull the user id from the session data and load the profile for the user that is authenticated
I think you will need to use CRUD authorization for adding EditOwn actions. The answer described in CakePHP ACL Database Setup: ARO / ACO structure? handles much of the logic that you need.
Please note that the solution is still not complete and you will get access to actions that can render other users' data. Example: scaffold 'index' methods. To restrict access to other users' data here, you can modify the query to add filters based on User ID that you get from Session.
I want to prevent banned users from logging in to the site and give them a message that they are banned. I tried to use isAuthorized() for this but it allows the user to login and only after that denies him permission to the unauthorized actions.
So, basically I want to know where to put the condition that would check if the user table as banned = true, before the login process takes place. Right now my login function is empty as its being automatically controlled by the Auth Component.
Finally, I found a solution by going through the API. I wonder if anyone has used this ever, cause nobody pointed me to this, or maybe I wasn't clear enough. Anyways, to add a condition to the login process you just have put it in the variable $this->Auth->userScope
So, to check if a user is banned I just added this line to the beforeFilter() in my AppController,
$this->Auth->userScope = array('User.banned'=>0);
Hope this helps someone.
Alternatively to:
$this->Auth->userScope = array('User.banned'=>0);
This can be done when you include your Auth Component. This probably saves some tiny amount of overhead as $this->Auth->userScope isn't called every time a controller is parsed.
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'passwordHasher' => 'Blowfish',
'scope' => array('User.banned' => 0)
)
),
'authorize' => array('Controller')
)
);
If you have the whole Auth system already up and running, why don't you just follow the KISS principle and revoke their password or alter there username? If they are not longer able to authenticate with your system as they could earlier they should be able to deduce that they are banned.
If that doesn't suffice, then additionally you could add the code below.
function login() {
if ($this->Session->read('Auth.User')) {
$this->Session->setFlash('You are alreadylogged in!~~~~~~~~~~~');
}
$this->Session->setFlash('You have been banned!');
$this->redirect(array('controller'=>'users','action'=>'index'));
}
Edit 1: For a more dynamically approach like you pointed out in your comment, you could check the is_banned column of the user record under concern in your UsersController::beforeFilter() and set your flash message accordingly. Also make a redirect based on the outcome of $this->Session->read('Auth.User.is_banned'). Maybe you want to have a look at the output of <?php debug $this->Session->read('Auth.User) ?> before attacking your problem.
Edit 2: My fault. You could store the is_banned somewhere in the Session via $this->Session->write(...). After you read an is_banned = true you can log the user out.
you have to use:
/** Function is executed after the login*/
function isAuthorized() {
return true;
}
where you can check if the user is banned or no. i.e.
/** Function is executed after the login*/
function isAuthorized() {
if($this->Auth->user('banned') == 1){ //column banned should be in the users table
$this->Session->setFlash('You have been banned!');
return false;
}
return true;
}
I believe this is the correct way.
Having read your last comment on Nik's way, I think that you could just refine your original solution by logging the user out manually via $this->Auth->logout() at the appropriate place in your code (followed by a redirect). This way it should look like he/she never logged in.