001/** 
002 * Copyright (C) 2009 "Darwin V. Felix" <darwinfelix@users.sourceforge.net>
003 * 
004 * This library is free software; you can redistribute it and/or
005 * modify it under the terms of the GNU Lesser General Public
006 * License as published by the Free Software Foundation; either
007 * version 2.1 of the License, or (at your option) any later version.
008 * 
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
012 * Lesser General Public License for more details.
013 * 
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this library; if not, write to the Free Software
016 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017 */
018
019package net.sourceforge.spnego;
020
021import java.util.Properties;
022
023/**
024 * An interface for specifying how a program/library interacts with an  
025 * implementing object when performing user authorization (authZ). This 
026 * interface allows for a much more limited and simpler design than the   
027 * <a href="https://en.wikipedia.org/wiki/Role-based_access_control">Role 
028 * Based Access Control</a> (RBAC) model and the  
029 * <a href="https://en.wikipedia.org/wiki/Attribute-Based_Access_Control">Attribute 
030 * Based Access Control</a> (ABAC) model. 
031 * 
032 * <p>
033 * This interface does not specify how an implementing object obtains 
034 * user information or from where the user information is stored. An 
035 * implementing object is free to aggregate information from multiple 
036 * sources (ie. LDAP Server, RDBMS, etc.). Data retrieval such as 
037 * roles, groups, attributes, resources, user information etc. is 
038 * defined by the implementing object. Since it is assumed that the 
039 * given store is a shared resource, the implementing object must take 
040 * care of synchronizing access to any shared resource. This interface 
041 * does not define, specify or suggest synchronization semantics. 
042 * </p>
043 * 
044 * <p>
045 * This interface does not specify a new or different Access Control model 
046 * and instead follows the RBAC and ABAC style for access control.  
047 * However, one meaningful difference is that instead of determining 
048 * resource permissions/access based on roles, the lookup is 
049 * based on attributes of the user. Usage semantics will be in the 
050 * same style as RBAC but policies will be in the style of ABAC.  
051 * </p>
052 * 
053 * <p>
054 * The SPNEGO Library provides at least one concrete reference implementation 
055 * of the <code>UserAccessControl</code> interface. The {@link LdapAccessControl} 
056 * class implements this interface. The <code>LdapAccessControl</code> 
057 * class connects to an LDAP server for the purpose of obtaining user attribute/
058 * role/group/etc. information. For additional examples about attribute/role semantics, 
059 * please see the javadoc for the {@link SpnegoAccessControl} interface.
060 * </p>
061 * 
062 * <p>
063 * In the following examples, the source of attribute information is defined to be 
064 * in an LDAP/Active Directory Group, User Department, Email Distribution List, etc. 
065 * A policy is configured to search one of these attribute sets or optionally to 
066 * search all of these attribute sets. The attribute sets can be mixed to allow 
067 * for a more expressive policy statement. e.g. 1) A user has access if they are 
068 * in <i>this</i> AD Group and belong in one of <i>these</i> departments, or 
069 * 2) a user has access if they are in <i>this</i> email distribution list 
070 * and in one of <i>these</i> AD Groups or is in one <i>these</i> departments, 
071 * or 3) a user can see <i>the edit button</i> if they are in <i>this</i> 
072 * AD Group and in one of <i>these</i> other AD Groups.
073 * </p>
074 * 
075 * <p>
076 * <b>Example Usage 1:</b><br />
077 * A new ticketing application where every user of the application has the same 
078 * access rights and the only requirement is that they have logged-in to their 
079 * workstation/computer. Under this scenario, every user must first authenticate (authN) 
080 * but since every authN user has the same access rights to the ticketing application, 
081 * authZ may have little to no value. Hence, there would be no need to implement 
082 * or enable authZ for this application.
083 * </p>
084 * 
085 * <p>
086 * <b>Example Usage 2:</b><br />
087 * A new feature will be introduced to the ticketing application. The new feature 
088 * should only be accessible by users who are in the LDAP Group/Active Directory Group 
089 * BizDev Mngrs., Client Services, or Acct. Mngrs. Since authN is assumed to have already 
090 * taken place, the developer of the new feature can protect access to the new feature 
091 * by invoking one of the methods in the <code>UserAccessControl</code> interface.
092 * 
093 * <pre>
094 * UserAccessControl accessControl = ...;
095 * boolean allowPrivAccess = false;
096 * 
097 * String[] attributes = new String[] {"BizDev Mngrs.", "Client Services", "Acct. Mngrs."};
098 * 
099 * if (accessControl.anyRole("dfelix", attributes)) {
100 *     allowPrivAccess = true;
101 * }
102 * </pre>
103 * 
104 * In the above example, if the user dfelix has at least one of those attributes (in this 
105 * example, each attribute is an AD group), 
106 * then the code inside the if block will execute. The <code>anyRole</code> method 
107 * will return true if it finds at least one matching attribute.
108 * </p>
109 * 
110 * <p>
111 * <b>Example Usage 3:</b><br />
112 * A second new feature will be introduced but this time only certain departments within the 
113 * Information Technology (IT) division should have access to this latest feature. 
114 * The IT division has many departments (e.g. Helpdesk/Desktop Support, Networking Team, 
115 * Database Admins,  Business Analysts, Software Developers, etc.). However, only Business 
116 * Analysts and Software Developers within IT should have access to this latest feature.
117 * 
118 * <pre>
119 * UserAccessControl accessControl = ...;
120 * boolean editAndAdminBtns = false;
121 * 
122 * String attributeX = "IT Group";
123 * String[] attributeYs = new String[] {"Biz. Analyst", "Developer"};
124 * 
125 * if (accessControl.hasRole("dfelix", attributeX, attributeYs)) {
126 *     editAndAdminBtns = true;
127 * }
128 * </pre>
129 * 
130 * In the above example, if dfelix has the attribute IT Group and has either 
131 * Biz. Analyst or Developer, then the code inside the if block will execute. 
132 * The <code>hasRole</code> method will return true if it can match the first 
133 * attribute AND at least one of the remaining attributes.
134 * </p>
135 * 
136 * <p>
137 * The <code>UserAccessControl</code> interface treats the traditional notion of a 
138 * role as a higher level abstraction that includes any and all attributes that can 
139 * identify a user or a set of users. For example, a job title named "Team Lead", a  
140 * department named "Human Resources", an Active Directory group named 
141 * "Offsite Users - NY", an email distribution list named "Performance Team", etc., 
142 * are abstractly defined as attributes.
143 * </p>
144 * 
145 * <p>
146 * Each attribute concretely belongs to an attribute set. The definition of an attribute set 
147 * is somewhat arbitrary but it is not abstract. i.e. job titles, departments, email distribution 
148 * lists, active directory groups, user location, etc. are attribute sets. An attribute set is 
149 * commonly and sometimes indirectly specified in a user store (database/ldap/active directory/etc.) 
150 * as user information properties, system roles/groups, company distribution lists/groups, etc.
151 * </p>
152 * 
153 * <p>
154 * In the simplest examples, an attribute from one attribute set and another attribute from 
155 * another attribute set may be combined to form a policy statement. In the example above 
156 * (Example Usage 3), edit/admin buttons will be enabled only if the user dfelix has the 
157 * attribute IT Group (from the division attribute set) AND a second attribute of either 
158 * Biz. Analyst or Developer (from the departments attribute set).
159 * </p>
160 * 
161 * <p>
162 * <b>Example Usage 4:</b></br />
163 * The ticketing system is nearing maturity and it has been determined that only 
164 * Biz. Analysts from the IT Group (i.e. policy statement A) or 
165 * any Biz. Analyst out of the Los Angeles office (i.e. policy statement B) 
166 * should have access to the edit/admin buttons. 
167 * Biz. Analysts from the other locations should not have the edit/admin buttons enabled.
168 * 
169 * <pre>
170 * UserAccessControl accessControl = ...;
171 * boolean editAndAdminBtns = false;
172 * 
173 * if (accessControl.hasRole("dfelix", "IT Group", "Biz. Analyst")
174 *         || accessControl.hasRole("dfelix", "Los Angeles", "Biz. Analyst")) {
175 *     editAndAdminBtns = true;
176 * }
177 * </pre>
178 * </p>
179 * 
180 * <p>
181 * In the above example, two policy statements were defined - policy A and policy B. 
182 * Policy statement A uses attributes from the attribute set division and an attribute 
183 * from the attribute set department. 
184 * Policy statement B uses attributes from the attribute set location and an attribute 
185 * from the attribute set department.
186 * Given the arbitrary nature of attributes, attribute sets and how they are combined 
187 * to form a policy statement, an implementing class of the <code>UserAccessControl</code> 
188 * interface MUST allow the above usage semantics. For an inspiration and/or example of how 
189 * this can be achieved, please see the {@link LdapAccessControl} source code. 
190 * </p>
191 * 
192 * <p>
193 * Note that policy statement A and policy statement B can be re-defined into 
194 * one policy statement, policy statement C. 
195 * 
196 * <pre>
197 * UserAccessControl accessControl = ...;
198 * boolean editAndAdminBtns = false;
199 * 
200 * String attributeX = "Biz. Analyst";
201 * String[] attributeYs = new String[] {"Los Angeles", "IT Group"};
202 * 
203 * if (accessControl.hasRole("dfelix", attributeX, attributeYs)) {
204 *     editAndAdminBtns = true;
205 * }
206 * </pre>
207 * 
208 * An implementation of this interface MUST support the usage semantics for policy A, 
209 * policy B, and policy C. Classes that implement the <code>UserAccessControl</code> 
210 * interface MUST allow symmetry in the usage semantics. By way of example, if a 
211 * developer calls <code>accessControl.hasRole("dfelix", "Biz. Analyst", "IT Group")</code> 
212 * in one part/section of the program/code and then calls  
213 * <code>accessControl.hasRole("dfelix", "IT Group", "Biz. Analyst")</code>  
214 * in another part/section of the program, BOTH calls MUST return true 
215 * (either both returns true or both returns false). For an inspiration and/or example 
216 * of how this can be achieved, please see the {@link LdapAccessControl} source code. 
217 * </p>
218 * 
219 * <p>
220 * It is encouraged that the attribute identifier, the value passed-in to the method(s) of 
221 * this interface, be unique across the entire set of attributes and attribute sets. For 
222 * example, if an email distribution list (an attribute set) is named "Performance Team" 
223 * (an attribute) and there exists a department (an attribute set) also named "Performance Team"
224 * (an attribute), one of the attribute sets must be excluded from the policy lookup definition 
225 * or one of the attributes must be renamed (e.g. "Perf. Team") or one of the attributes sets 
226 * can not be a search filter so that the uniqueness property is maintained. 
227 * Another alternative is to modify the policy statement(s) such that the uniqueness 
228 * property is still maintained. Finally, the uniqueness property is enabled by default 
229 * but can be disabled via a configuration parameter. 
230 * </p>
231 * 
232 * <p>
233 * <b>Uniqueness Example (with LdapAccessControl):</b>
234 * <pre>
235 * &lt;!-- AD Group (excluding group names that match with department) --&gt;
236 * &lt;init-param&gt;
237 *     &lt;param-name&gt;spnego.authz.ldap.filter.1&lt;/param-name&gt;
238 *      &lt;param-value&gt;
239 *      &lt;![CDATA[(&(sAMAccountName=%1$s)
240 *      (memberOf:1.2.840.113556.1.4.1941:=CN=%2$s,OU=Groups,OU=Los Angeles,DC=athena,DC=local)(!(&(sAMAccountType=805306368)(department=%2$s))))]]&gt;
241 *      &lt;/param-value&gt;
242 * &lt;/init-param&gt;
243 * &lt;!-- Department --&gt;
244 * &lt;init-param&gt;
245 *     &lt;param-name&gt;spnego.authz.ldap.filter.2&lt;/param-name&gt;
246 *     &lt;param-value&gt;
247 *     &lt;![CDATA[(&(sAMAccountType=805306368)(sAMAccountName=%1$s)(&(sAMAccountType=805306368)(department=%2$s)))]]&gt;
248 *     &lt;/param-value&gt;
249 * &lt;/init-param&gt;
250 * <i>filter must all be on one line. wrapped here for compactness.</i><br/>
251 * </pre>
252 * 
253 * In the above example, the filter one (1) will find the AD group provided that a department 
254 * is not named the same. The consequence to this alternative approach is that AD Groups 
255 * with the same name as a department name will no longer be available as an option.
256 * </p>
257 * 
258 * <p>
259 * Classes that implement the <code>UserAccessControl</code> interface MUST throw an 
260 * exception if it finds that the uniqueness property has been broken (provided that 
261 * the uniqueness property has not been disabled). For example, 
262 * if some policy statement D, specifies that a user MUST be in the IT Group AND that the 
263 * user must be in the Performance Team email distribution list; however the attribute 
264 * identifier Performance Team is defined in two attribute sets 
265 * (division and email distribution list), implementations of this interface 
266 * MUST throw an exception upon executing policy statement D.
267 * 
268 * <pre>
269 * UserAccessControl accessControl = ...;
270 * boolean editAndAdminBtns = false;
271 * 
272 * try {
273 *     if (accessControl.hasRole("dfelix", "IT Group", "Performance Team")) {
274 *         // will never get in here since an exception will be thrown
275 *         editAndAdminBtns = true;
276 *     }
277 * } catch (MyRuntimeExceptions mre) {
278 *     System.err.println("Uniqueness property broken. The attribute Performance Team " 
279 *         + "was found in at least two attribute sets. The division attribute set "
280 *         + "AND in the email distribution list attribute set. "
281 *         + "uniqueness property=enabled");
282 *     System.exit(-1);
283 * }
284 * </pre>
285 * </p>
286 * 
287 * <p>
288 * In the above example, if a policy was defined to search the  division attribute set 
289 * and the email distribution list attribute set, AND it finds the attribute 
290 * "Performance Team" in both attribute sets, an exception MUST be thrown.
291 * </p>
292 * 
293 * <p>
294 * For an inspiration and/or example of how this can be achieved, please see the 
295 * {@link LdapAccessControl} source code.
296 * </p>
297 * 
298 * <p>
299 * <b>User-defined Resource Label Example:</b>
300 * </p>
301 * 
302 * <p>
303 * An alternative to specifying department names, groups, email distribution lists, etc. 
304 * is to use a resource label. Resource labels are optional and hence must undergo additional  
305 * configuration before use. The <code>anyAccess</code> and the <code>hasAccess</code> 
306 * methods are used as an alternative to the <code>anyRole</code> method and the 
307 * <code>hasRole</code> methods.  
308 *
309 * <pre>
310 * UserAccessControl accessControl = ...;
311 * boolean editAndAdminBtns = false;
312 * 
313 * if (accessControl.hasAccess("dfelix", "admin-buttons")) {
314 *     editAndAdminBtns = true;
315 * }
316 * </pre>
317 * </p>
318 * 
319 * <p>
320 * In the above example, the attribute(s) that support the policy is abstracted by the 
321 * user-defined resource label named admin-buttons. Concretely, the resource label 
322 * admin-buttons could have been assigned the attributes IT Group, Biz. Analyst, and Developer.
323 * </p>
324 * 
325 * <p>
326 * To see how a web application/service can leverage user access controls, as well as see 
327 * additional usage examples, please take a look at the javadoc for the 
328 * {@link SpnegoAccessControl} interface.
329 * </p>
330 * 
331 * <p>
332 * Also, take a look at the <a href="http://spnego.sourceforge.net/reference_docs.html" 
333 * target="_blank">reference docs</a> for a complete list of configuration parameters.
334 * </p>
335 * 
336 * <p>
337 * To see a working example and instructions, take a look at the 
338 * <a href="http://spnego.sourceforge.net/user_access_control.html" 
339 * target="_blank">authZ for standalone apps</a> example. 
340 * </p>
341 * 
342 * <p>
343 * Enabling authZ for servlet containers (Tomcat, JBoss, etc.) can be found in the 
344 * <a href="http://spnego.sourceforge.net/enable_authZ_ldap.html" 
345 * target="_blank">enable authZ with LDAP</a> guide. 
346 * </p>
347 * 
348 * 
349 * @author Darwin V. Felix
350 *
351 */
352public interface UserAccessControl {
353    
354    /**
355     * Used for clean-up when usage of the object is no longer needed and no other 
356     * method calls will be made on this instance. 
357     * 
358     * <p>
359     * Calling this method is an indication that no other method calls will be called 
360     * on this instance. If method calls must resume on this instance, the init method 
361     * MUST be called before this instance can be placed back into service.
362     * </p>
363     * 
364     * <p>
365     * If this method has been called and a reference to the instance is 
366     * maintained, the init method must be called again to re-initialize the 
367     * object's instance variables.
368     * </p>
369     */
370    void destroy();
371    
372    /**
373     * Checks to see if the given user has at least one of the passed-in attributes.
374     * 
375     * <pre>
376     * String[] attributes = new String[] {"Developer", "Los Angeles", "Manager"};
377     * 
378     * if (accessControl.anyRole("dfelix", attributes)) {
379     *     // will be in here if dfelix has at least one matching attribute
380     * }
381     * </pre>
382     * 
383     * @param username e.g. dfelix
384     * @param attribute e.g. Team Lead, IT, Developer
385     * @return true if the user has at least one of the passed-in roles/features
386     */
387    boolean anyRole(final String username, final String... attribute);
388    
389    /**
390     * Checks to see if the given user has the passed-in attribute.
391     * 
392     * <pre>
393     * String attribute = "Los Angeles";
394     * 
395     * if (accessControl.hasRole("dfelix", attribute)) {
396     *     // will be in here if dfelix has that one attribute
397     * }
398     * </pre>
399     * 
400     * @param username e.g. dfelix
401     * @param attribute e.g. IT
402     * @return true if the user has the passed-in role/attribute
403     */
404    boolean hasRole(final String username, final String attribute);
405
406    /**
407     * Checks to see if the given user has the first attribute   
408     * AND has at least one of the passed-in attributes.
409     * 
410     * <pre>
411     * String attributeX = "Los Angeles";
412     * String[] attributeYs = new String[] {"Developer", "Manager"};
413     * 
414     * if (accessControl.hasRole("dfelix", attributeX, attributeYs)) {
415     *     // will be in here if dfelix has attributeX 
416     *     // AND has at least one of the attributeYs.
417     * }
418     * </pre>
419     * 
420     * @param username e.g. dfelix
421     * @param attributeX e.g. Information Technology
422     * @param attributeYs e.g. Team Lead, IT-Architecture-DL
423     * @return true if user has featureX AND at least one the featureYs
424     */
425    boolean hasRole(final String username
426        , final String attributeX, final String... attributeYs);
427    
428    /**
429     * Checks to see if the given user has at least one of the passed-in 
430     * user-defined resource labels.
431     * 
432     * <pre>
433     * if (accessControl.anyAccess("dfelix", "admin-links", "buttons-for-ops")) {
434     *     // will be in here if dfelix has at least one matching resource
435     * }
436     * </pre>
437     * 
438     * @param username e.g. dfelix
439     * @param resources e.g. admin-links, ops-buttons
440     * @return true if the user has at least one of the passed-in user-defined resource labels
441     */
442    boolean anyAccess(final String username, final String... resources);
443    
444    /**
445     * Checks to see if the passed-in user has access to the 
446     * user-defined resource label.
447     * 
448     * <pre>
449     * UserAccessControl accessControl = ...;
450     * boolean editAndAdminBtns = false;
451     * 
452     * if (accessControl.hasAccess("dfelix", "admin-buttons")) {
453     *     editAndAdminBtns = true;
454     * }
455     * </pre>
456     * 
457     * @param username e.g. dfelix
458     * @param resource e.g. admin-buttons
459     * @return true if user has access to the user-defined resource labels
460     */
461    boolean hasAccess(final String username, final String resource);
462    
463    /**
464     * Checks to see if the given user has the first resource label 
465     * AND has at least one of the passed-in resource labels.
466     * 
467     * <pre>
468     * String resourceX = "phone-list";
469     * String[] resourceYs = new String[] {"staff-directory", "procedure-manual"};
470     * 
471     * if (accessControl.hasAccess("dfelix", resourceX, resourceYs)) {
472     *     // will be in here if dfelix has resourceX 
473     *     // AND has at least one of the resourceYs.
474     * }
475     * </pre>
476     * 
477     * @param username e.g. dfelix
478     * @param resourceX e.g. phone-list
479     * @param resourceYs e.g. staff-directory, procedure-manual, emergency-contact-list
480     * @return true if user has resourceX AND at least one the resourceYs
481     */
482    boolean hasAccess(final String username, final String resourceX, final String... resourceYs);
483    
484    /**
485     * Returns the user's info object for the given user.
486     * 
487     * @return the user's info object for the given user
488     */
489    UserInfo getUserInfo(final String username);
490
491    /**
492     * Method is used for initialization prior to use/calling any other method. 
493     * 
494     * <p>
495     * Calling this method is an indication that this instance is in service/active
496     * and any method can be called at anytime for the purpose of servicing a request.
497     * </p>
498     * 
499     * <p>
500     * If this method has been called and a reference to the instance is 
501     * maintained, this method should not be called again unless the destroy 
502     * method is called first.
503     * </p>
504     */
505    void init(final Properties props); 
506}