CakePHP & Change user's profile - cakephp

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

Related

Logging a user in after registration in CakePHP

I did a lot of research, followed many different examples, but still cannot get it to run properly.
So here is a part of the controller action from the registration:
if(!empty($this->request->data)){
$this->request->data['Company']['start_date']= date("Y-m-d");
unset($this->Company->User->validate['company_id']);
if($this->Company->saveAssociated($this->request->data)){
$user = $this->request->data['User'];
$data['User']['password'] = $user[0]['password'];
$data['User']['email'] = $user[0]['email'];
if($this->Auth->login($data)){
$this->redirect($this->Auth->redirect(array('controller'=>'customers', 'action'=>'index')));
}...
So the user is saved and a new array of user's email and password is created. It is then passed to $this->Auth->login. The login seems to pass, but the following error is on redirection to customers controller:
Notice (8): Undefined index: role [APP\Controller\CustomersController.php, line 32]
Notice (8): Undefined index: role [APP\Controller\CustomersController.php, line 36]
Even though the role field is autoset as manager on user creation.
Here is how the CustomerController looks like:
public function isAuthorized($user){
if($user['role'] == 'manager'){
return true;}
if (in_array($this->action, array('add', 'edit', 'index', 'view', 'delete', 'users'))){
if($user['role'] != 'manager'){
return false;
}}return true;}
Any help is very much appreciated.
Check the docs and the source for AuthComponent::login()
http://api.cakephp.org/2.4/class-AuthComponent.html#_login
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#manually-logging-users-in
http://api.cakephp.org/2.4/source-class-AuthComponent.html#583-606
When passing user data to AuthComponent::login(), you are logging a user in, but no authentication is going to happen! "Logging in" in this case means, the data provided is being stored in the session, and on following requests the user is being treated as logged in in case data is present in the session (in the specific key used by the component), ie you could even just pass 123456, the user would be treated as being logged in.
Authenticating on the other hand would cause a DB lookup, where all the user data would be fetched and consequently being stored in the session.
So the role field is not available because you haven't passed it to AuthComponent::login(), you've only passed email and password, consequently these are the only fields being available later on. Btw, DO NOT supply the password when doing such a manual login! You don't want to carry such sensitive information in the session!
To fix this problem, either pass the role field too, or call AuthComponent::login() without passing any data at all (make sure you are using Form authentication so that the data passed in the request is being used), so that it's going to authenticate the user and fetch its data from the DB.
See also http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html

How to prevent user from edit someone's else post

Let's say I have User model and Post model. Post model contains field user_id.
User has $hasMany on Post and Post has $belongsTo on User.
I have some post edit action:
PostsController::edit($id) {
if($this->request->isPost())
{
$this->Post->id = $id;
$this->Post->save();
}
$post = $this->Post->read($id);
$this->set(compact('post'));
}
I use AuthComponent to login users.
How can I prevent user from editing some1 else post? Is there any cake build in function/option to do this? Because some1 can login and post edit action with any id. It's not even case of saving the post data - let's say post is private (only owner should see it) - when someone will call posts/edit/some_id it will see the edit form of this post...
The easier way is to just add this at the beginning of edit action:
$this->Post->id = $id;
if($this->Post->readField('user_id') != $this->Auth->user('id'))
{ //trigger error or redirect }
But I would have to add this at the beginning of each action that updates/reads any data that belongs to some user. So I am looking for more elegant way to do this.
Well an exact example ( handily using a Post/User model too ) is available in the cake manual
Everyones a winner!
Well there is no way of avoiding adding a line to check if a user is authorized to perform an action. Even if you use ACL (Access Control Lists), which is one of Cake's most powerful features.
But as you speak of elegance in general, ACLs will be beauty at its best :) Careful though, they've got a steep learning curve. Don't give up easy, its well worth it.
You should see ACLs from the Book http://book.cakephp.org/1.3/view/1543/Simple-Acl-controlled-Application

What's the most elegant way to handle users custom domains?

On my site, users have public profiles that can be accessed via http://mysite.com/vanity_url. I want to allow users to point their own domains to their profile page on my site. Just like Bandcamp does.
My Profile model has these two fields to deal with this: vanity_url, which is the normal username type of field; and a new custom_domain which is their own domains' name, eg, example.com.
This is what I have done so far, but I'm afraid it might not be the most elegant, safe, and efficient way to do it.
First, I made sure Apache's DocumentRoot is set to my app's webroot directory, so I can tell users to point their DNS to my site's IP.
Now, this is how the routing rules on my routes.php look like:
if (preg_match('/mysite\.com\.?$/', $_SERVER['SERVER_NAME'])){
// Normal routes when visitors go to my domain
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/**', array('controller' => 'pages', 'action' => 'display'));
// Move all other actions to a separate '/app/' area
Router::connect('/app/:controller/:action/**');
Router::connect('/app/:controller/**');
// Handle profile URLs
Router::connect('/:profile/**',
array('controller' => 'profiles', 'action' => 'view'),
array('pass' => array('profile'), 'profile' => '[0-9a-zA-Z\-\_]+')
);
}
else{
// If visitors come via a URL different to mysite.com, I let
// the ProfilesController deal with it passing the current SERVER_NAME
// as a param to the 'view' action
Router::connect('/', array(
'controller' => 'profiles',
'action' => 'view',
$_SERVER['SERVER_NAME'], // 'url' param
true // 'customDomain' param
));
Router::redirect('/*', 'http://mysite.com');
}
And this is how the view action on the ProfilesController looks like:
public function view($url = null, $customDomain = false) {
if ($url){
// Find the profile by its vanity_url or its custom_domain
$findOptions = array(
'conditions' => $customDomain?
array('custom_domain' => $url) :
array('vanity_url' => $url)
);
if ($profile = $this->Profile->find('first', $findOptions)) {
$this->set('profile', $profile);
}
}
else throw new NotFoundException(__('Invalid profile'));
}
What problems could I face with this approach?
Also, does anyone know why Bandcamp asks users to create a CNAME instead of an A record to set up a subdomain? Am I missing something I should consider here?
Edit Somebody helped me figure that last bit out: It seems you can't easily use an CNAME record to point a naked domain to another. The main question is still open.
I did something similar using cake 1.3 a year ago
There are 4 major steps in this solution:
Have a Domain model and domains datatable that records all the possible custom domains and Views for your users to enter their custom domains
create domain checking code in beforeFilter in AppController and save User data to Session
the Controller action that is responsible for viewing the public profile needs to reference the same User data that is saved to Session
your users need to setup CNAME and A records correctly with their domain registrars
Step 1 Domain model and datatable
What I did was I had a table called domains
Each Domain contains the web address which I assumed to be just a http://....
Domain belongsTo User
User hasMany Domain
domains
==========
id web_address user_id main
the main is a tinyint which indicates whether this is the main url to be used since User hasMany Domain.
so what happens is the user needs to create a new record or more for Domain.
Step 2 Code in beforeFilter of AppController to figure out which public profile to display
Next, your code needs to be able to retrieve the User id based on the url submitted upon each http request.
The User from this point onwards refer to the User whose public profile is viewed. Do NOT confuse this with the loggedIn User, should you have one.
I suggest that you do this in your beforeFilter of AppController.
Something like this
$currentUser = $this->User->getByDomain(FULL_BASE_URL);
$this->Session->write('CurrentUser', $currentUser);
The code for getByDomain for User model is also an exercise for you. Should be easy enough given that I have explained the schema for Domains
You may need to check against the currentUser inside your Session data before writing the currentUser because you do not wish to write the Session data all the time especially when the visitor is visiting the same webpage again and again.
You may need to change the above code snippet to something like this:
$currentUser = $this->Session->read('CurrentUser');
if(empty($currentUser) OR (!$this->checkUrlAgainstDomain(FULL_BASE_URL, $currentUser['Domain']['domain']))) {
$currentUser = $this->User->getByDomain(FULL_BASE_URL);
$this->Session->write('CurrentUser', $currentUser);
}
Again the function checkUrlAgainstDomain is an exercise for you.
Step 3 Code in beforeFilter of AppController to figure out which public profile to display
You will use the CurrentUser data saved in your Session to determine which User's public page you want to display.
I leave this as an exercise for you to figure out in your UsersController under action view
Step 4 your users need to enter the following in their domain registrars
Then your users need to go to their domain registrars and do a similar thing as BandCamp users in the faq you have linked to.
User's "www" subdomain should point using CNAME to example.Lucho.com
User's root domain (without the www) should have an A Record pointing to IP Address of your server ie where your Lucho.com is residing in
User must add both both of these Domains management page as explained earlier on the top. It can take up to 48 hours for the domain to completely update.
I guess there's no better way :P

How to: CakePHP logging in without password?

I'm trying to find a way to log in user without password.
The reason is that I have phpBB3 forums in my site and the users already log in there. So I'm now building an expansion to the site to have more than just the forum (Using CakePHP). I thought that I could attach automatic account creation to CakePHP when user creates an account to forums (And ofcourse other link for the existing users). So the users would get CakePHP account that has the same username that they have registered in forums. That means that the only way to register to CakePHP part of the site would be to register to the forums first.
Now I'd like to handle the whole logging thing by phpBB3 login so users would still login to forums, and then I'd attach a piece of code that would also login them to CakePHP part of the site with the username they used to login to forums.
This way I could do also put users to their own ACL groups by their status in forums.
Thats what I'm after and I need to know the way to login users this way. I'm not looking for complete code I'm just looking for an answer that explains how I log in users in CakePHP without them having passwords at all.
I have also looked http://bakery.cakephp.org/articles/wilsonsheldon/2009/01/13/phpbb3-api-bridge but it just doesn't quite look what I'm looking for...
As far as I recall, Auth requires two pieces of info for a login.
You can change which fields in the users table are checked by auth with.
$Auth->fields = array(
'username' => 'username',
'password' => 'password'
);
So if you you want to be able to log in users according to their nickname and shoesize:
$Auth->fields = array(
'username' => 'nickname',
'password' => 'shoesize'
);
IMPORTANT:
The AuthComponent expects the password value stored in the database to be hashed instead of being stored in plaintext.
(I think it is a sha1 of the password and Security.salt)
In the above example, if any entries already existed in the database you'd have to overwrite the shoesize field for each of them with hashed versions of the shoesizes.
To generate a hashed password yourself you can use $Auth->password('A Password');
Quick and Dirty
If you fill the password fields in your users table with the return value of:
$Auth->password(null);
Then you can use the following:
$Auth->login(
array(
'User'=>array(
'username'=> USERNAME_FROM_PHPBB3,
'password'=>null
)
)
);
Less Quick and Dirty
When creating a new user.
Set the password field to the md5 hash of some random input.
$this->authUser[$this->User->alias][$Auth->fields['password']] = $Auth->password(md5(rand().rand()));
Use the Username from phpBB3 to retrieve the relevant record
from the users table in the database.
$this->authUser = $this->User->findByUsername( USERNAME_FROM_PHPBB3 );
If the query was successful Log in the user
if($this->authUser){
if($Auth->login($this->authUser)){
// Login Successful
}
}
From your cakephp app you can check if a user exist in the phpbb forums table and you can use the phpbb session to check if a user is logged in.
This function will solve your problem:
public function forceLogin($userName = NULL) {
$this->_setDefaults();
$this->User = ClassRegistry::init('User');
$this->User->recursive = 0;
$user = $this->User->findByUsername($userName);
if (!empty($user['User'])) {
$this->Session->renew();
$user['User']['id'] = null;
$user['User']['password'] = null;
$this->Session->write(self::$sessionKey, $user['User']);
}
return $this->loggedIn();
}

CakePHP Auth Component Check User Before Login

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.

Resources