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.
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.
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 have started learning cakePHP and now I am facing the following problem. How can allow my users to change their profile (their info in the DB).
This is my model -> http://bin.cakephp.org/view/798927304 I use these validations when someone tries to register .
This is my method for editing profiles: http://bin.cakephp.org/view/841227800
First I check does the user have the permission to edit this profile (is the profile his own).
Then get the desired id and try to save the request->data... But unfortunately without success.
And last this is my view -> http://bin.cakephp.org/view/1798312426
The only things I want to make are:
-Change their email (if they add new email)
-Change their social profiles (if the add them)
-Change their password (if they add it)
Can you guide me to do these things?
Thanks in advance!
Typically, when you call a save, as in:
$this->User->save($this->request->data)
The data in $this->request->data is cleared by default. In your edit method, you have an if statement below that is using the same save again.
The default functionality which I assume you have copied from a cakebaked edit method typically does the save and uses the returned logic to power that if statement. The second time you call it, you might be getting a false returned, which is likely skipping over that if statement.
To debug this, I suggest several of these in different locations:
debug($this->request->data);
Also, debug printouts are cleared on redirect, so in the mean time, you may want to comment out the redirect inside the if statement like so:
if ($this->User->save($this->request->data)) {
$this->Session->setFlash('Успешно променихте профила си.', 'success_message');
//$this->redirect(array('action' => 'profile'));
}
if($this->Auth->user() && $this->Auth->user('id') == $id) {
$this->User->read(null, $id);
$this->User->set(array(
'email' => $this->data['User']['email'],
'password' => $this->data['User']['password'],
'social_profile' => 'bla bla'
));
$this->User->save();
// etc
may do what you're after.
more # http://book.cakephp.org/view/1031/Saving-Your-Data
I've had a look at the other questions relating to this topic but none have been useful.
Expected Outcome
From a homepage with links to different web apps, the user clicks to access a web app. The user is redirected to the login page. I've tried to override the login function to auto-login network users based on a lookup on their IP address. Their machine name is stored as their username in the database and all the passwords are the same. This is validated and the user is automatically logged in and redirected to the web apps home page, with no need to see a login form.
Note: this auto-login works when accessing the web address directly, but this problem started when I added in the "dashboard" of web apps. However, this is required so there is only one shortcut on the users desktop.
routes.php Code
Router::connect('/', array('controller' => 'cns', 'action' => 'view'));
app_controller.php Code
function beforeFilter() {
$this->Auth->allow('display');
# Users need authorised before viewing these pages
$this->Auth->deny('index','view', 'add','edit','delete');
$this->Auth->loginRedirect = array( 'controller'=>'cns', 'action'=>'view');
$this->Auth->logoutRedirect = array( 'controller'=>'pages', 'action'=>'logged_out');
}
users_controller.php Code
function beforeFilter() {
$this->Auth->autoRedirect = false;
}
function login () {
# Automatic login based on computer name
$this->data['User']['password'] = $this->Auth->password('qwerty');
# Result: x-xxxxxx.domain.local (function declared below)
$full_host = gethostbyaddr($this->get_user_IP_address());
# Result: x-xxxxxx[.domain.local]
$position_of_split = strrpos($full_host, ".gmi.local");
# Result: x-xxxxxx
$computer_name = substr($full_host,0,$position_of_split);
# If username is not been provided, use the computer name discovered by auto-login
(!isset($this->data['User']['username']) ? $this->data['User']['username'] = $computer_name : null);
# If login is successful, redirect to view. Otherwise, echo error message.
if($this->Auth->login($this->data)){
$this->redirect(array('controller' => 'cns', 'action' => 'view'));
} else {
echo "Unable to log you in. Please advise IT."; die;
}
}
function get_user_IP_address() {
if (isset($_SERVER)) {
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
return $_SERVER["HTTP_X_FORWARDED_FOR"];
if (isset($_SERVER["HTTP_CLIENT_IP"]))
return $_SERVER["HTTP_CLIENT_IP"];
return $_SERVER["REMOTE_ADDR"];
}
if (getenv('HTTP_X_FORWARDED_FOR'))
return getenv('HTTP_X_FORWARDED_FOR');
if (getenv('HTTP_CLIENT_IP'))
return getenv('HTTP_CLIENT_IP');
return getenv('REMOTE_ADDR');
}
If you need further code or explanation, just ask in the comments.
Thanks in advance. :)
I don't know if you found the solution to your problem but I had the same problem and found the following article which describes the cause and provides workaround.
Infinite loop login redirect