I got some troubles with the authentication in CakePHP 2.4. I baked a simple App to create a login. I did everything like in the book (Auth and Tutorial). But only sha1 worked. But not sha256 or md5. After searching and testing I came to a solution where I had to change the example code in the book and now it works. But I think, this is not the proper solution.
I did the following in the AppController:
App::uses('Controller', 'Controller');
class AppController extends Controller {
public $components = array(
'Session',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'passwordHasher' => array(
'className' => 'Simple',
'hashType' => 'sha256'
)
)
)
)
);
}
I tested this with md5, sha1 and sha256. No problem. Login works if the passwords are hashed appropriately.
But I noticed, that adding a user only works with sha1, because this is the default hash.
My User Models beforeSafe function was this (from the book):
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
class User extends AppModel {
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$passwordHasher = new SimplePasswordHasher();
$this->data[$this->alias]['password'] = $passwordHasher->hash(
$this->data[$this->alias]['password']
);
}
return true;
}
}
I thought, that the setting in the AppController is enough, to change the SimplePasswordHasher in this area too. But as it seems, it's not enough. So I changed it to this:
$this->data[$this->alias]['password'] = $passwordHasher->hash(
$this->data[$this->alias]['password']
);
to this
$this->data[$this->alias]['password'] = Security::hash(
$this->data[$this->alias]['password'],
'sha256',
true
);
Now everything works like a charm. But my questions:
1) I'm right, that this is necessary or is my code something else wrong?
2) As I know is $xxx->zzz only for Controllers and xxx::zzz() for everywhere and, right?
3) Why do I have to say the Security hash, that it should hash the string with sha256 again, when I said that already in the AppController in general?
1. As far as I know, your approach is right. Might exist a cleaner way, but I am using bcrypt, and the Cookbook says to do something very similar.
2. You can think like you said for a simple explanation. Basically -> is the object-operator, to deal with objects e.g. $this->doEverything. And :: is the scope-resolution-operator which is used to find Class static property or method. And normally you can run this everywhere, e.g., Security::hash().
3. Take a look here, you should probably change your AppController to:
'passwordHasher' => array(
'className' => 'Simple',
'hashType' => 'sha256'
)
to fit on the recomendations. Altough I've tryed this, and didn't changed the SimplePasswordHasher to SHA256. It is a reference that cake seems to ignore somehow. But that is the Simple hasher, intended to simple thing, so you are supposed to do your own approach, or one that fits better to your needs.
Related
When using the Auth component in CakePHP 3 you can define the findAuth() finder (or configure a different finder) to have control over what data is loaded:
// AppController
$this->loadComponent('Auth', [
//...
'authenticate' => [
'Form' => [
'finder' => 'auth'
]
],
//...
]);
// UsersTable
public function findAuth($query, array $options)
{
return $query
->...;
}
I need some functionality that cannot be done with the query builder. How can I post-process the loaded auth data before session creation?
Note that I have different ways of logging in my users, so I would prefer this be kept inside the AuthComponent logic.
(This is still for CakePHP 3, but a brief comment on how this could be done in the new CakePHP 4 Authentication plugion would also be appriciated.)
EDIT: Rough outline of what I need: data needs to re-organised in the users array based on current context, i.e. users can have an active project selected.
I'm still not really sure what exactly you need to re-organize in what way exactly, but generally you can modify the queried data using mappers/reducers and result formatters, the latter usually being the easier way.
Here's a quick example that would add an additional field named additional_data to the result in case a field named active_project_id is set:
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
if (isset($row['active_project_id'])) {
$row['additional_data'] = 'lorem ipsum';
}
return $row;
});
});
Such a finder query would work with the new authentication plugin too.
See also
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields
I encountered this annoying error when using custom LoginController, where the Auth::attempt always return false. I tried every solutions posted online but nothing works, any idea whats happening in this Laravel6 application ?
public function authenticate(Request $request) .......
$ubersmith = ///external api call to get email
if (Auth::attempt(['email' => $ubersmith->data->email, 'password' => $request->input('password')])) {
// Authentication passed...
var_dump('login');
return redirect('/');
}
$user = \App\User::create([
'password' => Hash::make($request->input('password')),
'email' => $ubersmith->data->email,
'name' => $ubersmith->data->fullname
]);
Auth::login($user);
var_dump($user);
return redirect('/2');
}
Looking at your code, seems that there's nothing wrong with it. You can try debugging it, e.g. putting dd($ubersmith->data->email); and dd($request->input('password')); to check if you're passing the correct data.
Another possibility is you're putting the incorrect password. Can you try doing:
dd(Hash::make($request->input('password')));
Then update your password using this value, then try to authenticate again.
I am having an issue inserting a record into the database. I am a beginner with the Yii framework, so I may have made some stupid mistakes.
This is from the SiteController
public function actionCreatePost(){
$model = new PostForm();
$post = new Post();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$post->body = $model->body;
$post->title = $model->title;
$post->save();
return $this->redirect('index');
}else {
return $this->render('createPost', ['model' => $model]);
}
}
This is from the Post class
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'created_at',
'updatedAtAttribute' => 'updated_at',
'value' => new Expression('NOW()'),
],
[
'class' => BlameableBehavior::className(),
'createdByAttribute' => 'id_author',
]
];
}
The issue is that you have created a PostForm class for the form (which is correct) but you are then trying to load the response into the Post class - a completely different class. This won’t work without modification.
If you have a look at the response:
var_dump(Yii:$app->request->post());
You will see the form data is located within the PostForm key. Yii will therefore only load the data into the PostForm class.
The correct solution is therefore to create a savePost() function within the PostForm eg:
public function savePost(){
$model = new Post();
$model->propertyABC = $this->propertyABC
...etc...
$model->save();
So the action would appear as follows:
$model = new PostForm();
If($model->load(Yii::$app->request->post()) && $model->validate()){
$model->savePost();
The other option is to rename the key from PostForm to Post. Yii will then load the data but this is not the best approach as it is a bit obscure.
Hope that helps
I would guess the issue is with the validation.
I can see several issues I will point out. First, I cannot figure out why are you creating a new PostForm, loading the data in it and verifying it, just to dump some values in a new Post and save it. Are there some functions, you are running in the PostForm model, that are triggered by load or verify? If that is not the case, I would suggest dropping one of the models, and using only the other. Usually, that is the Form model. It serves as a link between the ActiveForm and the model handling everything. You can do everything in the createPost() function in the Form model, and then in the controller it will look like
if ($model->load(Yii::$app->request->post())) {
$model->save();
return $this->redirect('index');
}
Second of all, you can dump post->getErrors() before the save to see if there are any errors with the validation. What you can also do, is call $post->save(false) instead. If you pass false to it, it will not trigger $post->validate(), and some errors can be neglected. Please, let me know if there is anything unclear.
I used a simple instruction from
http://framework.zend.com/manual/2.0/en/modules/zend.authentication.adapter.dbtable.html#advanced-usage-by-example for Zend Authentication.
Here is my code:
$adapter = $sm->get('adapter');
$authAdapter = new DbTable($adapter);
$authAdapter -> setTableName('users')->setIdentityColumn('username')->setCredentialColumn('password');
$authAdapter -> setIdentity('admin')-> setCredential('password');
$authAdapter -> authenticate();
The above code generates error as follows:
The supplied parameters to DbTable failed to produce a valid sql statement, please check table and column names for validity.
I know if makes sense to use ZF-Commons and ZF-Users module and not reinvent the wheel... but being relatively new to ZF2 I want to try it myself.
The answer's right there in the error message, Zend\Authentication\Adapter\DbTable expects more than just an adapter as a parameter, it also needs a table name, and the name of the identifier and credential columns...
$authAdapter = new DbTable($dbAdapter,
'tableName',
'identifierColumnName',
'credentialColumnName'
);
This info is covered in the docs, which is always a good starting point -> http://zf2.readthedocs.org/en/release-2.1.4/modules/zend.authentication.adapter.dbtable.html
I know this is an old one and probably you have the answer now. Even though, I will put my answer here for further users. I myself faced lots of similar issues where the tutorials expect us to have some preconditions in place.
This error occur because you are not completely right in the statement to get the DB adapter. You need to call a service management instance with an string defined in your locals or globals configurations. For example:
I have this factory defined in my global.php file:
return array(
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
'db' => array(
'driver' => 'Pdo',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
);
In my local.php I have the credentials and information to reach my DB server as follow:
return array(
'db' => array(
'username' => 'valid_dbusername',
'password' => 'valid_dbpass',
'dsn' => 'mysql:dbname=valid_dbname;host=valid_dbserver',
),
);
This is enough to define my service calle 'Zend\Db\Adapter\Adapter'. Then, to instantiate my Db adapter I have the following lines inside any function in my controller:
if (!$this->adapter) {
$sm = $this->getServiceLocator();
$this->adapter = $sm->get('Zend\Db\Adapter\Adapter');
}
It is important to note adapter here is a class variable. So, it must be defined inside my controller class like:
public $adapter;
This steps are enough to you get an DB adapter. I am assuming you dont have a factory called 'adapter'. This should make your example work.
I'm moving some of my find code inside models.
Previously in my controller I had
$this->Book->Review->find('first', array(
'conditions' => array(
'Review.book_id' => $id,
'Review.user_id' => $this->Auth->user('id')
)
));
so in my Review model I put something like
function own($id) {
$this->contain();
$review = $this->find('first', array(
'conditions' => array(
'Review.book_id' => $id,
'Review.user_id' => AuthComponent::user('id')
)
));
return $review;
}
So I'm calling AuthComponent statically from the Model. I know I can do this for the method AuthComponent::password(), which is useful for validation. But I'm getting errors using the method AuthComponent::user(), in particular
Fatal error: Call to a member function
check() on a non-object in
/var/www/MathOnline/cake/libs/controller/components/auth.php
on line 663
Is there a way to get the info about the currently logged user from a model?
Create a new function in the "app_model.php" ("AppModel.php" in CakePHP 2.x), so it will be available at all models within our application:
function getCurrentUser() {
// for CakePHP 1.x:
App::import('Component','Session');
$Session = new SessionComponent();
// for CakePHP 2.x:
App::uses('CakeSession', 'Model/Datasource');
$Session = new CakeSession();
$user = $Session->read('Auth.User');
return $user;
}
in the model:
$user = $this->getCurrentUser();
$user_id = $user['id'];
$username = $user['username'];
The way that I use is this:
App::import('component', 'CakeSession');
$thisUserID = CakeSession::read('Auth.User.id');
It seems to work quite nicely :-)
I think the code is fine as it is and belongs in the Controller, or at the very least it needs to receive the ids from the Controller and not try to get them itself. The Model should only be concerned with fetching data from a data store and returning it. It must not be concerned with how the data is handled in the rest of the application or where the parameters to its request are coming from. Otherwise you paint yourself into a corner where the ReviewModel can only retrieve data for logged in users, which might not always be what you want.
As such, I'd use a function signature like this:
function findByBookAndUserId($book_id, $user_id) {
…
}
$this->Review->findByBookAndUserId($id, $this->Auth->user('id'));
There is a nice solution by Matt Curry. You store the data of the current logged user in the app_controller using the beforeFilter callback and access it later using static calls. A description can be found here:
http://www.pseudocoder.com/archives/2008/10/06/accessing-user-sessions-from-models-or-anywhere-in-cakephp-revealed/
EDIT: the above link is outdated: https://github.com/mcurry/cakephp_static_user
I think this is not good idea to get value from Session. Better solution to get logged user id inside any model simply try this:
AuthComponent::user('id');
This will work almost every where. View, Model and Controller
Dirtiest way would be to just access the user information in the Session. Least amount of overhead associated with that.
The "proper" way would probably be to instantiate the AuthComponent object, so that it does all the stuff it needs to be fully operational. Much like a death star, the AuthComponent doesn't really work well when not fully setup.
To get a new AC object, in the model:
App::import( 'Component', 'Auth' );
$this->Auth = new AuthComponent();
Now you can use $this->Auth in the model, same as you would in the controller.
For CakePHP 3.x this easy component is available: http://cakemanager.org/docs/utils/1.0/components/globalauth/. Direct accessing the Session is not possible because of different SessionKeys.
With the GlobalAuthComponent you can access your user-data everywhere with: Configure::read('GlobalAuth');.
Greetz
Bob
I use cake 2.2 and these both work great:
$this->Session->read('Auth.User');
//or
$this->Auth->user();
You can also get a field of currently logged in user:
$this->Session->read('Auth.User.email');
//or
$this->Auth->user()['email'];
None of these solutions work in CakePHP version 3. Anyone know of a way to do this? Right now, I'm completely stepping around the framework by accessing the $_SESSION variable directly from my model.