multiuser-authorization.xml 9.5 KB

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