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 * <!-- AD Group (excluding group names that match with department) --> 236 * <init-param> 237 * <param-name>spnego.authz.ldap.filter.1</param-name> 238 * <param-value> 239 * <![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))))]]> 241 * </param-value> 242 * </init-param> 243 * <!-- Department --> 244 * <init-param> 245 * <param-name>spnego.authz.ldap.filter.2</param-name> 246 * <param-value> 247 * <![CDATA[(&(sAMAccountType=805306368)(sAMAccountName=%1$s)(&(sAMAccountType=805306368)(department=%2$s)))]]> 248 * </param-value> 249 * </init-param> 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}