I have following situation:
I have loged user, standard authentication with DB table
$authAdapter = new Zend_Auth_Adapter_DbTable(Zend_Db_Table::getDefaultAdapter());
$authAdapter->setTableName('users');
$authAdapter->setIdentityColumn('user_name');
$authAdapter->setCredentialColumn('password');
When user edits his profile, I save it into Db, but I need to update also storage (using standard Zend_Auth_Storage_Session). Is there any easy way how to do it? Many thanks.
$user = Zend_Auth::getInstance()->getIdentity();
$user->newValue = 'new value';
Assuming you are updating the session data in the same statement you are updating the database in there is no need to call the db again.
Your best bet would be to not use Zend_Auth's storage to hold information that's likely to change - by default it only holds the identity (for good reason). I'd probably make a User class that wrapped all the Auth, ACL (and probably profile) functionality that uses a static get_current() method to load the user stashed in the session. This bypasses all the consistency issues you run into when stuffing it into the session as well as giving you a single point from which to implement caching if/when you actually need the performance boost.
I have done it like this, it works, but I don't know if there is not some better way,how to do it
$user_data = User::getUser($user_id)->toArray();
unset($user_data['password']);
$std_user = new stdClass();
foreach ($user_data as $key => $value)
{
$std_user->$key = $value;
}
$auth = Zend_Auth::getInstance();
$auth->getStorage()->write($std_user);
Related
Example, suppose you have an User entity.
The user has id, username, password.
The id is autogenerated in database.
In an UpdateUserCommand, same data should be used.
In an UpdateUserCommandHandler, we need the ID to make the query update users set password ... where id = :id.
But how can I make sure that I have the data required for building this query? Is the only why a lot of if (!somedata) trow new MissingSomeDataException()?
Example:
if (!isset($data['id'])) {
throw new Exception('ID is missing');
if (!isset($data['username'])) {
throw new Exception('Username is missing');
if (!isset($data['password'])) {
throw new Exception('password is missing');
$id = (int) $data['id'];
if (!isset($data['id'])) {
throw new Exception('ID is invalid');
$query = $this->entityManager->createQuery('update Users u ... where u.id = :id)
$this->entityManager->execute($query, $params);
Well, technically, yes. Definitely.
You have to check every input you receive from client, because it may be malformed by a lot of reasons.
Moreover, you will need to check if user with provided ID actually exists, if you are going to do something with it later.
However
There are a lot of convenient options to do so.
$keys=['id','username','password'];
!array_diff_key(array_flip($keys), $data); // will be TRUE if all keys are there
Or you may want to use any ORM or CRUD-framework to make your life (not PHP parser, though) easier - right after you will configure it to check all inputs, it will do it automatically before accessing and querying database, and you will spend time only doing positive business-process level work, not bothering with checking every incoming bit.
Old good book/authors list example on Propel's website will help you to understand what do I mean: Propel ORM
Here is my code for now:
$cloud = new Rackspace('https://identity.api.rackspacecloud.com/v2.0/', $php_cloudconfig['credentials']);
$array_creds = getCredentials();
$cloud->ImportCredentials($array_creds);
$array_creds = $cloud->ExportCredentials();
setCredentials($array_creds['authorization_token'], $array_creds['expiration'], $array_creds['tenant_id'], $array_creds['service_catalog']);
function getCredentials() {
$sql_get_credential = "SELECT * FROM cloud_apiconnection";
$q = $conn->prepare($sql_get_credential);
return $q->execute();
}
function setCredentials($authorization_token, $expiration, $tenant_id, $service_catalog) {
$sql_insert = "INSERT INTO cloud_apiconnection (authorization_token, expiration, tenant_id, service_catalog) VALUES (:authorization_token, :expiration, :tenant_id, :service_catalog)";
$q = $conn->prepare($sql_insert);
$q->execute(array(':authorization_token' => $authorization_token, ':expiration' => $expiration, ':tenant_id' => $tenant_id, ':service_catalog' => $service_catalog));
}
Is there a way to detect if the credentials were updated in: $cloud->ImportCredentials($array_creds); ?
I am wandering because I don't want to write to the DB if I don't need to.
Also is this the best strategy for managing my connection to RackSpace API?
It seems like a good strategy for maintaining a persistent session, because you're re-using an existing token ID. The only other suggestion is to cache your credentials in a local file, rather than making an MySQL transaction. You don't really need to store your tenant ID and service catalog because these are easily retrievable through the software layer.
To check the validity an existing token, I'd do this:
$connection = new Rackspace(...);
// Import existing credentials
$credentials = getCredentials();
$connection->importCredentials($credentials);
// Authenticate against the API to make sure your token is still valid
$connection->authenticate();
// Compare result
$newCredentials = $connection->exportCredentials();
if ($newCredentials['authorization_token'] != $credentials['authorization_token']) {
// You know it's been updated, so save new ones
setCredentials();
}
All the authenticate() method does is execute a request against the Rackspace API; and based on the results, it's effectively letting you know whether your existing stuff is still valid. When other methods call authenticate(), they usually do another check beforehand: they check the expiry value (i.e. not in the past). You could implement the same thing they do (to find out whether credentials need to be refreshed and saved):
if (time() > ($credentials['expiration'] - RAXSDK_FUDGE)) {
// They're old, you need to call authenticate()
} else {
// They seem to be still valid. Sweet!
}
By the way, we've recently changed this functionality so that exportCredentials() makes a call to authenticate() - so this would mean you wouldn't need to call it yourself. But for your current version it's worth leaving it in.
Does that answer everything?
I used to program php in procedural way. Since I would like to learn more about OOP I have decided to program php in OOP way for my new project.
Anyways, let's say I have a project that requires user to login. Which means in login.php once user enters the correct username and password, it will be redirected to index.php and will start loading all the products from the product table and display them nicely in index.php.
Before, how I did this was, in login.php I'll have the following code:
login.php
session_start();
...
if (loggedCorrect($user, $password)) {
$_SESSION['loggedinuser'] = $user;
//redirect to index.php
}
index.php
session_start();
if (isset($_SESSION['loggedinuser']) {
//select fields from products table and display them
...
}
so in OOP it will be something like:
login.php
session_start();
$user = new User($user, $password);
if ($user->hasCorrectLogin()) {
$_SESSION['loggedinuser'] = $user->getUsername();
//redirect to index.php
}
index.php
session_start();
if (isset($_SESSION['loggedinuser']) {
$products = new Products();
//display all products
}
Product class
class Products {
private $productArray;
...
__construct() {
//select all products from mySQL table then put every product in productArray
}
...
}
My question are:
when initiating object (like Products in my case). do I have to check login session? if so, should I do it inside __contruct? or should I do it before the "class Products" line?
I also have a cronjob.php, which will be executed every x minutes. When it's executing, it will create some objects like Products and analysis them. So if login session checking is required, then I'm not sure how to make this works, as cronjob doesn't support session.
Please advise
Quick answers
No. Your domain objects themselves should not be dependent on a logged in session; however, they may need a User instance to perform certain duties, such as only showing the products that a particular user is able to see.
Because of #1, this is now trivial.
Code review
First let's consider your login page code:
$user = new User($user, $password);
if ($user->hasCorrectLogin()) {
In this code, it would seem that User interacts with the database and has knowledge how to validate credentials. That seems a bit too much responsibility for a single class.
It could perform password validation by keeping the hashed password inside the object, but you would only need to validate a password once, so there's really no need to keep that field around. Another reason not to have it done here is when you need to consider strengthening your passwords on-the-fly, which could be a site policy to scale with growing hardware (e.g. when you're using bcrypt).
It should definitely not be doing database interaction; to separate both database interaction and password verification from the User class you could consider adding an authentication service.
try {
$user = $authService->login($userName, $password);
$_SESSION['loggedinuser'] = $user;
// redirect to index.php
} catch (InvalidLoginException $e) {
// oops, username or password invalid
}
Inside the authentication service, you could add another layer of abstraction to load the user record (using a data mapper for instance).
Instead of only storing the username in the session, you can store the whole User object as well. In some cases this may lead to inconsistencies, but it saves round trips to the database.
Now, let's observe the product overview page:
$products = new Products();
In terms of naming I would say that Products is not a good candidate to describe a collection of objects. A name such as ProductList or ProductCollection is better.
As with the authentication above, it's unclear how the Products class gets populated; it should come from some storage, so let's introduce the repository that will provide the list of products:
$productRepository = new ProductRepository($db);
$products = $productRepository->getAll();
In the simplest scenario, the repository gets initialized with a database instance; more levels of abstraction can be applied when necessary.
What is the proper way to implement updating user's last visit in database?
I have column last_visit in users table and added this code to bootstrap.php file
protected function _initUser() {
$this->bootstrap(array('db'));
$auth = Zend_Auth::getInstance();
$identity = $auth->getIdentity();
if($identity) {
$userModel = new Model_User();
$user_id = $identity->id;
$userModel->updateLastVisit($user_id, date("Y-m-d H:i:s"));
}
}
Is that good? Or should I do this diffrent way?
EDIT
I ended up doing this so the database is queried only once every 5 minutes. Can I leave it as it is now or are there any more changes necessary to make it work better (in performance point of view)
protected function _initUser() {
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
$currentUser = $auth->getIdentity();
if((time() - strtotime($currentUser->last_visit)) > 60*5) {
$last_visit = date("Y-m-d H:i:s");
$currentUser->last_visit = $last_visit;
$this->bootstrap(array('db'));
$user = new Model_User();
$user->updateLastVisit($currentUser->id, $last_visit);
}
}
}
That is Ok if you have no problems with speed. Now you have a bunch of operations for each request (Even if it looks pretty simple - Zend will do a lot of work behind those two lines). And if you fight for tens of milliseconds - these lines are one of the first candidate for improvement. I do not think you need to have the latest possible value. So it can be enough to update last_visit after login or when user is logged in automatically because of "remember me" enabled.
You should perform this kind of operation using plug-in hooks when dispatching the request, the same way you do when checking if the user is logged in. While it works through the bootstrap class, from an architectural point of view, this is related to the cross-cutting concerns of your application (see Aspect-oriented programming).
You can even perform this operation within your access check plug-in, something similar to what #FAngel proposed: Storing the new date after logins or after grabbing the cookie, avoiding requesting the database every time the user reloads the page.
Last login: Update the datetime in your LoginController. Example: https://gist.github.com/2842698 (line 100), or as Keyne already said, put it in a special plug-in.
Last visit: We put this data temporary in a key-value storage(memached), and save the data with a cronjob each 5 minutes. So you don't have any performance issues with your DB.
When my users are logged in I display their details (name, email) on the UI. When they update their profile, I would like to show the updated details, without requiring the user to log out and back in.
The UI details are retrieved from Zend_Auth via a view helper. Zend_Auth is storing the 'identity' details in a session.
How should I go about updating the details in the session?:
I was considering retrieving the user's login credentials from the database and using them to call Zend_Auth->authenticate() again. The problem is that I don't know the password, only it's md5 hash. I could consider a new method, reauthenticate(), which configured the adapter to bypass the md5 and salt, but this sounds laborious.
I was considering writing directly to the Zend_Auth session namespace, but this sounds like a recipe for trouble?
Have you come across a similar problem? How did you handle it?
Your ideas are much appreciated!
You can update Zend_auth identity for the currently logged user. Very simplified action that updates only username could be as follows:
public function editAction() {
// check if user is logged, etc, and then
// show the edit user form and process the data after submission.
$userForm = new My_Form_EditUser();
if ($this->getRequest()->isPost()) {
if ($userForm->isValid($_POST)) {
// process the submitted data,
// and when you are sure that everything went ok,
// update the zend_auth identity
$authData = Zend_Auth::getInstance()->getIdentity();
// this line would depend on the format of your
// identity data and a structure of your
// actual form.
$authData->property->nickname = $formData['user']['nickname'];
$this->_helper->FlashMessenger('Your data was changed');
return $this->_redirect('/');
}
}
$this->view->form = $userForm;
}
Hope this helps.
What I really want is a method on Zend_Auth::setIdentity($user).
But in the absence of such a method, I have used a hack in which I have create an auth adapter that always returns success and sets the identity to the same user object I would have created in a "real" auth adapter. Then I just call Zend_Auth::authenticate($adapter) with that adapter and it sets the identity internally.
Now, looking mosre closely at the internals of Zend_Auth::authenticate(), I see that what we could do is just:
Zend_Auth::getInstance()->getStorage()->write($user);
Here's how I solved the problem for the time being. Not sure yet if it's the best solution:
create a wrapper for Zend_Auth
include a getAuthAdapter($raw) method
configure the adapter differently, depending on the value of $raw
if (false === $raw) {
$this->_authAdapter->setCredentialTreatment('SHA1(CONCAT(?,salt))');
} else {
$this->_authAdapter->setCredentialTreatment();
}