I am working on a Cakephp 2.3 on a very big project and I'm about to launch my site worldwide.
I have a login system on my app. I am sharing my code because I want to make sure if I am coding right or not ... and also any check for any functions missing or if any advice of adding something or removing something in the code would be greatly appreciated. And also comment in security perspective too...
Do tell me some tips of making my website faster.. for example how to write faster queries or remove unwanted from this blabla
class UsersController extends AppController
{
public $components = array('Cookie');
public function beforeFilter()
{
parent::beforeFilter();
App::uses('Utility', 'Utility');
$this->Auth->allow('index');
$this->Security->requireSecure('login'); // for security
$this->Auth->authenticate = array(
'Authenticate.Cookie' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
),
'userModel' => 'User',
'scope' => array(
'User.active' => 1
)
),
'Authenticate.MultiColumn' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
),
'columns' => array(
'email',
'mobileNo'
),
'userModel' => 'User'
)
);
}
public function index()
{
$this->layout = 'logindefault';
if (!$this->Auth->login() || !$this->Auth->loggedIn()) {
$this->redirect(array(
'controller' => 'users',
'action' => 'login'
));
} else {
$this->redirect(array(
'controller' => 'users',
'action' => 'dashboard'
));
}
}
public function login()
{
$this->layout = 'logindefault';
$this->set('title_for_layout', 'Account Login');
if ($this->Auth->login() || $this->Auth->loggedIn()) {
$lastLogin = $this->Auth->User('lastLogin');
if ($lastLogin != null) {
$this->redirect($this->Auth->redirect());
} else {
$this->redirect(array(
'controller' => 'Userinfo',
'action' => 'gettingstarted'
));
}
} else {
if ($this->request->is('post')) {
$mobileNo = $this->request->data['User']['email'];
$mobileNo = Utility::addPlusToMobileNo($mobileNo);
$this->request->data['User']['email'] = $mobileNo;
if ($this->Auth->login() || $this->Auth->loggedIn()) {
if ($this->Session->check('Auth.User')) {
$this->_setCookie($this->Auth->user('idUser'));
$lastLogin = $this->Auth->User('lastLogin');
if ($lastLogin != null) {
$this->redirect(array(
'controller' => 'users',
'action' => 'dashboard'
));
} else {
$this->redirect(array(
'controller' => 'Userinfo',
'action' => 'gettingstarted'
));
}
}
} else {
$this->Session->setFlash('Incorrect Email/Password Combination');
}
}
}
}
protected function _setCookie($id)
{
if (!$this->request->data('User.remember_me')) {
return false;
}
$data = array(
'username' => $this->request->data('User.email'),
'password' => $this->request->data('User.password')
);
$this->Cookie->write('User', $data, true, '1 week');
return true;
}
public function logout()
{
$this->Cookie->delete('User');
$this->redirect($this->Auth->logout());
}
Looks like you're already using the SecurityComponent if you want to secure your app use it everywhere. For AJAX forms white list only the fields you need, dont disable the component!
Put App::uses('Utility', 'Utility'); on top of the file
$mobileNo = Utility::addPlusToMobileNo($mobileNo); should happen in the model beforeSave()
If this is supposed to be used world wide I assume you want translations, this is missing the translation method call __() setFlash('Incorrect Email/Password Combination');
Most of the code CAN and should go into the model layer
Are there unit tests? If not add unit tests, specially test validation of data and false data input. You want ~85%+ Code Coverage for unit tests.
You're not following the CakePHP coding standards
There is no way to tell you more than this without being able to access the whole app code and doing a code review (I could do that). For queries, always just query the data you need, check the generated SQL queries, use DebugKit to check the query times to find slow querys and slowly rendering pages.
Related
I have created a little project with an admin section. I am using admin routes to redirect to admin actions in my controllers. The website has pages that are available to everyone with no login required. To access the /admin or /admin/users, etc... You must login.
I have spread my admin actions across my controllers like "admin_login", "admin_users", ...
So my question is, when someone goes to /admin/users or some other adminpage, I have to check in each controller action if the user is in the session and otherwise redirect to thelogin form.
Is there a way to do this in one place? I used a beforefilter in my AppController class.
When using something like this, I get an infinite loop:
AppController.php
class AppController extends Controller {
public $helpers = array('Paginator','Acl.AclHtml');
public $components = array('Acl', 'Session',
'Auth' => array(
'authError' => 'You are not authorized to access that location.',
'authorize' => array(
'Actions' => array(
'actionPath' => 'controllers')
),
'controllers' => array('users')
));
public function beforeFilter() {
if(isset($this->request->prefix) && ($this->request->prefix == 'admin')){
$username = $this->Session->read('Admin.username');
if (empty($username)) {
$this->redirect (array(
'controller'=>'users',
'action'=>'login',
'admin'=>true
));
} else {
$this->redirect (array(
'controller'=>'admin',
'action'=>'dashboard',
'admin'=>true
));
}
}
// LDAP
$server_ip = $_SERVER['SERVER_ADDR'];
$ldapIp = ClassRegistry::init('LdapIp');
$ldapIpCount = $ldapIp->find('count', array('conditions' => array('ldap_ip' => $server_ip)));
if ($ldapIpCount >= 1) {
$this->Auth->authenticate = array('Ldap');
} else {
$this->Auth->authenticate = array('Form');
}
$this->Auth->authenticate = array('Form');
$this->Auth->allow();
if (!$this->Auth->isAllow($this)) {
$this->set(array(
'message' => array(
'text' => __('un aunthaticated request'),
'type' => 'error',
'status' => "401"
),
'_serialize' => array('message')
));
throw new ForbiddenException();
}
}
}
The front login with LDAP (Active directory).
UsersController.php
App::uses('AppController', 'Controller');
App::uses('Sanitize', 'Utility');
class UsersController extends AppController {
public $components = array('Paginator', 'Session', 'RequestHandler', 'Auth', 'Acl');
public function admin_login() {
$this->layout = 'admin_login';
if ($this->request->is('post')) {
$username = $this->request->data['User']['username'];
$password = $this->request->data['User']['password'];
$password = Security::hash($password, null, true);
$logged_in = $this->User->find('count', array('conditions' => array('User.username' => $username, 'User.password' => $password, 'User.role' => 'Admin', 'User.active' => 1)));
if ($logged_in >= 1) {
$this->Session->setFlash(__('Login successful!'), 'default', array('class' => 'alert alert-success'));
$users = $this->User->find('first', array('conditions' => array('User.username' => $username, 'User.password' => $password, 'User.role' => 'Admin', 'User.active' => 1)));
$this->Session->write('Admin.id', $users['User']['id']);
$this->Session->write('Admin.username', $users['User']['username']);
$this->Session->write('Admin.group_id', $users['User']['group_id']);
$this->Session->write('Admin.full_name', $users['UserProfile']['fname'] . " " . $users['UserProfile']['lname']);
$this->redirect(array('controller' => 'admin', 'action' => 'dashboard', 'admin' => true));
} else {
$this->Session->setFlash(__('Username or password is incorrect!'), 'default', array('class' => 'alert alert-error'));
}
}
}
public function admin_logout() {
$this->Session->delete("Admin");
//$this->Session->destroy();
$this->Session->setFlash(__('Logged out successful!'), 'default', array('class' => 'alert alert-success'));
$this->redirect(array('controller' => 'users', 'action' => 'login', 'admin' => true));
}
}
Yoy are getting an infinite loop because beforeFilter() will be called when you attempt to access /admin/users/login.
The proper way of dealing with your needs is setting up the Auth Component.
Once you've set up component, in UsersController::beforeFilter() you have to allow access to those actions that don't require login by means of the allow() method. E.g.
public function beforeFilter() {
$this->Auth->allow(array('signup'));
parent::beforeFilter();
}
This is also applicable to any other controller with actions that need to be accessed by non logged in users.
The loginAction you define in the Auth component configuration will be automatically allowed access.
In the blog tutorial you will find a good example of the Auth component usage.
Edit
As mentioned, AppController::beforeFilter() is always called, even when you try to access /admin/users/login. To prevent this from happening, try adding the following condition:
if (empty($username) && $this->action!='login') {
$this->redirect (array(
'controller'=>'users',
'action'=>'login',
'admin'=>true
));
}
You wouldn't need this if you allowed AuthComponent to take care of authentication for you.
Still, there's no guarantee that your code will work as expected. You are making your life difficult by not using AuthComponent to its fullest. I recommend that you research on the topic:
Creating Custom Authorize objects in the Cookbook 2.x
LdapAuth in cakephp 2.0 in Stack Overflow
I am following instructions directly from Cakebook to hide certain things from a common user on my administrative system. Thus it would be possible only the administrator to access all pages. However, I am unable to run any user who accesses can see all. The following code:
public function beforeFilter(){
$this->Auth->authenticate = array('Form' => array(
'userModel' => 'Usuario',
'fields' => array(
'username' => 'login',
'password' => 'senha')));
$this->Auth->loginAction = array(
'controller' => 'usuarios',
'action' => 'login');
parent::beforeFilter();
}
public function isAuthorized($user) {
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
return false;
}
I'm not sure if I understand you correctly, but I'll give it a try.
I have cleaned your code, made some indents and more readable (for me at least). I also removed parent::beforeFilter();. AppController.php:
public function beforeFilter(){
// You don't need this line: parent::beforeFilter();
$this->Auth->authenticate = array('Form' => array(
'userModel' => 'Usuario',
'fields' => array(
'username' => 'login',
'password' => 'senha'
)
)
);
$this->Auth->loginAction = array(
'controller' => 'usuarios',
'action' => 'login'
);
}
public function isAuthorized($user) {
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
return false;
}
Now only users with the admin role can access your pages. If you want any user to have access to post comments, this is an example of how to let him. CommentsController.php:
public function isAuthorized($user) {
// All registered users can add comment
if ($this->action === 'add') {
return true;
}
return parent::isAuthorized($user);
}
Now admins can view every single page, but a registered user can only see app/comments/add
I'm having the weirdest Cake error with my code. When the code below calls the add() method (also reproduced here and from a different controller), the code redirects him back with a 302 found HTTP code to the edit() action (in essence, to the user it appears as if nothing happens). To further complicate matters, when I call the same URL when I'm not on that page (despite the pages not being reliant on each other), I get redirected to the base of my app, which results in a redirect loop. I tried calling other methods from this controller (with their proper arguments) to see if this was just a problem with the add() action, but I get the same redirect loop. I've already checked all over SE but cannot find a relevant answer.
Here is the relevant code:
function edit($id=null) {
if(!$id) {
$this->Session->setFlash('Invalid!');
$this->redirect(array(
'action' => 'index')
);
}
else {
//Get the slides themselves
$slides = $this->Slide->find('all', array('conditions' => array('Slide.module_id' => $id)));
$this->set('slides', $slides);
//Get the data for the Module
$module = $this->Module->find('first',
array(
'conditions' => array (
'Module.id' => $id
),
'fields' => array(
'Module.module_name',
'Module.id')
)
);
}
}
And here is the add() code (again, from a different module):
function add($module = null) {
if ($this->request->is('get')) {
//Set some variables for the view (this code I know works as it has been used successfully elsewhere
}
else { //User is POSTing
$this->Slide->create();
$this->Slide->save($this->data);
}
}
Thanks to everyone in advance; I couldn't do this without your support!
EDIT: Here is the AppController code:
public $components = array(
'Auth' => array(
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
),
'loginAction' => array(
'controller' => 'users',
'action' => 'login'
),
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email')
)
),
'logoutRedirect' => array('/')
),
'Session'
);
public $helpers = array('Html', 'Form', 'Session');
public function isAuthorized() {
return true;
}
public function Controller() {
}
}
The web app is working great except for auth manual calls. I've been struggling with this for days. In my sample code I have temporarily rewritten the cookie to narrow down the cause. Here is my app controller snip:
App::import('Sanitize');
//uses('sanitize');
class AppController extends Controller {
var $components = array('Clean','Acl', 'Auth', 'Session', 'RequestHandler', 'Cookie', /* 'DebugKit.Toolbar' */);
var $helpers = array('uiNav','Flash','Html', 'Form', 'Session','Javascript','Ajax','Js' => array('Jquery'), 'Time','Js');
function beforeFilter() {
//Configure AuthComponent
$this->Auth->authorize = 'actions';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'view');
$this->Auth->actionPath = 'controllers/';
$this->Auth->autoRedirect = false;
$this->Auth->allowedActions = array('display');
if(!$this->Auth->user()){
//$cookie = $this->Cookie->read('Auth.User');
$cookie = array('username' => 'chris22', 'password' => 'stuff');
if (!is_null($cookie)) {
$this->set('checking_cookie',$cookie);
if ($this->Auth->login($cookie)) {
$this->set('cookie_message','cookie validates!');
// Clear auth message, just in case we use it.
$this->Session->delete('Message.auth');
/* $this->redirect($this->Auth->redirect()); */
}
}
}
}
}
As you can see I'm just plugging the user name and password into $this->Auth->login and it's not working!!
I don't know if my user controller is relevent, but here is the login function for that too:
function login() {
if ($this->Auth->user()) {
if (!empty($this->data) && $this->data['User']['remember_me'] && isset($this->data['User']['password'])) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+1 month');
//unset($this->data['User']['remember_me']);
$this->set('cookie-00', 'setting cookie.');
}else{ $this->set('cookie_message', 'not setting cookie.');}
$this->redirect($this->Auth->redirect());
}
}
Thanks!
EDIT - 1 - I think I know why this is not working for you.
Reason 1: $this->Auth->login takes data in the form of
array(
'User'=>array(
'username'=>'myusername',
'password'=>'mypassword'
)
)
Reason 2: $this->Auth->login does NOT hash the password.
You must send the password exactly as it appears in the database.
EVERYTHING BELOW THIS LINE IS POSSIBLE BUT NOT LIKELY IN THIS CASE:
Are you sure that when you originally created the username and password, the hashes were setup?
To check that your hashes match look at your users table with phpmyadmin or mysql workbench and find the password field for the user chris22
Compare that entry to your current hashing. To check your current hash, put the code below somewhere in a controller function (index) and navigate there.
debug(Security::hash('stuff'));
exit;
I hope this helps!
Make sure the fields are correctly assigned in the Auth component.
if you use different field names than username & password to connect, you must declare them in your controller like this way :
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email', 'password' => 'mot_de_passe')
)
)
)
I have this in orders_controller.php
function beforeFilter() {
$this->Auth->allow('checkout', 'checkout_confirm', 'checkout_done');
parent::beforeFilter();
}
When I try to go to orders/checkout it always redirects me to users/login
Don't know where to look for solution.
I have an app_controller.php in app/
class AppController extends Controller {
var $components = array(
'Email',
'RequestHandler',
'Session',
'Cookie',
'Auth' => array(
'fields' => array(
'username' => "email",
'password' => "password"
),
'autoRedirect' => true,
'loginAction' => array('controller' => "users", 'action' => "login", 'admin' => false), // 'loginRedirect' => array('controller'
=> "users", 'action' => "check_account") // 'loginRedirect' => array('admin' => false, 'controller' => "users", 'action' => "account_home")
),
'Acl',
'Loviu'
);
var $helpers = array('Html', 'Form', 'Paginator', 'Session', 'Image', 'Javascript', 'Time', 'Text', 'Embed', 'Loviu');
var $uses = array('User', 'Shelf');
function beforeFilter() {
if (isset($this->params['admin']) && (1 == $this->params['admin'])) {
$this->testAccess("admin");
}
if($this->params['controller'] == 'pages'){
$this->Session->write('menu.active', 'inactive');
}
$this->Auth->allow('display');
if (false == $this->Session->check('Auth.User')) {
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (false == is_null($cookie)) {
// login user
if ($this->Auth->login($cookie)) {
// delete auth message
$this->Session->delete('Message.auth');
}
else {
// delete invalid cookie
$this->Cookie->delete('Auth.User');
}
} elseif(!$this->Session->read('loggedOut') && $this->params['action'] != 'login_fb') {
$this->__checkFBStatus();
}
}
}
$this->set('user_id', $this->User->id);
$this->set('lng', $this->Cookie->read("language") ? $this->Cookie->read("language") : 'eng');
parent::beforeFilter();
}
I would also put the $this->Auth->allow('checkout', 'checkout_confirm', 'checkout_done');line in your app_controller. In my experience, sometimes the problem is that the system gets confused about which controller this action belongs to, depending on how your code is setup.
Here is what I use in my app_controller that has been perfect, in case it helps:
function beforeFilter() {
$this->allowAccess();
}
private function allowAccess() {
// this actually searches the URL to see what controller you're accessing, and allows actions for that controller.
if(in_array($this->name, array('Pages'))) {
$this->Auth->allow(array('home','blog','index'));
}
}
This specificity has saved me so much trouble, and calling the Auth->Allow in app_controller is where it really should be. Hope this helps!
I had the same problem and solved for my project.
My cakephp version 3. While you loadcomponent just put loginaction.
class AppController extends BaseController
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Admin',
'action' => 'login',
'plugin' => 'Admin'
],
'loginRedirect' => [
'controller' => 'admin',
'action' => 'dashboard'
],
'logoutRedirect' => [
'controller' => 'admin',
'action' => 'login'
]
]);
}
}
hope helps others.
#rncrtr's answer worked for me, but I had to add the parent::beforeFilter() to the allowAccess method:
public function beforeFilter() {
parent::beforeFilter();
$this->allowAccess();
}
private function allowAccess() {
if (in_array($this->name, array('Pages'))) {
$this->Auth->allow(array('home','index','display'));
}
}
Oh yeah, I also had to add display to the allow array.
if you work on cakephp 2.x you must do like this :
function beforeFilter(){
$this->Auth->allow(array('action you want to allow1','action you want to allow2'));
}
allow(array()) instead allow()
---put that code into controller have action you want allow access without login
if you use $this->Auth->allow() you must call parent::beforeFilter(); in function beforeFilter() like this :
function beforeFilter(){
parent::beforeFilter();
$this->Auth->allow('add','view');
}