001package com.randomnoun.common.security;
002
003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
005 */
006
007import java.io.IOException;
008import java.util.ArrayList;
009import java.util.HashMap;
010import java.util.Iterator;
011import java.util.List;
012import java.util.Map;
013
014import com.randomnoun.common.MRUCache;
015
016/**
017 * This class manages users, roles, resources and permissions for an application.
018 * 
019 * <p>Most of the code that adds/deletes/maintains these objects has been removed, that is
020 * now the responsibility of the security implementation code. 
021 * 
022 * <p>Methods to read user and permission data are delegated to the SecurityLoader, and
023 * methods that authenticate users are delegated to the SecurityAuthenticator.
024 * 
025 * <p>This class now mostly acts as a cache for user and role data, and can perform
026 * simple and complex permission checks for users against resources.
027 * 
028 * <p>The following properties can be passed to the SecurityContext during construction;
029 * property keys are defined as static public final Strings in this class.
030 *
031 * <ul>
032 * <li>INIT_CASE_INSENSITIVE - make the security cache case-insensitive, typically when interfacing with 
033 *     Active Directory. Defaults to false.
034 * <li>INIT_USER_CACHE_SIZE - maximum size of user cache
035 * <li>INIT_USER_CACHE_EXPIRY - expiry time of users from the user cache (in milliseconds). If this
036 *     property is not set, user caching is disabled.
037 * </ul>
038 *
039 * <p>Additional properties may also be required based on the SecurityLoader implementation used.
040 * 
041 * @author knoxg
042 *
043 * @see com.randomnoun.common.security.SecurityLoader
044 */
045public class SecurityContext {
046    
047    // /** The logger for this class */
048    // private static Logger logger = Logger.getLogger(SecurityContext.class.getName());
049
050    /** SecurityContext properties */
051    private Map<String, Object> properties = null;
052
053    /** An initialisation property key. See the class documentation for details. */
054    public static final String INIT_USER_CACHE_SIZE = "securityContext.userCacheSize";
055
056    /** An initialisation property key. See the class documentation for details. */
057    public static final String INIT_USER_CACHE_EXPIRY = "securityContext.userCacheExpiry";
058
059    /** An initialisation property key. See the class documentation for details. */
060    public static final String INIT_USERNAME_MASK = "securityContext.usernameMask";
061    
062    /** An initialisation property key. See the class documentation for details. */
063        public static final String INIT_CASE_INSENSITIVE = "securityContext.caseInsensitive";
064
065    /** Maps rolenames to maps of permission names (in the form 'activity.resource')
066     * to Permission objects (possibly containing ResourceCriteria objects). 
067     * 
068         * If the security context is case-insensitive, then role names are lower-cased.
069     */
070    private Map<String, Map<String, Permission>> rolePermissionCache = null;
071
072    /** Maps usernames to maps of permission names (in the form 'activity.resource')
073     * to Permission objects (possibly containing ResourceCriteria objects). 
074     *  
075     * If the security context is case-insensitive, then usernames are lower-cased. */
076    private Map<User, Map<String, Permission>> userPermissionCache = null;
077    
078    /** Maps user objects to list of roles. 
079     * 
080     * @TODO convert to HashSet ?
081     */
082    private Map<User, List<String>> userRoleCache = null;
083
084    /** Maps userIds to Users. 
085     */
086    private Map<Long, User> userCache = null;
087    
088    /** This security loader is used to retrieve information from a persistant data
089     *  store for this context */
090    private SecurityLoader securityLoader = null;
091
092    
093    /** The authenticator, if you want to actually check passwords */
094    private SecurityAuthenticator securityAuthenticator = null;
095
096    // should probably use guava caches for all of this now
097    
098    /** This class is invoked by the MRUCache to recalculate values in the
099     * 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}