|
|
|
Simon Mundy
|
Some javascript/style in this post has been disabled (why?)
Hi there all After submitting the initial example of how Zend_Auth and Zend_Acl could be implemented Gavin pointed out areas that weren't really addressed in my proof of concept and it could potentially confuse newcomers to the way MVC is utilised. I'd like to clarify that post to a) Address those concerns and b) see if there's any constructive criticism of the process that could benefit everyone. Requirements: ------------- Demonstrate a web environment where 'public' (i.e. non-authenticated) users and 'member' users have access restrictions, and to what context they may visit those resources. In a lot of ways this broad concept relates very well to small-medium sites of a lot of Zend developers (in my opinion). For purpose of clarity we will assume this is a SIG group for Mac Users to discuss all things Mac OS X-related. The site has 3 areas (home, news, tutorials) that are for the general public. Members can also view a discussion forum, community newsletter and support request area for members to share common problems. Site layout ------------- Expressed as :controller/:action notation:- /home /news/index /view /email /tutorials/index /view /forum/index /category /view /add /update /reply /search /report - report abuse, etc. /support/index /view /search /submit /confirmation - /comment - add comment /login/index - handles form processing and auth processing /logout/index - destroys current auth instance /error/noroute - handles all 404s /failure - handles 'Site error' messages /privileges - handles 'You are not privileged...' messages /admin - a cms to handle all site management This loosely illustrates the site functionality and content - for the sake of brevity we'll assume that the general concepts and operations of these site functions are understood and familiar. What we're interested in is how to handle user authentication and then access, but at least this gives us some 'real world' understanding of what is required. Access rules: ------------- Three types of user 'roles' have been identified for the site:- guest (not authenticated) - Guests can access 'home', 'news' and 'tutorials' only. Guests attempting to access member-only content will be asked to authenticate. member (authenticated) - Access all top-level controllers. Can update forum posts but only those authored by themselves. Not allowed access to admin section. Access to 'admin' will result in 'privileges' error message. admin (authenticated) - Unrestricted access. Application layout: ------------------- Using the 'Conventional' layout that Gavin outlines in http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+Directory+Layout The bootstrap is located inside /htdocs/index.php Bootstrap: ---------- The bootstrap takes care of the usual suspects - Db, View, Config, Log, Router - and stores them inside the Zend_Front_Controller so that they can be accessed via each controller using the getInvokeArg() method. This negates the need for an extra registry object and (hopefully) makes the dependencies somewhat easier to track. To satisfy the needs of the Access rules, we create a subclassed instance of Zend_Acl like so:- class MyAcl extends Zend_Acl { public function __construct(Zend_Auth $auth) { parent::__construct(); $roleGuest = new Zend_Acl_Role('guest'); $this->add(new Zend_Acl_Resource('home')); $this->add(new Zend_Acl_Resource('news')); $this->add(new Zend_Acl_Resource('tutorials')); $this->add(new Zend_Acl_Resource('forum')); $this->add(new Zend_Acl_Resource('support')); $this->add(new Zend_Acl_Resource('admin')); $this->addRole($roleGuest); $this->addRole(new Zend_Acl_Role('member'), 'guest'); $this->addRole(new Zend_Acl_Role('admin'), 'member'); // Guest may only view content $this->allow('guest', 'home'); $this->allow('guest', 'news'); $this->allow('guest', 'tutorials'); $this->allow('member', 'forum'); $this->deny('member', 'forum', 'update'); // Remove specific privilege $this->allow('member', 'support'); $this->allow('admin'); // unrestricted access // Add authoring ACL check $this->allow('member', 'forum', 'update', new MyAcl_Forum_Assertion($auth)); // NOTE: Dependency on auth object to allow getIdentity() for authenticated user object } } ...and then this is added to the bootstrap. The final index.php file looks something like:- Index.php <?php // Initialise configuration / environment $config = new Zend_Config(new Zend_Config_Ini('../application/config/config.ini', 'live')); // Create sitemap from .ini using structure from example $sitemap = new Zend_Config(new Zend_Config_Ini('../application/config/sitemap.ini', 'live')); // Create db object and enable/disable debugging $db = Zend_Db::factory($config->db->connection, $config->db->asArray()); ...etc... // Create auth object $auth = Zend_Auth::getInstance(); // Create acl object $acl = new MyAcl($auth); // see // Create router and configure (LIFO order for routes) $router = new Zend_Controller_RewriteRouter; ...add rules... // Create view and register objects $view = new My_View; ...init view... $front = Zend_Controller_Front::getInstance(); $front->throwExceptions(true); $front->setRouter($router) ->setDispatcher(new Zend_Controller_ModuleDispatcher()) ->registerPlugin(new My_Plugin_Auth($auth, $acl)) ->registerPlugin(new My_Plugin_Agreement($auth)) ->registerPlugin(new My_Plugin_View($view)) ->setControllerDirectory(array('default' => realpath('../application/controllers/default'), 'admin' => realpath('../application/controllers/admin'))) ->setParam('auth', $auth) ->setParam('view', $view) ->setParam('config', $config) ->setParam('sitemap', $sitemap) ->dispatch(); This is a pretty standard (IMO) bootstrap - the areas to note for the purpose of Authentication/Acl are the two first plugins:- Auth.php The purpose of this plugin is to first determine the 'role' of the current Auth identity. If Zend_Auth::getIdentity() returns false then we don't have a 'role' for the identity, so we assume 'guest'. If a user is authenticated, the Zend_Auth identity would be returned as an object and we would extract the role from this. For simplicity's sake, let's assume that the 'role' is stored in a MySQL database and is returned as a public property from the Identity object (i.e. 'member' or 'admin'). The 'role' is then a one-to-one match against the Acl rules. If we interrogate the Acl and we are allowed to view the current controller (maps to the 'resource' id given to each Acl resource) then the dispatcher continues on its merry way. If the Acl denies the access, we then determine if the user has a valid identity. If not, we tell the request object that we want to redirect to a new controller (login) to perform a login. At this stage, no request data is required - this will be handled via a form in the LoginController. If, however, the identity is valid then we know that access if definitely blocked for that user and we send the request to the 'error' controller to display the 'no privleges' error. I've chosen this strategy as it means that none of the controllers need know anything about the ACL process - they can assume that access to the action has been already approved and need only check action-specific privilege checks (e.g. ensuring they view valid articles, forum threads, etc.) However a developer could still choose to add further ACL rules if required and reduce the amount of ACL-related 'clutter' in the controllers themselves. <?php class My_Plugin_Auth extends Zend_Controller_Plugin_Abstract { private $_auth; private $_acl; private $_noauth = array('module' => 'default', 'controller' => 'login', 'action' => 'index'); private $_noacl = array('module' => 'default', 'controller' => 'error', 'action' => 'privileges'); public function __construct($auth, $acl) { $this->_auth = $auth; $this->_acl = $acl; } public function preDispatch($request) { if ($this->_auth->hasIdentity()) { $role = $this->_auth->getIdentity()->getUser()->role; } else { $role = 'guest'; } $controller = $request->controller; $action = $request->action; $module = $request->module; $resource = $controller; if (!$this->_acl->has($resource)) { $resource = null; } if (!$this->_acl->isAllowed($role, $resource, $action)) { if (!$this->_auth->hasIdentity()) { $module = $this->_noauth['module']; $controller = $this->_noauth['controller']; $action = $this->_noauth['action']; |
||||||||||||||||
|
Bryce Lohr
|
I this a lot; there are a lot of good ideas in there. It seems to me like it
plays nice with the MVC framework. Personally, I think it would be a good start for Gavin's tutorial. :) I think for doing forms-based authentication, this is exactly what you'd want to do. At the moment, I'm still parsing through this trying to figure how to apply it to HTTP Authentication. If I figure it out, I might update the HTTP Authentication Auth adapter to better fit this set up. Regards, Bryce Lohr Simon Mundy wrote: > Hi there all > > After submitting the initial example of how Zend_Auth and Zend_Acl could > be implemented Gavin pointed out areas that weren't really addressed in > my proof of concept and it could potentially confuse newcomers to the > way MVC is utilised. I'd like to clarify that post to a) Address those > concerns and b) see if there's any constructive criticism of the process > that could benefit everyone. |
||||||||||||||||
|
nervo-3
|
In reply to this post
by Simon Mundy
In the boostrap, you are using "$auth = Zend_Auth::getInstance();", but
as far as i know, zend_Auth does not implement the singleton design pattern, no ? Have you made such implementation on your own ? > Hi there all > > After submitting the initial example of how Zend_Auth and Zend_Acl > could be implemented Gavin pointed out areas that weren't really > addressed in my proof of concept and it could potentially confuse > newcomers to the way MVC is utilised. I'd like to clarify that post to > a) Address those concerns and b) see if there's any constructive > criticism of the process that could benefit everyone. > > > *Requirements:* > ------------- > Demonstrate a web environment where 'public' (i.e. non-authenticated) > users and 'member' users have access restrictions, and to what context > they may visit those resources. In a lot of ways this broad concept > relates very well to small-medium sites of a lot of Zend developers > (in my opinion). For purpose of clarity we will assume this is a SIG > group for Mac Users to discuss all things Mac OS X-related. The site > has 3 areas (home, news, tutorials) that are for the general public. > Members can also view a discussion forum, community newsletter and > support request area for members to share common problems. > > *Site layout* > ------------- > Expressed as :controller/:action notation:- > > /home > > /news/index > /view > > /tutorials/index > /view > > /forum/index > /category > /view > /add > /update > /reply > /search > /report - report abuse, etc. > > /support/index > /view > /search > /submit > /confirmation - > /comment - add comment > > /login/index - handles form processing and auth processing > > /logout/index - destroys current auth instance > > /error/noroute - handles all 404s > /failure - handles 'Site error' messages > /privileges - handles 'You are not privileged...' messages > > /admin - a cms to handle all site management > > This loosely illustrates the site functionality and content - for the > sake of brevity we'll assume that the general concepts and operations > of these site functions are understood and familiar. What we're > interested in is how to handle user authentication and then access, > but at least this gives us some 'real world' understanding of what is > required. > > > *Access rules:* > ------------- > Three types of user 'roles' have been identified for the site:- > > *guest* (not authenticated) - Guests can access 'home', 'news' and > 'tutorials' only. Guests attempting to access member-only content will > be asked to authenticate. > > *member* (authenticated) - Access all top-level controllers. Can > update forum posts but only those authored by themselves. Not allowed > access to admin section. Access to 'admin' will result in 'privileges' > error message. > > *admin* (authenticated) - Unrestricted access. > > > *Application layout:* > ------------------- > Using the 'Conventional' layout that Gavin outlines > in http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+Directory+Layout > > The bootstrap is located inside /htdocs/index.php > > > *Bootstrap:* > ---------- > The bootstrap takes care of the usual suspects - Db, View, Config, > Log, Router - and stores them inside the Zend_Front_Controller so that > they can be accessed via each controller using the getInvokeArg() > method. This negates the need for an extra registry object and > (hopefully) makes the dependencies somewhat easier to track. > > To satisfy the needs of the Access rules, we create a subclassed > instance of Zend_Acl like so:- > > class MyAcl extends Zend_Acl > { > public function __construct(Zend_Auth $auth) > { > parent::__construct(); > > $roleGuest = new Zend_Acl_Role('guest'); > > $this->add(new Zend_Acl_Resource('home')); > $this->add(new Zend_Acl_Resource('news')); > $this->add(new Zend_Acl_Resource('tutorials')); > $this->add(new Zend_Acl_Resource('forum')); > $this->add(new Zend_Acl_Resource('support')); > $this->add(new Zend_Acl_Resource('admin')); > > $this->addRole($roleGuest); > $this->addRole(new Zend_Acl_Role('member'), 'guest'); > $this->addRole(new Zend_Acl_Role('admin'), 'member'); > > // Guest may only view content > $this->allow('guest', 'home'); > $this->allow('guest', 'news'); > $this->allow('guest', 'tutorials'); > $this->allow('member', 'forum'); > $this->deny('member', 'forum', 'update'); // Remove specific > privilege > $this->allow('member', 'support'); > $this->allow('admin'); // unrestricted access > > // Add authoring ACL check > $this->allow('member', 'forum', 'update', new > MyAcl_Forum_Assertion($auth)); > // NOTE: Dependency on auth object to allow getIdentity() for > authenticated user object > } > } > > ...and then this is added to the bootstrap. The final index.php file > looks something like:- > > > *Index.php* > <?php > > // Initialise configuration / environment > $config = new Zend_Config(new > Zend_Config_Ini('../application/config/config.ini', 'live')); > > // Create sitemap from .ini using structure from example > $sitemap = new Zend_Config(new > Zend_Config_Ini('../application/config/sitemap.ini', 'live')); > > // Create db object and enable/disable debugging > $db = Zend_Db::factory($config->db->connection, $config->db->asArray()); > ...etc... > > // Create auth object > $auth = Zend_Auth::getInstance(); > > // Create acl object > $acl = new MyAcl($auth); // see > > // Create router and configure (LIFO order for routes) > $router = new Zend_Controller_RewriteRouter; > ...add rules... > > // Create view and register objects > $view = new My_View; > ...init view... > > $front = Zend_Controller_Front::getInstance(); > $front->throwExceptions(true); > $front->setRouter($router) > ->setDispatcher(new Zend_Controller_ModuleDispatcher()) > ->registerPlugin(new My_Plugin_Auth($auth, $acl)) > ->registerPlugin(new My_Plugin_Agreement($auth)) > ->registerPlugin(new My_Plugin_View($view)) > ->setControllerDirectory(array('default' => > realpath('../application/controllers/default'), > 'admin' => > realpath('../application/controllers/admin'))) > ->setParam('auth', $auth) > ->setParam('view', $view) > ->setParam('config', $config) > ->setParam('sitemap', $sitemap) > ->dispatch(); > > > This is a pretty standard (IMO) bootstrap - the areas to note for the > purpose of Authentication/Acl are the two first plugins:- > > *Auth.php* > The purpose of this plugin is to first determine the 'role' of the > current Auth identity. If Zend_Auth::getIdentity() returns false then > we don't have a 'role' for the identity, so we assume 'guest'. If a > user is authenticated, the Zend_Auth identity would be returned as an > object and we would extract the role from this. For simplicity's sake, > let's assume that the 'role' is stored in a MySQL database and is > returned as a public property from the Identity object (i.e. 'member' > or 'admin'). > > The 'role' is then a one-to-one match against the Acl rules. If we > interrogate the Acl and we are allowed to view the current controller > (maps to the 'resource' id given to each Acl resource) then the > dispatcher continues on its merry way. > > If the Acl denies the access, we then determine if the user has a > valid identity. If not, we tell the request object that we want to > redirect to a new controller (login) to perform a login. *At this > stage, no request data is required - this will be handled via a form > in the LoginController*. > > If, however, the identity is valid then we know that access if > definitely blocked for that user and we send the request to the > 'error' controller to display the 'no privleges' error. > > I've chosen this strategy as it means that none of the controllers > need know anything about the ACL process - they can assume that access > to the action has been already approved and need only check > action-specific privilege checks (e.g. ensuring they view valid > articles, forum threads, etc.) > > However a developer could still choose to add further ACL rules if > required and reduce the amount of ACL-related 'clutter' in the > controllers themselves. > > <?php > > class My_Plugin_Auth extends Zend_Controller_Plugin_Abstract > { > private $_auth; > private $_acl; > > private $_noauth = array('module' => 'default', > 'controller' => 'login', > 'action' => 'index'); > > private $_noacl = array('module' => 'default', > 'controller' => 'error', > 'action' => 'privileges'); > > public function __construct($auth, $acl) > { > $this->_auth = $auth; > $this->_acl = $acl; > } > > public function preDispatch($request) > { > if ($this->_auth->hasIdentity()) { > $role = $this->_auth->getIdentity()->getUser()->role; > } else { > $role = 'guest'; > } > > $controller = $request->controller; > $action = $request->action; > $module = $request->module; > $resource = $controller; > > if (!$this->_acl->has($resource)) { > $resource = null; > } > > if (!$this->_acl->isAllowed($role, $resource, $action)) { > if (!$this->_auth->hasIdentity()) { > $module = $this->_noauth['module']; > $controller = $this->_noauth['controller']; > $action = $this->_noauth['action']; > } else { > $module = $this->_noacl['module']; > $controller = $this->_noacl['controller']; > $action = $this->_noacl['action']; > } > } > > $request->setModuleName($module); > $request->setControllerName($controller); > $request->setActionName($action); > } > } > > > *Agreement.php* > Many sites choose to enforce a set of terms and conditions to access. > This intercepting plugin simply checks the Zend_Auth identity method > hasAgreement() (for the sake of demonstration lets just say this is a > boolean property that has been set in the user table of the database). > Again, this is only enacted if an identity exists, and the request is > redirected to a specific agreement controller/action. > > <?php > > class MyPlugin_Agreement extends Zend_Controller_Plugin_Abstract > { > private $_auth; > > private $_noagreement = array('module' => 'default', > 'controller' => 'login', > 'action' => 'agreement'); > > public function __construct($auth) > { > $this->_auth = $auth; > } > > public function preDispatch($request) > { > if ($request->controller != 'logout' && > $this->_auth->hasIdentity()) { > if (!$this->_auth->getIdentity()->getUser()->hasAgreement()) { > $request->setModuleName($this->_noagreement['module']); > > $request->setControllerName($this->_noagreement['controller']); > $request->setActionName($this->_noagreement['action']); > } > } > } > } > > > *Login (Authentication)* > The act of authentication - in my app - all happens within a domain > model - MyForm_Login. Using Matt Zandstra's excellent reference on > Observers/Observable at zend.com as a starting point I have created a > form object that extends the PEAR HTML_Quickform component to allow > one or more observers to be added to the form and activated upon > validation. > > The form is constructed (and auto-populated in my domain-specific > instance with form elements like 'Username', 'Password' and a > 'Remember Me' checkbox), then several observers are added to it. > > When a form validates, the observers are all notified and given an > instance of the form values and the Zend_Auth instance. From there, it > is simply a matter of checking the /sanitised/ form values (we've > applied our form filters, right? :) and passing them to a > domain-specific Zend_Auth_Identity object to query the database, > perform a lookup and then either spit out an error message or start > the login session. > > The example below would also create a hypothetical log observer to > record the login time, date, details, etc. > > BTW, in case you're wondering why the $view->render() isn't called, > it's because I generally have a View_Plugin that's registered in the > the Front_Controller that kicks in during dispatch shutdown. It allows > me to incrementally add components/properties to the view as the > dispatcher loops through all the application actions. > > > *LoginController.php* > class LoginController extends Zend_Controller_Action > { > public function indexAction() > { > $auth = $this->getInvokeArg('auth'); > $view = $this->getInvokeArg('view'); > > if ($auth->hasIdentity()) { > $this->_redirect('/home/index'); // Already authenticated? > Navigate away > } > > $form = new MyForm_Login(); // creates all fields, adds > filters, etc... > $form->attach(new MyPlugin_Login_User($auth); // Perform login > of user identity > $form->attach(new MyPlugin_Login_Log($auth); // Perhaps log > the event? > > if ($form->validate()) { > $this->_redirect('/home/index'); > } > // Render page > $this->getInvokeArg('view')->title = 'Login'; > $this->getInvokeArg('view')->template = 'login/index.tpl'; > $this->getInvokeArg('view')->form = $form->render(); > } > public function agreementAction() > { > $auth = $this->getInvokeArg('auth'); > $view = $this->getInvokeArg('view'); > > $form = new MyForm_Agreement(); > $form->attach(new MyPlugin_Agreement_User($auth)); > > if ($form->validate()) { > $this->_redirect('/home/index'); > } > // Render page > $this->getInvokeArg('view')->title = 'Agreement'; > $this->getInvokeArg('view')->template = 'login/agreement.tpl'; > $this->getInvokeArg('view')->form = $form->render(); > } > } > > > *User.php* > class MyPlugin_Login_User implements Observer > { > function notify($form) > { > $auth = $this->_auth; > $values = $form->exportValues(); > > $adapter = new MyAuth_Adapter(); > $adapter->setUsername($values['username']); > $adapter->setPassword($values['password']); > > try { > $auth->authenticate($adapter); > } catch (MyAuth_Adapter_Exception_Missing $e) { > // Let form know that login has failed... > } catch (MyAuth_Adapter_Exception_Locked $e) { > // Let form know that login has failed... > } > > if (!$auth->isAuthenticated()) { > // Let form know that password was incorrect or your > account is not active... > } > > $identity = $auth->getIdentity(); > > // Retrieve row of user info and store inside Identity object > (including role!) > $userTable = new MyUser_Table; // Instance of Zend_Db_Table or > similar... > $identity->setUser($userTable->find($identity->getIdentifier())); > } > } > > > *Conclusions:* > ------------- > This is obviously an over-simplified example that attempts to address > the challenges of the Acl/Auth components in relation to the MVC > components. > > I believe this approach - though needing some further bulletproofing - > demonstrates good practice and encourages the developer to think about > logical and clean ways of separating the process of authentication. > Some benefits are:- > > * You could easily drop in ACL rules in the one point and have a more > complex and rich set of rules without needing any update to your > controllers, relieving a lot of maintenance issues. > * Processing user input happens only in a single point in an area of > the application that makes it more natural for developers to > understand and build upon. The filters and validation take place in a > separate form model that can be replaced/updated without affecting any > other portion of the process. Post-login business rules can be added > without touching other plugins or the bootstrap. > > I hope this is useful! Would very much appreciate feedback - even if > to say I'm doing it all wrong! :-) I'm still learning a lot from > listening to the discussions on this list and I'm keen to find out how > others approach this kind of layer in their own applications. > > -- > > Simon Mundy | Director | PEPTOLAB > > """ " "" """""" "" "" """"""" " "" """"" " """"" " """""" "" " > 202/258 Flinders Lane | Melbourne | Victoria | Australia | 3000 > Voice +61 (0) 3 9654 4324 | Mobile 0438 046 061 | Fax +61 (0) 3 9654 4124 > http://www.peptolab.com > > |
||||||||||||||||
|
Simon Mundy
|
It may well have changed - I've been using Ralph's proof of concept
from 2 weeks ago which did indeed use a singleton pattern. > In the boostrap, you are using "$auth = Zend_Auth::getInstance > ();", but as far as i know, zend_Auth does not implement the > singleton design pattern, no ? > Have you made such implementation on your own ? >> Hi there all >> >> After submitting the initial example of how Zend_Auth and Zend_Acl >> could be implemented Gavin pointed out areas that weren't really >> addressed in my proof of concept and it could potentially confuse >> newcomers to the way MVC is utilised. I'd like to clarify that >> post to a) Address those concerns and b) see if there's any >> constructive criticism of the process that could benefit everyone. >> >> >> *Requirements:* >> ------------- >> Demonstrate a web environment where 'public' (i.e. non- >> authenticated) users and 'member' users have access restrictions, >> and to what context they may visit those resources. In a lot of >> ways this broad concept relates very well to small-medium sites of >> a lot of Zend developers (in my opinion). For purpose of clarity >> we will assume this is a SIG group for Mac Users to discuss all >> things Mac OS X-related. The site has 3 areas (home, news, >> tutorials) that are for the general public. Members can also view >> a discussion forum, community newsletter and support request area >> for members to share common problems. >> >> *Site layout* >> ------------- >> Expressed as :controller/:action notation:- >> >> /home >> >> /news/index >> /view >> >> /tutorials/index >> /view >> >> /forum/index >> /category >> /view >> /add >> /update >> /reply >> /search >> /report - report abuse, etc. >> >> /support/index >> /view >> /search >> /submit >> /confirmation - /comment - add comment >> >> /login/index - handles form processing and auth processing >> >> /logout/index - destroys current auth instance >> >> /error/noroute - handles all 404s >> /failure - handles 'Site error' messages >> /privileges - handles 'You are not privileged...' messages >> >> /admin - a cms to handle all site management >> >> This loosely illustrates the site functionality and content - for >> the sake of brevity we'll assume that the general concepts and >> operations of these site functions are understood and familiar. >> What we're interested in is how to handle user authentication and >> then access, but at least this gives us some 'real world' >> understanding of what is required. >> >> >> *Access rules:* >> ------------- >> Three types of user 'roles' have been identified for the site:- >> >> *guest* (not authenticated) - Guests can access 'home', 'news' and >> 'tutorials' only. Guests attempting to access member-only content >> will be asked to authenticate. >> >> *member* (authenticated) - Access all top-level controllers. Can >> update forum posts but only those authored by themselves. Not >> allowed access to admin section. Access to 'admin' will result in >> 'privileges' error message. >> >> *admin* (authenticated) - Unrestricted access. >> >> >> *Application layout:* >> ------------------- >> Using the 'Conventional' layout that Gavin outlines in http:// >> framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s >> +Directory+Layout >> >> The bootstrap is located inside /htdocs/index.php >> >> >> *Bootstrap:* >> ---------- >> The bootstrap takes care of the usual suspects - Db, View, Config, >> Log, Router - and stores them inside the Zend_Front_Controller so >> that they can be accessed via each controller using the >> getInvokeArg() method. This negates the need for an extra registry >> object and (hopefully) makes the dependencies somewhat easier to >> track. >> >> To satisfy the needs of the Access rules, we create a subclassed >> instance of Zend_Acl like so:- >> >> class MyAcl extends Zend_Acl >> { >> public function __construct(Zend_Auth $auth) >> { >> parent::__construct(); >> >> $roleGuest = new Zend_Acl_Role('guest'); >> >> $this->add(new Zend_Acl_Resource('home')); >> $this->add(new Zend_Acl_Resource('news')); >> $this->add(new Zend_Acl_Resource('tutorials')); >> $this->add(new Zend_Acl_Resource('forum')); >> $this->add(new Zend_Acl_Resource('support')); >> $this->add(new Zend_Acl_Resource('admin')); >> >> $this->addRole($roleGuest); >> $this->addRole(new Zend_Acl_Role('member'), 'guest'); >> $this->addRole(new Zend_Acl_Role('admin'), 'member'); >> >> // Guest may only view content >> $this->allow('guest', 'home'); >> $this->allow('guest', 'news'); >> $this->allow('guest', 'tutorials'); >> $this->allow('member', 'forum'); >> $this->deny('member', 'forum', 'update'); // Remove >> specific privilege >> $this->allow('member', 'support'); >> $this->allow('admin'); // unrestricted access >> >> // Add authoring ACL check >> $this->allow('member', 'forum', 'update', new >> MyAcl_Forum_Assertion($auth)); >> // NOTE: Dependency on auth object to allow getIdentity() >> for authenticated user object >> } >> } >> >> ...and then this is added to the bootstrap. The final index.php >> file looks something like:- >> >> >> *Index.php* >> <?php >> >> // Initialise configuration / environment >> $config = new Zend_Config(new Zend_Config_Ini('../application/ >> config/config.ini', 'live')); >> >> // Create sitemap from .ini using structure from example >> $sitemap = new Zend_Config(new Zend_Config_Ini('../application/ >> config/sitemap.ini', 'live')); >> >> // Create db object and enable/disable debugging >> $db = Zend_Db::factory($config->db->connection, $config->db- >> >asArray()); >> ...etc... >> >> // Create auth object >> $auth = Zend_Auth::getInstance(); >> >> // Create acl object >> $acl = new MyAcl($auth); // see >> // Create router and configure (LIFO order for routes) >> $router = new Zend_Controller_RewriteRouter; >> ...add rules... >> >> // Create view and register objects >> $view = new My_View; >> ...init view... >> >> $front = Zend_Controller_Front::getInstance(); >> $front->throwExceptions(true); >> $front->setRouter($router) >> ->setDispatcher(new Zend_Controller_ModuleDispatcher()) >> ->registerPlugin(new My_Plugin_Auth($auth, $acl)) >> ->registerPlugin(new My_Plugin_Agreement($auth)) >> ->registerPlugin(new My_Plugin_View($view)) >> ->setControllerDirectory(array('default' => realpath('../ >> application/controllers/default'), >> 'admin' => realpath('../ >> application/controllers/admin'))) >> ->setParam('auth', $auth) >> ->setParam('view', $view) >> ->setParam('config', $config) >> ->setParam('sitemap', $sitemap) >> ->dispatch(); >> >> >> This is a pretty standard (IMO) bootstrap - the areas to note for >> the purpose of Authentication/Acl are the two first plugins:- >> >> *Auth.php* >> The purpose of this plugin is to first determine the 'role' of the >> current Auth identity. If Zend_Auth::getIdentity() returns false >> then we don't have a 'role' for the identity, so we assume >> 'guest'. If a user is authenticated, the Zend_Auth identity would >> be returned as an object and we would extract the role from this. >> For simplicity's sake, let's assume that the 'role' is stored in a >> MySQL database and is returned as a public property from the >> Identity object (i.e. 'member' or 'admin'). >> >> The 'role' is then a one-to-one match against the Acl rules. If we >> interrogate the Acl and we are allowed to view the current >> controller (maps to the 'resource' id given to each Acl resource) >> then the dispatcher continues on its merry way. >> >> If the Acl denies the access, we then determine if the user has a >> valid identity. If not, we tell the request object that we want to >> redirect to a new controller (login) to perform a login. *At this >> stage, no request data is required - this will be handled via a >> form in the LoginController*. >> >> If, however, the identity is valid then we know that access if >> definitely blocked for that user and we send the request to the >> 'error' controller to display the 'no privleges' error. >> >> I've chosen this strategy as it means that none of the controllers >> need know anything about the ACL process - they can assume that >> access to the action has been already approved and need only check >> action-specific privilege checks (e.g. ensuring they view valid >> articles, forum threads, etc.) >> >> However a developer could still choose to add further ACL rules if >> required and reduce the amount of ACL-related 'clutter' in the >> controllers themselves. >> >> <?php >> >> class My_Plugin_Auth extends Zend_Controller_Plugin_Abstract >> { >> private $_auth; >> private $_acl; >> >> private $_noauth = array('module' => 'default', >> 'controller' => 'login', >> 'action' => 'index'); >> >> private $_noacl = array('module' => 'default', >> 'controller' => 'error', >> 'action' => 'privileges'); >> public function __construct($auth, $acl) >> { >> $this->_auth = $auth; >> $this->_acl = $acl; >> } >> >> public function preDispatch($request) >> { >> if ($this->_auth->hasIdentity()) { >> $role = $this->_auth->getIdentity()->getUser()->role; >> } else { >> $role = 'guest'; >> } >> $controller = $request->controller; >> $action = $request->action; >> $module = $request->module; >> $resource = $controller; >> if (!$this->_acl->has($resource)) { >> $resource = null; >> } >> >> if (!$this->_acl->isAllowed($role, $resource, $action)) { >> if (!$this->_auth->hasIdentity()) { >> $module = $this->_noauth['module']; >> $controller = $this->_noauth['controller']; >> $action = $this->_noauth['action']; >> } else { >> $module = $this->_noacl['module']; >> $controller = $this->_noacl['controller']; >> $action = $this->_noacl['action']; >> } >> } >> >> $request->setModuleName($module); >> $request->setControllerName($controller); >> $request->setActionName($action); >> } >> } >> >> >> *Agreement.php* >> Many sites choose to enforce a set of terms and conditions to >> access. This intercepting plugin simply checks the Zend_Auth >> identity method hasAgreement() (for the sake of demonstration lets >> just say this is a boolean property that has been set in the user >> table of the database). Again, this is only enacted if an identity >> exists, and the request is redirected to a specific agreement >> controller/action. >> >> <?php >> >> class MyPlugin_Agreement extends Zend_Controller_Plugin_Abstract >> { >> private $_auth; >> >> private $_noagreement = array('module' => 'default', >> 'controller' => 'login', >> 'action' => 'agreement'); >> public function __construct($auth) >> { >> $this->_auth = $auth; >> } >> >> public function preDispatch($request) >> { >> if ($request->controller != 'logout' && $this->_auth- >> >hasIdentity()) { >> if (!$this->_auth->getIdentity()->getUser()- >> >hasAgreement()) { >> $request->setModuleName($this->_noagreement >> ['module']); >> $request->setControllerName($this->_noagreement >> ['controller']); >> $request->setActionName($this->_noagreement >> ['action']); >> } >> } >> } >> } >> >> >> *Login (Authentication)* >> The act of authentication - in my app - all happens within a >> domain model - MyForm_Login. Using Matt Zandstra's excellent >> reference on Observers/Observable at zend.com as a starting point >> I have created a form object that extends the PEAR HTML_Quickform >> component to allow one or more observers to be added to the form >> and activated upon validation. >> >> The form is constructed (and auto-populated in my domain-specific >> instance with form elements like 'Username', 'Password' and a >> 'Remember Me' checkbox), then several observers are added to it. >> >> When a form validates, the observers are all notified and given an >> instance of the form values and the Zend_Auth instance. From >> there, it is simply a matter of checking the /sanitised/ form >> values (we've applied our form filters, right? :) and passing them >> to a domain-specific Zend_Auth_Identity object to query the >> database, perform a lookup and then either spit out an error >> message or start the login session. >> >> The example below would also create a hypothetical log observer to >> record the login time, date, details, etc. >> >> BTW, in case you're wondering why the $view->render() isn't >> called, it's because I generally have a View_Plugin that's >> registered in the the Front_Controller that kicks in during >> dispatch shutdown. It allows me to incrementally add components/ >> properties to the view as the dispatcher loops through all the >> application actions. >> >> >> *LoginController.php* >> class LoginController extends Zend_Controller_Action >> { >> public function indexAction() >> { >> $auth = $this->getInvokeArg('auth'); >> $view = $this->getInvokeArg('view'); >> >> if ($auth->hasIdentity()) { >> $this->_redirect('/home/index'); // Already authenticated? >> Navigate away >> } >> >> $form = new MyForm_Login(); // creates all fields, adds >> filters, etc... >> $form->attach(new MyPlugin_Login_User($auth); // Perform >> login of user identity >> $form->attach(new MyPlugin_Login_Log($auth); // Perhaps >> log the event? >> >> if ($form->validate()) { >> $this->_redirect('/home/index'); >> } >> // Render page >> $this->getInvokeArg('view')->title = 'Login'; >> $this->getInvokeArg('view')->template = 'login/index.tpl'; >> $this->getInvokeArg('view')->form = $form->render(); >> } >> public function agreementAction() >> { >> $auth = $this->getInvokeArg('auth'); >> $view = $this->getInvokeArg('view'); >> >> $form = new MyForm_Agreement(); >> $form->attach(new MyPlugin_Agreement_User($auth)); >> >> if ($form->validate()) { >> $this->_redirect('/home/index'); >> } >> // Render page >> $this->getInvokeArg('view')->title = 'Agreement'; >> $this->getInvokeArg('view')->template = 'login/ >> agreement.tpl'; >> $this->getInvokeArg('view')->form = $form->render(); >> } >> } >> >> >> *User.php* >> class MyPlugin_Login_User implements Observer >> { >> function notify($form) >> { >> $auth = $this->_auth; >> $values = $form->exportValues(); >> >> $adapter = new MyAuth_Adapter(); >> $adapter->setUsername($values['username']); >> $adapter->setPassword($values['password']); >> try { >> $auth->authenticate($adapter); >> } catch (MyAuth_Adapter_Exception_Missing $e) { >> // Let form know that login has failed... >> } catch (MyAuth_Adapter_Exception_Locked $e) { >> // Let form know that login has failed... >> } >> if (!$auth->isAuthenticated()) { >> // Let form know that password was incorrect or your >> account is not active... >> } >> $identity = $auth->getIdentity(); >> // Retrieve row of user info and store inside Identity >> object (including role!) >> $userTable = new MyUser_Table; // Instance of >> Zend_Db_Table or similar... >> $identity->setUser($userTable->find($identity- >> >getIdentifier())); >> } >> } >> >> >> *Conclusions:* >> ------------- >> This is obviously an over-simplified example that attempts to >> address the challenges of the Acl/Auth components in relation to >> the MVC components. >> >> I believe this approach - though needing some further >> bulletproofing - demonstrates good practice and encourages the >> developer to think about logical and clean ways of separating the >> process of authentication. Some benefits are:- >> >> * You could easily drop in ACL rules in the one point and have a >> more complex and rich set of rules without needing any update to >> your controllers, relieving a lot of maintenance issues. * >> Processing user input happens only in a single point in an area of >> the application that makes it more natural for developers to >> understand and build upon. The filters and validation take place >> in a separate form model that can be replaced/updated without >> affecting any other portion of the process. Post-login business >> rules can be added without touching other plugins or the bootstrap. >> >> I hope this is useful! Would very much appreciate feedback - even >> if to say I'm doing it all wrong! :-) I'm still learning a lot >> from listening to the discussions on this list and I'm keen to >> find out how others approach this kind of layer in their own >> applications. >> >> -- >> >> Simon Mundy | Director | PEPTOLAB >> >> """ " "" """""" "" "" """"""" " "" """"" " """"" " """""" "" " >> 202/258 Flinders Lane | Melbourne | Victoria | Australia | 3000 >> Voice +61 (0) 3 9654 4324 | Mobile 0438 046 061 | Fax +61 (0) 3 >> 9654 4124 >> http://www.peptolab.com >> >> > -- Simon Mundy | Director | PEPTOLAB """ " "" """""" "" "" """"""" " "" """"" " """"" " """""" "" " 202/258 Flinders Lane | Melbourne | Victoria | Australia | 3000 Voice +61 (0) 3 9654 4324 | Mobile 0438 046 061 | Fax +61 (0) 3 9654 4124 http://www.peptolab.com |
||||||||||||||||
|
Wojciech Naruniec
|
In reply to this post
by nervo-3
Some javascript/style in this post has been disabled (why?)
Hi,You can use something like this: $auth = new Zend_Auth(new My_Auth_Db_Adapter()); Greetings, Wojciech Naruniec On 07.02.2007, at 13:15 PM, nerVo wrote:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||