View Javadoc
1   package com.randomnoun.common.security;
2   
3   /* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
4    * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
5    */
6   
7   import java.io.IOException;
8   import java.util.ArrayList;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.Map;
13  
14  import com.randomnoun.common.MRUCache;
15  
16  /**
17   * This class manages users, roles, resources and permissions for an application.
18   * 
19   * <p>Most of the code that adds/deletes/maintains these objects has been removed, that is
20   * now the responsibility of the security implementation code. 
21   * 
22   * <p>Methods to read user and permission data are delegated to the SecurityLoader, and
23   * methods that authenticate users are delegated to the SecurityAuthenticator.
24   * 
25   * <p>This class now mostly acts as a cache for user and role data, and can perform
26   * simple and complex permission checks for users against resources.
27   * 
28   * <p>The following properties can be passed to the SecurityContext during construction;
29   * property keys are defined as static public final Strings in this class.
30   *
31   * <ul>
32   * <li>INIT_CASE_INSENSITIVE - make the security cache case-insensitive, typically when interfacing with 
33   *     Active Directory. Defaults to false.
34   * <li>INIT_USER_CACHE_SIZE - maximum size of user cache
35   * <li>INIT_USER_CACHE_EXPIRY - expiry time of users from the user cache (in milliseconds). If this
36   *     property is not set, user caching is disabled.
37   * </ul>
38   *
39   * <p>Additional properties may also be required based on the SecurityLoader implementation used.
40   * 
41   * @author knoxg
42   *
43   * @see com.randomnoun.common.security.SecurityLoader
44   */
45  public class SecurityContext {
46      
47      // /** The logger for this class */
48      // private static Logger logger = Logger.getLogger(SecurityContext.class.getName());
49  
50      /** SecurityContext properties */
51      private Map<String, Object> properties = null;
52  
53      /** An initialisation property key. See the class documentation for details. */
54      public static final String INIT_USER_CACHE_SIZE = "securityContext.userCacheSize";
55  
56      /** An initialisation property key. See the class documentation for details. */
57      public static final String INIT_USER_CACHE_EXPIRY = "securityContext.userCacheExpiry";
58  
59      /** An initialisation property key. See the class documentation for details. */
60      public static final String INIT_USERNAME_MASK = "securityContext.usernameMask";
61      
62      /** An initialisation property key. See the class documentation for details. */
63  	public static final String INIT_CASE_INSENSITIVE = "securityContext.caseInsensitive";
64  
65      /** Maps rolenames to maps of permission names (in the form 'activity.resource')
66       * to Permission objects (possibly containing ResourceCriteria objects). 
67       * 
68  	 * If the security context is case-insensitive, then role names are lower-cased.
69       */
70      private Map<String, Map<String, Permission>> rolePermissionCache = null;
71  
72      /** Maps usernames to maps of permission names (in the form 'activity.resource')
73       * to Permission objects (possibly containing ResourceCriteria objects). 
74       *  
75       * If the security context is case-insensitive, then usernames are lower-cased. */
76      private Map<User, Map<String, Permission>> userPermissionCache = null;
77      
78      /** Maps user objects to list of roles. 
79       * 
80       * @TODO convert to HashSet ?
81       */
82      private Map<User, List<String>> userRoleCache = null;
83  
84      /** Maps userIds to Users. 
85       */
86      private Map<Long, User> userCache = null;
87      
88      /** This security loader is used to retrieve information from a persistant data
89       *  store for this context */
90      private SecurityLoader securityLoader = null;
91  
92      
93      /** The authenticator, if you want to actually check passwords */
94      private SecurityAuthenticator securityAuthenticator = null;
95  
96      // should probably use guava caches for all of this now
97      
98      /** This class is invoked by the MRUCache to recalculate values in the
99       * user permission cache that have expired, or where not in the cache to begin with.
100      */
101     private static class UserPermissionCallback
102         implements MRUCache.RetrievalCallback {
103 
104     	SecurityLoader loader;
105 
106         /** Creates a new callback used to populate the
107          *  user cache
108          */
109         public UserPermissionCallback(SecurityLoader loader) {
110             this.loader = loader;
111         }
112 
113         /** this method is only called if the required value is not in the cache,
114          * or the value in the cache has expired.
115          *
116          * @param key The username of the User to return
117          */
118         public Object get(Object key) {
119             if (key==null) { throw new NullPointerException("null key"); }
120             if (!(key instanceof User)) {
121             	throw new IllegalArgumentException("Expected user as User, found " + key.getClass().getName());
122             }
123 
124             User user = (User) key;
125 			
126 			Map<String, Permission> result = new HashMap<String, Permission>();
127             try {
128                 List<Permission> permissions = loader.loadUserPermissions(user);
129                 for (Iterator<Permission> i = permissions.iterator(); i.hasNext(); ) {
130                 	Permission perm = (Permission) i.next();
131                 	if (result.containsKey(perm.getActivity() + "." + perm.getResource())) {
132                 		throw new IllegalStateException("User '" + key + "' contains two versions of permission '" + 
133                 		  perm.getActivity() + "." + perm.getResource() + "'; please check the database");
134                 	}
135                 	result.put(perm.getActivity() + "." + perm.getResource(), perm);
136                 }
137             } catch (IOException ioe) {
138                 throw new RuntimeException("IOException reading user permissions", ioe);
139             }
140             return result;
141         }
142     }
143 
144 	/** This class is invoked by the MRUCache to recalculate values in the
145 	 * user permission cache that have expired, or where not in the cache to begin with.
146 	 */
147 	private static class RolePermissionCallback
148 		implements MRUCache.RetrievalCallback {
149 
150 		SecurityLoader loader;
151 
152 		/** Creates a new rowcount callback used to populate the
153 		 *  user cache
154 		 */
155 		public RolePermissionCallback(SecurityLoader loader) {
156 			this.loader = loader;
157 		}
158 
159 		/** this method is only called if the required value is not in the cache,
160 		 * or the value in the cache has expired.
161 		 *
162 		 * @param key The username of the User to return
163 		 */
164 		public Object get(Object key) {
165 			if (key==null) { throw new NullPointerException("null key"); }
166 			if (!(key instanceof String)) {
167 				throw new IllegalArgumentException("Expected roleName as string, found " + key.getClass().getName());
168 			}
169 			String rolename = (String) key;
170 			Map<String, Permission> result = new HashMap<String, Permission>();
171 			try {
172 				List<Permission> permissions = loader.loadRolePermissions(rolename);
173 				for (Iterator<Permission> i = permissions.iterator(); i.hasNext(); ) {
174 					Permission perm = (Permission) i.next();
175 					if (result.containsKey(perm.getActivity() + "." + perm.getResource())) {
176 						throw new IllegalStateException("Role '" + key + "' contains two versions of permission '" + 
177 						  perm.getActivity() + "." + perm.getResource() + "'; please check the database");
178 					}
179 					result.put(perm.getActivity() + "." + perm.getResource(), perm);
180 				}
181 			} catch (IOException ioe) {
182 				throw new RuntimeException("IOException reading role permissions", ioe);
183 			}
184 			return result;
185 		}
186 	}
187 
188 
189 	/** This class is invoked by the MRUCache to recalculate values in the
190 	 * user role cache that have expired, or where not in the cache to begin with.
191 	 */
192 	private static class UserRoleCallback
193 		implements MRUCache.RetrievalCallback {
194 
195 		SecurityLoader loader;
196 
197 		/** Creates a new rowcount callback used to populate the
198 		 *  user cache
199 		 */
200 		public UserRoleCallback(SecurityLoader loader) {
201 			this.loader = loader;
202 		}
203 
204 		/** this method is only called if the required value is not in the cache,
205 		 * or the value in the cache has expired.
206 		 *
207 		 * @param key The username of the User to return
208 		 */
209 		public Object get(Object key) {
210 			if (key==null) { throw new NullPointerException("null key"); }
211 			if (!(key instanceof User)) {
212 				throw new IllegalArgumentException("Expected user as User, found " + key.getClass().getName());
213 			}
214 			
215 			try {
216 				// @TODO convert to HashSet ?
217 				return loader.loadUserRoles((User) key);
218 			} catch (IOException ioe) {
219 				throw new RuntimeException("IOException reading user permissions", ioe);
220 			}
221 		}
222 	}
223 
224 
225 	/** This class is invoked by the MRUCache to load Users. It delegates to the Loader.
226 	 */
227 	private static class UserCallback
228 		implements MRUCache.RetrievalCallback {
229 
230 		SecurityLoader loader;
231 
232 		/** Creates a new rowcount callback used to populate the
233 		 *  user cache
234 		 */
235 		public UserCallback(SecurityLoader loader) {
236 			this.loader = loader;
237 		}
238 
239 		/** this method is only called if the required value is not in the cache,
240 		 * or the value in the cache has expired.
241 		 *
242 		 * @param key The username of the User to return
243 		 */
244 		public Object get(Object key) {
245 			if (key==null) { throw new NullPointerException("null key"); }
246 			if (!(key instanceof Number)) {
247 				throw new IllegalArgumentException("Expected numeric userId, found " + key.getClass().getName());
248 			}
249 			
250 			try {
251 				// @TODO convert to HashSet ?
252 				return loader.loadUser(((Number) key).longValue());
253 			} catch (IOException ioe) {
254 				throw new RuntimeException("IOException reading user", ioe);
255 			}
256 		}
257 	}
258 
259 
260     /**
261      * Creates a new SecurityContext object.
262      *
263      * @param properties Initialisation properties for this SecurityContext, its
264      *   SecurityLoader, and SecurityAuthenticator
265      *
266      * @throws IllegalStateException if the context is configured to preload,
267      *   and it fails to do so.
268      */
269     public SecurityContext(Map<String, Object> properties, SecurityLoader securityLoader, 
270     		SecurityAuthenticator securityAuthenticator) {
271         this.properties = properties;
272         this.securityLoader = securityLoader;
273         this.securityLoader.initialise(properties);
274         this.securityAuthenticator = securityAuthenticator;
275         this.securityAuthenticator.initialise(properties);
276 		resetSecurityContext();
277     }
278     
279 
280 	/** Retrieve a list of permissions for this user, as Permission objects.
281 	 * 
282 	 * @param user
283 	 * @return
284 	 * @throws IOException
285 	 */
286 	public List<Permission> getUserPermissions(User user) throws IOException {
287 		// @TODO should get this User out of our userCache, keyed by id
288 		
289 		List<Permission> permissions = new ArrayList<Permission>();
290 		Map<String, Permission> cachedPermissions = userPermissionCache.get(user);
291 		for (Iterator<Permission> i = cachedPermissions.values().iterator(); i.hasNext(); ) {
292 			permissions.add(i.next());
293 		}
294 		return permissions;
295 	}
296 
297 
298     /** Returns a list of Permission objects that apply to the specified rolename.
299      *
300      * @param roleName the role name
301      * @return A List of Permission objects that apply to that role
302      */
303     public List<Permission> getRolePermissions(String roleName) {
304         // retrieve from cache
305         List<Permission> result = new ArrayList<Permission>();
306         if (roleName==null) {
307         	throw new NullPointerException("null roleName");
308         } else {
309         	Map<String, Permission> rolePermissions = rolePermissionCache.get(roleName);
310         	if (rolePermissions == null) {
311         		throw new IllegalArgumentException("Unknown role '" + roleName + "'");
312         	}
313         	for (Iterator<Permission> i = rolePermissions.values().iterator(); i.hasNext(); ) {
314         		result.add(i.next());
315         	}
316         }
317         return result;
318     }
319 
320     /**
321      * Return a list of User objects representing all users contained in this
322      * security context. Permission information relating to that user is not
323      * populated unless the 'populatePermission' parameter is set to true.
324      *
325      * <p>The information returned by this function may be cached, depending
326      * on the initialisation properties of the security context.
327      *
328      * @return A List of Users.
329      */
330     public List<User> getAllUsers() throws IOException {
331         return securityLoader.loadAllUsers();
332     }
333 
334     /**
335      * Return a List of all resources in this security context, identified
336      * by String.
337      *
338      * <p>The information returned by this function may be cached, depending
339      * on the initialisation properties of the security context.
340      *
341      * @return A List of resources
342      */
343     public List<String> getAllResources() throws IOException {
344         return securityLoader.loadAllResources();
345     }
346 
347 	/**
348 	 * Return a List of all Permissions in this security context.
349 	 *
350 	 * <p>The information returned by this function may be cached, depending
351 	 * on the initialisation properties of the security context.
352 	 *
353 	 * @return A List of resources
354 	 */
355 	public List<Permission> getAllPermissions() throws IOException {
356 		return securityLoader.loadAllPermissions();
357 	}
358 
359 
360     /**
361      * Return a List of all activities in this security context for a given
362      * resource, identified by String.
363      *
364      * <p>The information returned by this function may be cached, depending
365      * on the initialisation properties of the security context.
366      *
367      * @param resourceName The resource we wish to retrieve activities for
368      *
369      * @return A List of activities.
370      *
371      * @throws SecurityException
372      */
373     public List<String> getAllActivities(String resourceName) throws IOException {
374         return securityLoader.loadAllActivities(resourceName);
375     }
376 
377     /**
378      * Return a List of roles in this security context for the User, identified
379      * by String.
380      *
381      * <p>The information returned by this function may be cached, depending
382      * on the initialisation properties of the security context.
383      *
384      * @return A List of roles.
385      */
386     public List<String> getAllRoles()
387         throws IOException {
388         return securityLoader.loadAllRoles();
389     }
390 
391     /**
392      * Return a List of all roles in this security context, identified
393      * by String.
394      *
395      * <p>The information returned by this function may be cached, depending
396      * on the initialisation properties of the security context.
397      *
398      * @return A List of roles.
399      */
400     public List<String> getUserRoles(User user)
401         throws IOException 
402     {
403         List<String> cachedRoles = userRoleCache.get(user);
404         if (cachedRoles==null) {
405         	throw new IllegalStateException("Unknown user '" + user + "'");
406         }
407         return cachedRoles;
408     }
409 
410 	/** 
411 	 * Returns a detailed list of roles from the security context. Each role
412 	 * is defined as a Map with the following keys:
413 	 * 
414 	 * <attributes>
415 	 * roleId - the numeric id for the role
416 	 * roleName - the name of the role for
417 	 * system - (Number) set to 1 if this role is read-only, 0 otherwise
418 	 * description - a description for the role
419 	 * </attributes>
420 	 * 
421 	 * @return a list of roles, as described above
422 	 * 
423 	 * @throws IOException
424 	 */
425     public List<Map<String, Object>> getAllRoleDetails()
426         throws IOException {
427         return securityLoader.loadAllRoleDetails();
428     }
429 
430 	/** 
431 	 * Returns a detailed list of users from the security context. Each user
432 	 * is defined as a Map with the following keys:
433 	 * 
434 	 * <attributes>
435 	 * userId - the login name for the user
436 	 * name - the full name of the user
437 	 * system - (Number) set to 1 if this role is read-only, 0 otherwise
438 	 * </attributes>
439 	 * 
440 	 * @return a list of users, as described above
441 	 * 
442 	 * @throws IOException
443 	 */
444     public List<Map<String, Object>> getAllUserDetails()
445         throws IOException {
446         return securityLoader.loadAllUserDetails();
447     }
448 
449   
450 
451 
452     /** Returns true if a user is allowed to perform the permission supplied. The permission
453      *  is expressed in 'activity.resourceType' format, e.g. 'update.message'. No expression
454      *  context is supplied; this method will not evaluate any conditional resource
455      *  restrictions. This is useful in cases where the full resource context is not known,
456      *  for example when a message is first created by a user.
457      *
458      *  <p>In this case, the 'create.message' permission can be checked using this method
459      *  before the user starts entering information, and 'create.message' can be
460      *  checked with an expression context after the header fields have been populated.
461      *
462      * <p>If a permission is supplied that is not known by the application, this
463      * method will return false.
464      *
465      * @param user The user we are determining
466      * @param permission The permission we are testing for. Permissions are expressed in
467      *   'activity.resourceType' format.
468      * @return true if the permission is allowed, false is the permission is denied.
469      *
470      * @throws NullPointerException if either parameter to this method is null
471      * @throws IllegalArgumentException if the permission supplied is formatted incorrectly.
472      */
473     public boolean hasPermission(User user, String permission) {
474         return hasPermission(user, permission, null);
475     }
476 
477     /**
478      * Returns true if a user is allowed to perform the permission supplied, with
479      * given resource context. If a permission is assigned to both the user
480      * and the role, then the user permission is evaluated first.
481      *
482      * @param user The user we are determining
483      * @param permission The permission we are testing for. Permissions are expressed in
484      *   'activity.resourceType' format.
485      * @param context The resource context used to evaluate against the resource expression
486      *
487      * @return true if the permission is allowed, false is the permission is denied.
488      *
489      * @throws NullPointerException if either parameter to this method is null
490      * @throws IllegalArgumentException if the permission supplied is formatted incorrectly.
491      */
492     public boolean hasPermission(User user, String permission, Map<String, Object> context) {
493     	// @TODO should get this User out of our userCache, keyed by id
494         if (permission == null) { throw new NullPointerException("Null permission"); }
495         if (user == null) { throw new NullPointerException("Null user"); }
496 
497         int pos = permission.indexOf('.');
498         if (pos == -1) {
499             throw new IllegalArgumentException("Illegal permission value '" + permission + "'");
500         }
501 
502 		//  try per-user permissions...
503 		Map<String, Permission> userPermissions = userPermissionCache.get(user);
504 		if (userPermissions == null) {
505 			// @TODO - load from DB ? should be handled by MRUCache
506 			throw new IllegalStateException("Unknown user '" + user.getUsername() + "'");
507 		} else {
508 			Permission userPermission = (Permission) userPermissions.get(permission);
509 			if (userPermission!=null) {
510 				if (context == null) {
511 					return true;
512 				} else {
513 					ResourceCriteria criteria = userPermission.getResourceCriteria();
514 					if (criteria == null || criteria.evaluate(context)) {
515 						return true;
516 					}
517 				}
518 			}
519 		}
520 		
521 		// then try per-role permissions ...
522 		List<String> roles = userRoleCache.get(user);
523 		if (roles == null) {
524 			// @TODO - load from DB ? should be handled by MRUCache
525 			throw new IllegalStateException("Unknown user '" + user.getUsername() + "'");
526 		} else {
527 			for (Iterator<String> i = roles.iterator(); i.hasNext(); ) {
528 				String rolename = (String) i.next();
529 				Map<String, Permission> rolePermissions = rolePermissionCache.get(rolename);
530 				if (rolePermissions == null) {
531 					// @TODO - load from DB ? should be handled by MRUCache
532 					throw new IllegalStateException("Unknown role '" + rolename + "'");
533 				} else {
534 					// this is a strange data structure
535 					Permission rolePermission = (Permission) rolePermissions.get(permission);
536 					if (rolePermission!=null) {
537 						if (context == null) {
538 							return true;
539 						} else {
540 							ResourceCriteria criteria = rolePermission.getResourceCriteria();
541 							if (criteria == null || criteria.evaluate(context)) {
542 								return true;
543 							}
544 						}	
545 					}
546 				}
547 			}
548 		}
549 
550         return false;
551     }
552 
553 	/**
554 	 * Returns the Permission object for a specific user/permission combination, or null
555 	 * if this permission is not granted. This method will not search the user's 
556 	 * role-based permissions. 
557 	 *
558 	 * @param user The user we are determining
559 	 * @param permission The permission we are testing for. Permissions are expressed in
560 	 *   'activity.resourceType' format.
561 	 *
562 	 * @return a permission object. 
563 	 *
564 	 * @throws NullPointerException if either parameter to this method is null
565 	 * @throws IllegalArgumentException if the permission supplied is formatted incorrectly.
566 	 */
567 	public Permission getPermission(User user, String permission) {
568 		// @TODO should get this User out of our userCache, keyed by id
569 		if (permission == null) { throw new NullPointerException("Null permission"); }
570 		if (user == null) { throw new NullPointerException("Null user"); }
571 
572 		int pos = permission.indexOf('.');
573 		if (pos == -1) {
574 			throw new IllegalArgumentException("Illegal permission value '" + permission + "'");
575 		}
576 
577 		//  try per-user permissions...
578 		Map<String, Permission> userPermissions = userPermissionCache.get(user);
579 		if (userPermissions == null) {
580 			// @TODO - load from DB ? should be handled by MRUCache
581 			throw new IllegalStateException("Unknown user '" + user.getUsername() + "'");
582 		} else {
583 			Permission userPermission = (Permission) userPermissions.get(permission);
584 			if (userPermission != null) {
585 				return userPermission;
586 			}
587 		}
588 
589 		return null;
590 	}
591     
592     
593     /**
594      * Returns a list of all Permission objects assigned to a user and all the 
595      * roles that the user is a member of. This allows multiple permission conditions
596      * to be applied to a user, one for each role.
597      *
598      * @param user The user we are determining
599      * @param permission The permission we are testing for. Permissions are expressed in
600      *   'activity.resourceType' format.
601      *
602      * @return a List of Permission objects, or an empty list if the user 
603      *   (and none of their roles) contains this permission
604      *
605      * @throws NullPointerException if either parameter to this method is null
606      * @throws IllegalArgumentException if the permission supplied is formatted incorrectly.
607      */
608     public List<Permission> getPermissions(User user, String permission) {
609     	// @TODO should get this User out of our userCache, keyed by id
610         if (permission == null) { throw new NullPointerException("Null permission"); }
611         if (user == null) { throw new NullPointerException("Null user"); }
612         List<Permission> result = new ArrayList<Permission>();
613 
614         int pos = permission.indexOf('.');
615         if (pos == -1) {
616             throw new IllegalArgumentException("Illegal permission value '" + permission + "'");
617         }
618 
619 		//  try per-user permissions...
620 		Map<String, Permission> userPermissions = userPermissionCache.get(user);
621 		if (userPermissions == null) {
622 			// @TODO - load from DB ? should be handled by MRUCache
623 			throw new IllegalStateException("Unknown user '" + user.getUsername() + "'");
624 		} else {
625 			Permission userPermission = (Permission) userPermissions.get(permission);
626 			if (userPermission != null) {
627 				result.add(userPermission);
628 			}
629 		}
630 		
631 		// then try per-role permissions ...
632 		List<String> roles = userRoleCache.get(user);
633 		if (roles == null) {
634 			throw new IllegalStateException("Unknown user '" + user.getUsername() + "'");
635 		} else {
636 			for (Iterator<String> i = roles.iterator(); i.hasNext(); ) {
637 				String rolename = (String) i.next();
638 				Map<String, Permission> rolePermissions = rolePermissionCache.get(rolename);
639 				if (rolePermissions == null) {
640 					throw new IllegalStateException("Unknown role '" + rolename + "'");
641 				} else {
642 					Permission rolePermission = (Permission) rolePermissions.get(permission);
643 					if (rolePermission!=null) {
644 						result.add(rolePermission);
645 					}
646 				}
647 			}
648 		}
649 		return result;
650     }
651     
652 
653 
654     /** Returns a string representation of this security context.
655      *
656      * @return a string representation of this security context.
657      */
658     public String toString() {
659         return super.toString() + (rolePermissionCache == null ? " - uninitialised" : ": " + rolePermissionCache.toString());
660     }
661 
662     /** Clear all caches and re-initialises this security context (as defined 
663      * in this instance's initial initialisation properties). 
664      * This method also resets this security context's loader.
665      */
666     @SuppressWarnings("unchecked")
667 	public void resetSecurityContext() {
668         // logger.debug("Security context properties: " + properties.toString());
669     	
670 		int cacheSize = Integer.MAX_VALUE;
671 		int cacheExpiry = Integer.MAX_VALUE;
672         if (properties.get(INIT_USER_CACHE_SIZE) != null && properties.get(INIT_USER_CACHE_EXPIRY) != null) {
673             cacheSize = Integer.parseInt((String) properties.get(INIT_USER_CACHE_SIZE));
674             cacheExpiry = Integer.parseInt((String) properties.get(INIT_USER_CACHE_EXPIRY));
675         }
676 
677         UserPermissionCallback userPermissionCallback = new UserPermissionCallback(this.securityLoader);
678         userPermissionCache = new MRUCache(cacheSize, cacheExpiry, userPermissionCallback );
679 
680 		UserRoleCallback userRoleCallback = new UserRoleCallback (this.securityLoader);
681 		userRoleCache = new MRUCache(cacheSize, cacheExpiry, userRoleCallback );
682 
683 		RolePermissionCallback rolePermissionCallback = new RolePermissionCallback (this.securityLoader);
684 		rolePermissionCache = new MRUCache(cacheSize, cacheExpiry, rolePermissionCallback );
685 
686 		UserCallback userCallback = new UserCallback (this.securityLoader);
687 		userCache = new MRUCache(cacheSize, cacheExpiry, userCallback );
688 
689 		try {
690 			securityLoader.resetSecurityContext();	
691 		} catch (IOException ioe) {
692 			throw (IllegalArgumentException) new IllegalArgumentException(
693 			  "Cannot initialise security Context").initCause(ioe);
694 		}
695     }
696 
697     /**
698      * Authenticate the supplied username and password with the authentication provider.
699      * Returns true if the username/password combination is valid, false otherwise
700      *
701      * <p>Some authentication providers may require more complex handshakes (e.g. TFA authentication)
702      * which are currently suported by setting flags in a subclassed User object. 
703      * Possible mangling the password parameter as well. See the securityAuthenticator
704      * documentation for details.
705      *
706      * <p>The User object passed to this method may not have a valid userId assigned to it (this 
707      * may be set by the authentication provider).
708      *
709      * @param user user 
710      * @param password password
711      *
712      * @return true if the username/password combination is valid, false otherwise
713      *
714      * @throws IOException an exception occurred accessing the authentication provider.
715      */
716     public boolean authenticate(User user, String password)
717         throws IOException {
718     	// @TODO should get this User out of our userCache, keyed by id
719         return securityAuthenticator.authenticate(user, password);
720     }
721 
722     /** Returns a User, given their userId
723      * 
724      * <p>This method will not load role or permissions data for the user. 
725      * 
726      * @param userId
727      * @return
728      */
729     public User getUser(long userId) {
730     	// this cache has different User objects than the User objects in the role/user permission caches, 
731 		User user = (User) userCache.get(userId);
732 		return user;
733     }
734 
735     
736 	// why are these methods public ?
737     public List<Permission> loadRolePermissions(String role)
738         throws IOException {
739         return securityLoader.loadRolePermissions(role);
740     }
741 
742     public List<Permission> loadUserRolePermissions(User user)
743         throws IOException {
744         return securityLoader.loadUserRolePermissions(user);
745     }
746 
747 
748 }