Can (and should?) Zend_Auth return class as the Identity? - zend-framework

I have a class R00_Model_User, which, curiously enough, represents user as he is. Can $result->getIdentity() return me an object of this class? (Or maybe it's stupid?)
(There is a factory method in R00_Model_User which prevents from duplicating objects. I'd like Zend_Auth to use it instead of creating a new object, if it can)

Two options:
write your own authentication adapter subclassing the out-of-the-box-adapter that matches your scenario best
class R00_Auth_Adapter extends Zend_Auth_Adapter_*
{
/**
* authenticate() - defined by Zend_Auth_Adapter_Interface. This method is called to
* attempt an authentication. Previous to this call, this adapter would have already
* been configured with all necessary information to successfully connect to a database
* table and attempt to find a record matching the provided identity.
*
* #throws Zend_Auth_Adapter_Exception if answering the authentication query is impossible
* #return Zend_Auth_Result
*/
public function authenticate()
{
$result = parent::authenticate();
if ($result->isValid() {
return new Zend_Auth_Result(
$result->getCode(),
R00_Model_User::load($result->getIdentity()),
$result->getMessages()
);
} else {
return $result;
}
}
}
This will allow you to code
$adapter = new R00_Auth_Adapter();
//... adapter initialisation (username, password, etc.)
$result = Zend_Auth::getInstance()->authenticate($adapter);
and on successfull authentication your user-object is automatically stored in the authentication storage (session by default).
or use your login-action to update the stored user identity
$adapter = new Zend_Auth_Adapter_*();
$result = $adapter->authenticate();
if ($result->isValid()) {
$user = R00_Model_User::load($result->getIdentity());
Zend_Auth::getInstance()->getStorage()->write($user);
}

In one of my applications, I have getIdentity() return a user object, and it works pretty well for me. To use your factory method, do like this:
$auth = Zend_Auth::getInstance();
$user = R00_Model_User::getInstance(...);
$auth->getStorage()->write($user);
Then when you call getIdentity(), you will have your user object.

Related

capturing the login event in zendframework 2

I want to capture the loggin event in zendframework2 and then update the database when the user last logged in.
i am aware that if i do the following in the onBootstrap(MVCEvent $e) of my module i will be able to capture the event etc:
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
$em->attach(
'ZfcUser\Authentication\Adapter\AdapterChain',
'authenticate',
function($e)
{
$id = $e->getIdentity();
}
);
this will give me the Id of the user. however, the confusion is how i can then update my database from the bootstrap. i mean, i dont have access to the entity manager in my bootstrap and i am not sure how to transport it there. the entity manager is held in the service config file.
i.e
getServiceConfig()
'Members\Model\WorkerTable' => function($sm) {
$db = $sm->get('doctrine.entitymanager.orm_default');
$table = new Model\MemberTable($db);
return $table;
},
.
with the above settings i am able to access the entity manager in my MemberTable class
so, a simple solution would be to transfer the loggin event manager to my
** MemberTable class** where i would use my entity manager to update the database.
issue, i am not sure how to set this up:
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
i mean, i dont know how to get the variable $e into the MemberTable class so that i can access the eventManger and the sharedManager.
in summary; the issues are twofold.
how do i get the entity manager into the bootstrap function
alternatively
how do i get the eventManager and shared eventmanager into a normal class so that i can call the eventmanger in a class that already contains the entity manager
Well, all you have to do is to retrieve the service manager this way:
$serviceManager = $e->getApplication()->getServiceManager();
and then get your entity manager like this:
$entityManager = $serviceManager->get('Members\Model\WorkerTable');
Does it solve your problem?
The answer to the second question
To bring a variable into the closure from outside you can use "use" operator like so:
function() use ($myVar){
// some code
}
So, in your case I would do:
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
$em->attach(
'ZfcUser\Authentication\Adapter\AdapterChain',
'authenticate',
function($e) use ($entityManager){
$id = $e->getIdentity();
}
);
If you are planning to have a lot of code inside your closure I would suggest to put it into a separate class and make it invokable. For instance,
class YourClosureCode
{
private $entityManager;
public function __construct($eventManager)
{
$this->eventManager = $eventManager;
}
public function __invoke(EventInterface $e)
{
// put your closure code here
}
}
Then a slight modification here:
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
$em->attach(
'ZfcUser\Authentication\Adapter\AdapterChain',
'authenticate',
new YourClosureCode($entityManager);
);

Laravel 4 Auth with Facebook (no password authentication)

I'm trying to set up an authentication system with Laravel 4 with a Facebook login. I am using the madewithlove/laravel-oauth2 package for Laravel 4.
Of course, there is no password to add to my database upon a user loggin in with Facebook. I am, however, trying to check to see if a user id is in the database already to determine if I should create a new entity, or just log in the current one. I would like to use the Auth commands to do this. I have a table called "fans".
This is what I'm working with:
$fan = Fan::where('fbid', '=', $user['uid']);
if(is_null($fan)) {
$fan = new Fan;
$fan->fbid = $user['uid'];
$fan->email = $user['email'];
$fan->first_name = $user['first_name'];
$fan->last_name = $user['last_name'];
$fan->gender = $user['gender'];
$fan->birthday = $user['birthday'];
$fan->age = $age;
$fan->city = $city;
$fan->state = $state;
$fan->image = $user['image'];
$fan->save();
return Redirect::to('fans/home');
}
else {
Auth::login($fan);
return Redirect::to('fans/home');
}
Fan Model:
<?php
class Fan extends Eloquent {
protected $guarded = array();
public static $rules = array();
}
When I run this, I get the error:
Argument 1 passed to Illuminate\Auth\Guard::login() must be an instance of Illuminate\Auth\UserInterface, instance of Illuminate\Database\Eloquent\Builder given
EDIT: When I use: $fan = Fan::where('fbid', '=', $user['uid'])->first();
I get the error:
Argument 1 passed to Illuminate\Auth\Guard::login() must be an instance of Illuminate\Auth\UserInterface, null given, called in /Applications/MAMP/htdocs/crowdsets/laravel-master/vendor/laravel/framework/src/Illuminate/Auth/Guard.php on line 368 and defined
I do not know why it is giving me this error. Do you have suggestions on how I can make this work? Thank you for your help.
You have to implement UserInterface to your model for Auth to work properly
use Illuminate\Auth\UserInterface;
class Fan extends Eloquent implements UserInterface{
...
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
}
getAuthIdentifier and getAuthPassword are abstract method and must be implemented in you class implementing UserInterface
To login any user into the system, you need to use the User model, and I bet inherited classes will do the trick as well but I'm not sure.
Anyway, your Fan model does not associate with the User model/table in any way and that's a problem. If your model had a belong_to or has_one relationship and a user_id field then you could replace Auth::login($user) with Auth::loginUsingId(<some id>).
Original answer:
You are missing an extra method call: ->get() or ->first() to actually retrieve the results:
$fan = Fan::where('fbid', '=', $user['uid'])->first();
Alternatively, you can throw an exception to see what's going on:
$fan = Fan::where('fbid', '=', $user['uid'])->firstOrFail();
If you see different errors, update your question with those errors.

In Zend, is there a better way to check if a user hasIdentity()?

Currently, I am using Zend_Auth::getInstance()->hasIdentity() to check if my user is logged in in every controller that requires a login. I feel like I am practicing Zend horribly, so I wanted to ask the more experienced and know if this is the proper way to do this? If not, could you please tell me what is?
We use a Controller plugin (bootstrapped in application.ini config file) that handles our authentications. It checks the requested controller/action in the preDispatch() phase and matches against ACL objects (could be fetched out of DB, config files, XML, etc.). If the user does not have the privilege to access the target controller/action, the a message is stored in the session and user is redirected to another page, displaying the access forbidden message.
If the user needs to have authentication to access the target controller/action, user is redirected to the login action by modifying the request object.
Using this plugin there is no need to check for user authentication/ACL in each controller and so all the "Access" code would be enclosed in one file, the "Access Plugin".
In order to check for user identity we mostly use the same method of "Zend_Auth::getInstance()->hasIdenity()", but this just shows if the user is authenticated or not. the '''getIdentity()''' method of Zend_Auth returns the current user identity, but again just the identity and not more. However if you would need more information of the user, you could store the user information in a session.
We implement our users as data models, so each user is defined as an object. after a user is authenticated on the login action, we create the appropriate user object and store it in the user session, like this:
// This could be a sample code in AuthController/processloginAction()
// suppose $username is validated before and stores the username
$user = new Default_Model_User($username);
// now $user is our user object, suppose $log is a Zend_Log instance
$log->info("user id '{$user->getId()}' username: '{$user->getUsername()}' logged in");
$sess = Zend_Session_Namespace('auth');
$sess->user = $user;
From now one, the $user property of the session namespace of 'auth' is the user object with all the information you would need about, not just the identity. and whenever you wanted to check if user is logged in (beside using Zend_Auth) you could check for availability of this value on the user session:
$sess = Zend_Session_Namespace('auth');
if (!isset($sess->user) || !$sess->user) {
// user is not logged in, redirect to login page
}
$user = $sess->user;
/*#var $user Default_Model_User*/
$email = $user->getEmail();
now we checked for authentication, and have access to user information (email, phone, etc.).
I use a method similar to the method described by Herman Radtke in his blog at http://www.hermanradtke.com/blog/more-reliable-authentication-in-zend-framework/. Basically create a controller plugin as farzad mentioned:
class My_Authentication extends Zend_Controller_Plugin_Abstract
{
private $_whitelist;
public function __construct()
{
$this->_whitelist = array(
'index/login'
);
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$controller = strtolower($request->getControllerName());
$action = strtolower($request->getActionName());
$route = $controller . '/' . $action;
if (in_array($route, $this->_whitelist)) {
return;
}
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
return;
}
self::setDispatched(false);
// handle unauthorized request...
}
}
and then register that plugin in your bootstrap:
public function run() {
$front->registerPlugin(new My_Authentication());
}
I generally take this approach a little farther and integrate the Zend_Acl into the system as well. To do that I would define the plugin below:
class My_Acl_Authentication extends Zend_Controller_Plugin_Abstract
{
private $_acl;
public function __construct($acl)
{
$this->_acl = $acl
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$controller = strtolower($request->getControllerName());
$action = strtolower($request->getActionName());
$route = $controller . '/' . $action;
if (in_array($route, $this->_whitelist)) {
return;
}
$auth = Zend_Auth::getInstance();
$role = 'anonymous';
if ($auth->hasIdentity()) {
$role = $auth->getStorage->read()->role;
}
if ($this->_acl->isAllowed($role, $route)) {
return;
}
self::setDispatched(false);
// handle unauthorized request...
}
}
If you go this route there is some more work to be done, specifically you have to setup the ACL and then you also have to store the user's role in the auth storage.
Thats perfectly ok to do so but to save you from repeating that code you can extend all your controllers from a class A which is subclass of Zend_Controller_Action . Then inside this class declare a method
protected function hasIdentity()
{
return Zend_Auth::getInstance()->hasIdentity();
}
Now in your controller which is subclass of A you can simply do $this->hasIdentity(); instead

How does Zend_Auth storage work?

This is very simple. I write
$auth->getStorage()->write($user);
And then I want, in a separate process to load this $user, but I can't because
$user = $auth->getIdentity();
is empty. Didn't I just... SET it? Why does it not work? Halp?
[EDIT 2011-04-13]
This has been asked almost two years ago. Fact is, though, that I repeated the question in July 2010 and got a fantastic answer that I back then simply did not understand.
Link: Zend_Auth fails to write to storage
I have since built a very nice litte class that I use (sometimes with extra tweaking) in all my projects using the same storage engine as Zend_Auth but circumventing all the bad.
<?php
class Qapacity_Helpers_Storage {
public function save($name = 'default', $data) {
$session = new Zend_Session_Namespace($name);
$session->data = $data;
return true;
}
public function load($name = 'default', $part = null) {
$session = new Zend_Session_Namespace($name);
if (!isset($session->data))
return null;
$data = $session->data;
if ($part && isset($data[$part]))
return $data[$part];
return $data;
}
public function clear($name = 'default') {
$session = new Zend_Session_Namespace($name);
if (isset($session->data))
unset($session->data);
return true;
}
}
?>
It's supposed to work.
Here's the implementation of the Auth getIdentity function.
/**
* Returns the identity from storage or null if no identity is available
*
* #return mixed|null
*/
public function getIdentity()
{
$storage = $this->getStorage();
if ($storage->isEmpty()) {
return null;
}
return $storage->read();
}
Here's the implementation of the PHP Session Storage write and read functions:
/**
* Defined by Zend_Auth_Storage_Interface
*
* #return mixed
*/
public function read()
{
return $this->_session->{$this->_member};
}
/**
* Defined by Zend_Auth_Storage_Interface
*
* #param mixed $contents
* #return void
*/
public function write($contents)
{
$this->_session->{$this->_member} = $contents;
}
Are you sure you are loading the same instance of the Zend_Auth class?
Are you using
$auth = Zend_Auth::getInstance();
Maybe you are calling the write method after the getIdentity method?
Anyway, as I said before, what you are doing should work.
So on a page reload you can fetch the session, while not if redirecting? Are you redirecting on a different Domain-Name? Then it might be an issues with the Cookies and you'd need to manually set session.cookie_domain ini variable.
Check if the cookie named PHPSESSID has been properly been set and if it's being sent to the server on every page request? Is it constant or does it change on every request?
Also, you might want to check if the session data is being properly saved to the disk. The sessions can be found in the directory defined by the ini variable session.save_path. Is the file corresponding to your PHPSESSID there and does it contain a meaningful entry? In my case it contains
root#ip-10-226-50-144:~# less /var/lib/php5/sess_081fee38856c59a563cc320899f6021f
foo_auth|a:1:{s:7:"storage";a:1:{s:9:"user_id";s:2:"77";}}
Add:
register_shutdown_function('session_write_close');
to index.php before:
$application->bootstrap()->run();

Zend Framework Authentication and Redirection

What is the best method in Zend Framework to provide restricted areas and redirect users to a login page? What I want to do is set a flag on my controllers for restricted pages:
class AdminController extends Zend_Controller_Action
{
protected $_isRestricted = true;
....
and have a plugin check to see if the controller is restricted and if the user has authenticated, otherwise redirect them to the login page. If I do this directly in the controller's preDispatch I can use $this->_redirect(), but looking at Action Helpers they won't have access to that. It's also a lot of duplicate code to copy/paste the authentication check code in every controller that needs it.
Do I need an Action Controller linked to preDispatch, or a Front Controller plugin? How would I do the redirect and still preserve things like the base URL?
Use Zend_Acl (best combined with Zend_Auth)
See also Practical Zend_ACL + Zend_Auth implementation and best practices
For one project, I've extended Zend_Controller_Action, and in that class's preDispatch put a check for logged-in-ness. I can override it on a per-action basis with an init() that checks the actionname and turns off the requirement (or preDispatch() that calls it's parent for the actual checks).
In a project I was working on, I had trouble with various users experiencing timeouts from their browsers. This meant the Zend_Auth no longer existed in the registry and users lost access to required pages/functions.
In order to stop this from occuring, I setup a Plugin (as you suggest) and have this plugin perform checks in the preDispatch(). An example is below:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
public function run()
{
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new App_Controller_Plugin_Timeout());
parent::run();
}
}
with the timeout class implementing any Zend_Auth or Zend_Acl requirements, using a check via the function below.
class App_Controller_Plugin_Timeout extends Zend_Controller_Plugin_Abstract
{
/**
* Validate that the user session has not timed out.
* #param Zend_Controller_Request_Abstract $request
* #return void
* #todo Validate the user has access to the requested page using Zend_Acl
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$frontController = Zend_Controller_Front::getInstance();
$controllerName = $frontController->getRequest()->getControllerName();
$actionName = $frontController->getRequest()->getActionName();
$authInstance = Zend_Auth::getInstance();
/** If the controller is not the Auth or Error controller, then check for
* a valid authorized user and redirect to the login page if none found */
if (($controllerName !== 'auth') && ($controllerName !== 'index') && ($controllerName !== 'error')) {
if (!$authInstance->hasIdentity()) {
$this->_response->setRedirect('/index/timeout')->sendResponse();
exit;
}
} else if (($controllerName == 'index') || (($controllerName == 'auth') && ($actionName !== 'logout'))) {
/** If running the Auth or Index (default) controller (and not the logout
* action), check if user already signed in and redirect to the welcome page */
if ($authInstance->hasIdentity()) {
$this->_response->setRedirect('/general/welcome')->sendResponse();
exit;
}
}
}
}
....
/**
* Test that the input user belongs to a role based on the user input and
* the values loaded into the Acl registry object setup when the site first
* loads
*
* #param mixed|Zend_Auth $userData
* #param string $userRole
* #return boolean
* #throws Zend_Exception When invalid input is provided
*/
public function isUserMemberOfRole($userData, $userRole)
{
if (empty($userData)) {
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
$userData = $auth->getIdentity();
} else {
return FALSE;
}
}
if (!is_string($userRole)){
throw new Zend_Exception('Invalid input provided to ' . __METHOD__);
}
// Setup the required variables and access the registry for the Acl values
$rolesTable = new App_Model_Internal_UsersToRoles();
$registry = Zend_Registry::getInstance();
$acl = $registry->get('acl');
$roles = $rolesTable->getUserRoles($userData); // returns an array of values
foreach ($roles as $value) {
if ($value['Name'] == $userRole) {
return $acl->isAllowed($value['Name'], null, $userRole);
}
}
}
I had the user access implemented in a database table and then initialized as an "_init" function at Bootstrap->run() as follows:
protected function _initAclObjectForUserRoles()
{
$userTable = new App_Model_Internal_Roles();
$acl = new Zend_Acl();
$userRoles = $userTable->fetchAll();
$roles = $userRoles->toArray();
// Cycle through each Role and set the allow status for each
foreach($roles as $value) {
$department = $value['Name'];
$acl->addRole(new Zend_Acl_Role($department));
$acl->allow($department, null, $department);
}
// Add the new Acl to the registry
$registry = Zend_Registry::getInstance();
$registry->set('acl', $acl);
}
So, using this method you could put access restrictions via the roles loaded via from a database into an Zend_Acl object, or you could load the controller class attribute via the Timeout plugin and check it's value. Although, I've found it's easier to maintain access policies in a database than spread them throughout your code base... :-)