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