multiuser-authorization.xml 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- Reviewed: no -->
  3. <sect1 id="learning.multiuser.authentication">
  4. <title>Building an Authorization System in ZF</title>
  5. <sect2 id="learning.multiuser.authorization.intro">
  6. <title>Introduction to Authorization</title>
  7. <para>
  8. After a user has been identified as being authentic, an application can go about its
  9. business of providing some useful and desirable resources to a consumer. In many cases,
  10. applications might contain different resource types, with some resources having stricter
  11. rules regarding access. This process of determining who has access to which resources is
  12. the process of "authorization". Authorization in its simplest form is the composition of
  13. these elements:
  14. </para>
  15. <itemizedlist>
  16. <listitem>
  17. <para>
  18. the identity whom wishes to be granted access
  19. </para>
  20. </listitem>
  21. <listitem>
  22. <para>
  23. the resource the identity is asking permission to consume
  24. </para>
  25. </listitem>
  26. <listitem>
  27. <para>
  28. and optionally, what the identity is privileged to do with the resource
  29. </para>
  30. </listitem>
  31. </itemizedlist>
  32. <para>
  33. In ZF, the Zend_Acl component handles the task of building a tree of roles, resources and
  34. privileges to manage and query authorization requests against.
  35. </para>
  36. </sect2>
  37. <sect2 id="learning.multiuser.authorization.basic-usage">
  38. <title>Basic Usage of Zend_Acl</title>
  39. <!-- explain the interaction with a User object, how -->
  40. <para>
  41. When using Zend_Acl, any models can serve as roles or resources by simply implementing
  42. the proper interface. To be used in a role capacity, the class must implement the
  43. Zend_Acl_Role_Interface, which requires only getRoleId(). To be used in a resource
  44. capacity, a class must implement the Zend_Acl_Resource_Interface which similarly requires
  45. the class implement the getResourceId() method.
  46. </para>
  47. <para>
  48. Demonstrated below is a simple user model. This model can take part in our ACL system
  49. simply by implementing the Zend_Acl_Role_Interface. The method getRoleId() will return
  50. the id "guest" when an ID is not known, or it will return the role ID that was assigned
  51. to this actual user object. This value can effectively come from anywhere, a static definition
  52. or perhaps dynamically from the users database role itself.
  53. </para>
  54. <programlisting language="php"><![CDATA[
  55. class Default_Model_User implements Zend_Acl_Role_Interface
  56. {
  57. protected $_aclRoleId = null;
  58. public function getRoleId()
  59. {
  60. if ($this->_aclRoleId == null) {
  61. return 'guest';
  62. }
  63. return $this->_aclRoleId;
  64. }
  65. }
  66. ]]></programlisting>
  67. <para>
  68. While the concept of a user as a role is pretty straight forward, your application might
  69. choose to have any other models in your system as a potential "resource" to be consumed in
  70. this ACL system. For simplicity, we'll use the example of a blog post. Since the type of
  71. the resource is tied to the type of the object, this class will only return 'blogPost' as
  72. the resource ID in this system. Naturally, this value can be dynamic if your system requires
  73. it to be so.
  74. </para>
  75. <programlisting language="php"><![CDATA[
  76. class Default_Model_BlogPost implements Zend_Acl_Resource_Interface
  77. {
  78. public function getResourceId()
  79. {
  80. return 'blogPost';
  81. }
  82. }
  83. ]]></programlisting>
  84. <para>
  85. Now that we have at least a role and a resource, we can go about defining the rules of the
  86. ACL system. These rules will be consulted when the system receives a query about what is
  87. possible given a certain role, resources, and optionally a privilege.
  88. </para>
  89. <para>
  90. Lets assume the following rules:
  91. </para>
  92. <programlisting language="php"><![CDATA[
  93. $acl = new Zend_Acl();
  94. // setup the various roles in our system
  95. $acl->addRole('guest');
  96. $acl->addRole('owner', 'guest'); // owner inherits all of the rules of guest
  97. // add the resources
  98. $acl->addResource('blogPost');
  99. // add privileges to roles and resource combinations
  100. $acl->allow('guest', 'blogPost', 'view');
  101. $acl->allow('owner', 'blogPost', 'post');
  102. $acl->allow('owner', 'blogPost', 'publish');
  103. ]]></programlisting>
  104. <para>
  105. The above rules are quite simple: a guest role and an owner role exist; as does a blogPost
  106. type resource. Guests are allowed to view blog posts, and owners are allowed to post and publish
  107. blog posts. To query this system one might do any of the following:
  108. </para>
  109. <programlisting language="php"><![CDATA[
  110. $guestUser = new Default_Model_User(); // assume the user model is of type guest resource
  111. $ownerUser = new Default_Model_Owner('OwnersUsername');
  112. $post = new Default_Model_BlogPost();
  113. $acl->isAllowed($guestUser, $post, 'view'); // true
  114. $acl->isAllowed($ownerUser, $post, 'view'); // true
  115. $acl->isAllowed($guestUser, $post, 'post'); // false
  116. $acl->isAllowed($ownerUser, $post, 'post'); // true
  117. ]]></programlisting>
  118. <para>
  119. As you can see, the above rules exercise whether owners and guests can view posts, which
  120. they can, or post new posts, which owners can and guests cannot. But as you might expect
  121. this type of system might not be as dynamic as we wish it to be. What if we want to ensure
  122. a specific owner actual owns a very specific blog post before allowing him to publish it?
  123. In other words, we want to ensure that only post owners have the ability to publish their
  124. own posts.
  125. </para>
  126. <para>
  127. This is where assertions come in. Assertions are methods that will be called out to
  128. when the static rule checking is simply not enough. When registering an assertion object
  129. this object will be consulted to determine, typically dynamically, if some roles has access
  130. to some resource, with some optional privlidge that can only be answered by the logic within
  131. the assertion. For this example, we'll use the following assertion:
  132. </para>
  133. <programlisting language="php"><![CDATA[
  134. class OwnerCanPublishBlogPostAssertion implements Zend_Acl_Assert_Interface
  135. {
  136. /**
  137. * This assertion should receive the actual User and BlogPost objects.
  138. *
  139. * @param Zend_Acl $acl
  140. * @param Zend_Acl_Role_Interface $user
  141. * @param Zend_Acl_Resource_Interface $blogPost
  142. * @param $privilege
  143. * @return bool
  144. */
  145. public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user = null, Zend_Acl_Resource_Interface $blogPost = null, $privilege = null)
  146. {
  147. if (!$user instanceof Default_Model_User) {
  148. throw new Exception(__CLASS__ . '::' . __METHOD__ . ' expects the role to be an instance of User');
  149. }
  150. if (!$blogPost instanceof Default_Model_BlogPost) {
  151. throw new Exception(__CLASS__ . '::' . __METHOD__ . ' expects the resource to be an instance of BlogPost');
  152. }
  153. // if role is publisher, he can always modify a post
  154. if ($user->getRoleId() == 'publisher') {
  155. return true;
  156. }
  157. // check to ensure that everyone else is only modifying their own post
  158. if ($user->id != null && $blogPost->ownerUserId == $user->id) {
  159. return true;
  160. } else {
  161. return false;
  162. }
  163. }
  164. }
  165. ]]></programlisting>
  166. <para>
  167. To hook this into our ACL system, we would do the following:
  168. </para>
  169. <programlisting language="php"><![CDATA[
  170. // replace this:
  171. // $acl->allow('owner', 'blogPost', 'publish');
  172. // with this:
  173. $acl->allow('owner', 'blogPost', 'publish', new OwnerCanPublishBlogPostAssertion());
  174. // lets also add the role of a "publisher" who has access to everything
  175. $acl->allow('publisher', 'blogPost', 'publish');
  176. ]]></programlisting>
  177. <para>
  178. Now, anytime the ACL is consulted about whether or not an owner can publish a specific
  179. blog post, this assertion will be run. This assertion will ensure that unless the role
  180. type is 'publisher' the owner role must be logically tied to the blog post in question.
  181. In this example, we check to see that the ownerUserId property of the blog post matches
  182. the id of the owner passed in.
  183. </para>
  184. </sect2>
  185. </sect1>