View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   *
18   * $Id: AuthorizationManager.java,v 1.35 2005/11/24 14:28:45 catoolsen Exp $
19   */
20  package no.feide.moria.authorization;
21  
22  import no.feide.moria.log.MessageLogger;
23  import org.jdom.Document;
24  import org.jdom.Element;
25  import org.jdom.JDOMException;
26  import org.jdom.input.SAXBuilder;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Properties;
35  
36  /***
37   * The AuthorizationManager class is used to parse and store authorization data.
38   * The authorization data source is XML which is passed as a properties object
39   * through the setConfig method. The config data must contain information about
40   * every web service allowed to access Moria, and which attributes, operations
41   * and subsystems the service can access. <br>
42   * <br>
43   * When a new set of data arrives, the authorization manager parses it and
44   * replaces the old dataset if the parsing was successful. The authorization
45   * manager can then be used to answer authorization questions, most likely from
46   * the Moria controller. <br>
47   * <br>
48   * When the controller receives a request, it asks the authorization manager if
49   * the web service is authorized to perform the request. Every request includes
50   * the service principal.
51   * @author Lars Preben S. Arnesen &lt;lars.preben.arnesen@conduct.no&gt;
52   * @version $Revision: 1.35 $
53   */
54  public final class AuthorizationManager {
55  
56      /***
57       * For logging of error messages that cannot be sent to the calling layer.
58       */
59      private final MessageLogger messageLogger = new MessageLogger(AuthorizationManager.class);
60  
61      /***
62       * List of client authorization objects. Must be synchronized.
63       */
64      private HashMap authzClients = new HashMap();
65  
66      /***
67       * List of attributes that is allowed to be cached.
68       */
69      private HashSet cachableAttributes = new HashSet();
70  
71      /***
72       * True if the authorization manager is ready to be used.
73       */
74      private boolean activated = false;
75  
76  
77      /***
78       * Parses an XML element and creates an AuthorizationAttribute object in
79       * return. Throws an IllegalConfigException if there is something wrong with
80       * the element or its attributes.
81       * @param element
82       *            The XML element to parse.
83       * @return AuthorizationAttribute with same attributes as the supplied
84       *         <code>element</code>.
85       * @throws IllegalConfigException
86       *             If the element's sso attribute is not <code>true</code> or
87       *             <code>false</code>.
88       * @throws IllegalArgumentException
89       *             If the <code>AuthorizationAttribute</code> constructor
90       *             throws an exception.
91       */
92      static AuthorizationAttribute parseAttributeElem(final Element element)
93      throws IllegalConfigException {
94  
95          String name = null;
96          String secLevel = null;
97          final String allowSSOStr;
98  
99          if (element.getAttribute("name") != null) {
100             name = element.getAttribute("name").getValue();
101         }
102 
103         if (element.getAttribute("sso") == null) {
104             throw new IllegalConfigException("allowSSO has to be set.");
105         } else {
106             allowSSOStr = element.getAttribute("sso").getValue();
107             if (!(allowSSOStr.equals("true") || allowSSOStr.equals("false"))) { throw new IllegalConfigException("allowSSO has to be 'true' or 'false'"); }
108         }
109 
110         if (element.getAttribute("secLevel") != null) {
111             secLevel = element.getAttribute("secLevel").getValue();
112         }
113 
114         try {
115             return new AuthorizationAttribute(name, new Boolean(allowSSOStr).booleanValue(), new Integer(secLevel).intValue());
116         } catch (IllegalArgumentException e) {
117             throw new IllegalConfigException("Illegal attributes: " + e.getMessage());
118         }
119     }
120 
121 
122     /***
123      * Parses the content of an Attributes element. The element can contain 0 or
124      * more Attribute elements which will be transformed into
125      * AuthorizationAttributes and returned in a HashMap with attribute name as
126      * key.
127      * @param element
128      *            The DOM element that contains <code>Attribute</code> child
129      *            elements.
130      * @return HashMap with AuthorizationAttributes as value and attribute name
131      *         as key.
132      * @throws IllegalConfigException
133      *             If <code>element</code> is not of type
134      *             <code>Attributes</code>.
135      * @throws IllegalArgumentException
136      *             If <code>element</code> is <code>null</code>.
137      */
138     static HashMap parseAttributesElem(final Element element)
139     throws IllegalConfigException {
140 
141         final HashMap attributes = new HashMap();
142 
143         /* Validate element */
144         if (element == null) { throw new IllegalArgumentException("Element cannot be null."); }
145 
146         if (!element.getName().equalsIgnoreCase("attributes")) { throw new IllegalConfigException("Element isn't of type 'Attributes'"); }
147 
148         /* Create AuthorizationAttribute of all child elements */
149         final Iterator it = (element.getChildren()).iterator();
150         while (it.hasNext()) {
151             final AuthorizationAttribute attribute = parseAttributeElem((Element) it.next());
152             attributes.put(attribute.getName(), attribute);
153         }
154 
155         return attributes;
156     }
157 
158 
159     /***
160      * Parses 'operation' and 'organization' elements and returns the name
161      * attribute.
162      * @param element
163      *            The operation element.
164      * @return String containing the name attribute of the element.
165      * @throws IllegalConfigException
166      *             If the element is not of type <code>Operation</code>,
167      *             <code>Subsystem</code> or <code>Organization</code> OR
168      *             element's <code>name</code> attribute is not set.
169      * @throws IllegalArgumentException
170      *             If <code>element</code> is <code>null</code> or an empty
171      *             string.
172      */
173     static String parseChildElem(final Element element)
174     throws IllegalConfigException {
175 
176         if (element == null) { throw new IllegalArgumentException("Element cannot be null"); }
177 
178         if (!element.getName().equalsIgnoreCase("Operation") && !element.getName().equalsIgnoreCase("Subsystem") && !element.getName().equalsIgnoreCase("Organization")) { throw new IllegalConfigException("Element must be of type 'Operation', 'Subsystem' or 'Organization'"); }
179 
180         if (element.getAttribute("name") == null) { throw new IllegalConfigException("Element's name attribute must be set."); }
181 
182         if (element.getAttributeValue("name").equalsIgnoreCase("")) { throw new IllegalConfigException("Element's name attribute cannot be an empty string."); }
183 
184         return element.getAttributeValue("name");
185     }
186 
187 
188     /***
189      * Parses the content of an Attributes element. The element can contain 0 or
190      * more Attribute elements which will be transformed into
191      * AuthorizationAttributes and returned in a HashMap with attribute name as
192      * key.
193      * @param element
194      *            The DOM element that contains Attribute child elements.
195      * @return HashMap with AuthorizationAttributes as value and attribute name
196      *         as key.
197      * @throws IllegalConfigException
198      *             If element is not of type <code>Operations</code>,
199      *             <code>Affiliation</code>, <code>Subsystems</code> or
200      *             <code>OrgsAllowed</code>.
201      * @throws IllegalArgumentException
202      *             If <code>element</code> is <code>null</code>.
203      */
204     static HashSet parseListElem(final Element element)
205     throws IllegalConfigException {
206 
207         final HashSet operations = new HashSet();
208 
209         /* Validate element */
210         if (element == null) { throw new IllegalArgumentException("Element cannot be null."); }
211 
212         if (!element.getName().equalsIgnoreCase("Operations") && !element.getName().equalsIgnoreCase("Subsystems") && !element.getName().equalsIgnoreCase("Affiliation") && !element.getName().equalsIgnoreCase("OrgsAllowed")) { throw new IllegalConfigException("Element isn't of type 'Operations', 'Subsystems', 'Affiliation' or 'OrgsAllowed'"); }
213 
214         /* Create AuthorizationAttribute of all child elements */
215         final Iterator it = (element.getChildren()).iterator();
216         while (it.hasNext()) {
217             final String operation = parseChildElem((Element) it.next());
218             operations.add(operation);
219         }
220 
221         return operations;
222     }
223 
224 
225     /***
226      * Creates an AuthorizationClient object based on the supplied XML element.
227      * @param element
228      *            The XML element representing the client service.
229      * @return A new object representing the client service.
230      * @throws IllegalConfigException
231      *             If the <code>name</code> attribute is not set for the given
232      *             element, or if any of the following tags are missing:
233      *             <ul>
234      *             <code>
235      *             <li>DisplayName
236      *             <li>URL
237      *             <li>Language
238      *             <li>Home
239      *             <li>Attributes
240      *             <li>Operations
241      *             <li>Affiliation
242      *             <li>OrgsAllowed
243      *             </code>
244      *             </ul>
245      * @throws IllegalArgumentException
246      *             If <code>element</code> is <code>null</code>.
247      */
248     static AuthorizationClient parseClientElem(final Element element)
249     throws IllegalConfigException {
250 
251         // Sanity check.
252         if (element == null)
253             throw new IllegalArgumentException("Client element cannot be null");
254 
255         // Prepare some variables for later use.
256         final String name;
257         final String displayName;
258         final String url;
259         final String language;
260         final String home;
261         final HashSet oper;
262         final HashSet affil;
263         final HashSet orgsAllowed;
264         HashSet subsys = null;
265         final HashMap attrs;
266 
267         // Get and check name.
268         name = element.getAttributeValue("name");
269         if (name == null || name.equals(""))
270             throw new IllegalConfigException("Name attribute must be a non empty string.");
271 
272         // Get other content. Error logging is done in called method.
273         displayName = getChildContent(element, "DisplayName");
274         url = getChildContent(element, "URL");
275         language = getChildContent(element, "Language");
276         home = getChildContent(element, "Home");
277 
278         // Parse attributes element.
279         Element child = element.getChild("Attributes");
280         if (child == null)
281             throw new IllegalConfigException("Attributes tag (Attributes) not found for client '" + name + "'");
282         attrs = parseAttributesElem(child);
283 
284         // Parse operations element.
285         child = element.getChild("Operations");
286         if (child == null)
287             throw new IllegalConfigException("Operations tag (Operations) not found for client '" + name + "'");
288         oper = parseListElem(child);
289 
290         // Parse affiliation element.
291         child = element.getChild("Affiliation");
292         if (child == null)
293             throw new IllegalConfigException("Affiliations tag (Affiliation) not found for client '" + name + "'");
294         affil = parseListElem(child);
295 
296         // Parse allowed organizations element.
297         child = element.getChild("OrgsAllowed");
298         if (child == null)
299             throw new IllegalConfigException("Organizations allowed tag (OrgsAllowed) not found for client '" + name + "'");
300         orgsAllowed = parseListElem(child);
301 
302         // Parse subsystems element, if it exists.
303         child = element.getChild("Subsystems");
304         if (child != null)
305             subsys = parseListElem(child);
306 
307         return new AuthorizationClient(name, displayName, url, language, home, affil, orgsAllowed, oper, subsys, attrs);
308     }
309 
310 
311     /***
312      * Parses a configuration root element with client elements.
313      * @param element
314      *            The root element.
315      * @return A HashMap containing AuthorizationClient objects.
316      * @throws IllegalConfigException
317      *             If the element is not of type
318      *             <code>ClientAuthorizationConfig</code>.
319      * @throws IllegalArgumentException
320      *             If <code>element</code> is <code>null</code>.
321      * @see AuthorizationClient
322      */
323     static HashMap parseRootElem(final Element element)
324     throws IllegalConfigException {
325 
326         final HashMap clients = new HashMap();
327 
328         if (element == null) { throw new IllegalArgumentException("Element cannot be null"); }
329 
330         if (!element.getName().equalsIgnoreCase("ClientAuthorizationConfig")) { throw new IllegalConfigException("Wrong type of element: " + element.getName()); }
331 
332         final List children = element.getChildren("Client");
333         final Iterator it = children.iterator();
334         while (it.hasNext()) {
335             final AuthorizationClient client = parseClientElem((Element) it.next());
336             clients.put(client.getName(), client);
337         }
338 
339         return clients;
340     }
341 
342 
343     /***
344      * Retrieves the content of an XML element.
345      * @param element
346      *            Parent element.
347      * @param childName
348      *            Name of the child node.
349      * @return The content of the child element.
350      * @throws IllegalConfigException
351      *             If the content of the child element is null.
352      */
353     private static String getChildContent(final Element element,
354                                           final String childName)
355     throws IllegalConfigException {
356 
357         final String value = element.getChildText(childName);
358         if (value == null) {
359             throw new IllegalConfigException(childName + " tag not found");
360         } else {
361             return value;
362         }
363     }
364 
365 
366     /***
367      * Returns a client object for a given identifier.
368      * @param servicePrincipal
369      *            The client object identifier.
370      * @return The client object for the identifier.
371      * @throws NoConfigException
372      *             If the authorization manager is not activated.
373      * @throws IllegalArgumentException
374      *             If <code>servicePrincipal</code> is <code>null</code> or
375      *             an empty string.
376      */
377     private AuthorizationClient getAuthzClient(final String servicePrincipal) {
378 
379         /* Is the manager activated? */
380         if (!activated) { throw new NoConfigException(); }
381 
382         /* Validate input parameters */
383         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string."); }
384 
385         return (AuthorizationClient) authzClients.get(servicePrincipal);
386     }
387 
388 
389     /***
390      * Validates a request for access to attributes for a given client/service.
391      * @param servicePrincipal
392      *            The identifier of the client.
393      * @param requestedAttributes
394      *            The list of requested attributes.
395      * @return true if the service is allowed access, false if not or the client
396      *         does not exist.
397      * @throws UnknownServicePrincipalException
398      *             If the service principal does not exist.
399      */
400     public boolean allowAccessTo(final String servicePrincipal,
401                                  final String[] requestedAttributes)
402     throws UnknownServicePrincipalException {
403 
404         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
405 
406         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
407 
408         return authzClient.allowAccessTo(requestedAttributes);
409     }
410 
411 
412     /***
413      * Validates a request for access to SSO for a given client/service.
414      * @param servicePrincipal
415      *            The identifier of the client.
416      * @param requestedAttributes
417      *            The list of requested attributes.
418      * @return true if the service is allowed access, false if not or the client
419      *         does not exist.
420      * @throws UnknownServicePrincipalException
421      *             If the service principal does not exist.
422      */
423     public boolean allowSSOForAttributes(final String servicePrincipal,
424                                          final String[] requestedAttributes)
425     throws UnknownServicePrincipalException {
426 
427         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
428 
429         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
430 
431         return authzClient.allowSSOForAttributes(requestedAttributes);
432     }
433 
434 
435     /***
436      * Validates a request for access to operations for a given client/service.
437      * @param servicePrincipal
438      *            The identifier of the client.
439      * @param requestedOperations
440      *            The list of requested operations.
441      * @return true if the service is allowed access, false if not or the client
442      *         does not exist.
443      * @throws UnknownServicePrincipalException
444      *             If the servicePrincipal does not exist.
445      */
446     public boolean allowOperations(final String servicePrincipal,
447                                    final String[] requestedOperations)
448     throws UnknownServicePrincipalException {
449 
450         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
451 
452         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
453 
454         return authzClient.allowOperations(requestedOperations);
455     }
456 
457 
458     /***
459      * Checks if the organization is allowed to use the service.
460      * @param servicePrincipal
461      *            The identifier of the client.
462      * @param userorg
463      *            The user's organization.
464      * @return true if the organization is allowed to use the service, false if
465      *         the client does not exists, or if the organization is not allowed
466      *         to use the service.
467      * @throws UnknownServicePrincipalException
468      *             If the servicePrincipal does not exist.
469      */
470     public boolean allowUserorg(final String servicePrincipal,
471                                 final String userorg)
472     throws UnknownServicePrincipalException {
473 
474         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
475 
476         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
477 
478         return authzClient.allowUserorg(userorg);
479     }
480 
481 
482     /***
483      * Swaps the old client database with the supplied HashMap.
484      * @param newClients
485      *            The new client database.
486      * @throws IllegalArgumentException
487      *             If <code>newClients</code> is <code>null</code>.
488      */
489     synchronized void setAuthzClients(final HashMap newClients) {
490 
491         if (newClients == null) { throw new IllegalArgumentException("newClients to be set cannot be null"); }
492 
493         /* Generate a list of attributes that is allowed to be cached */
494         final HashSet newCachableAttributes = new HashSet();
495         final Iterator clientIt = newClients.keySet().iterator();
496         while (clientIt.hasNext()) {
497             final AuthorizationClient authzClient = (AuthorizationClient) newClients.get(clientIt.next());
498 
499             final HashMap attributes = authzClient.getAttributes();
500             final Iterator attrIt = attributes.keySet().iterator();
501             while (attrIt.hasNext()) {
502                 final AuthorizationAttribute attr = (AuthorizationAttribute) attributes.get(attrIt.next());
503                 if (attr.getAllowSSO()) {
504                     newCachableAttributes.add(attr.getName());
505                 }
506             }
507         }
508 
509         /* Set new authorization configuration */
510         synchronized (authzClients) {
511             authzClients = newClients;
512             cachableAttributes = newCachableAttributes;
513             activated = true;
514         }
515     }
516 
517 
518     /***
519      * Sets the configuration data for this manager.
520      * @param properties
521      *            The properties containing the authorization database.
522      * @throws IllegalArgumentException
523      *             If <code>properties</code> is <code>null</code>.
524      */
525     public void setConfig(final Properties properties) {
526 
527         // Sanity checks.
528         if (properties == null)
529             throw new IllegalArgumentException("Properties cannot be null");
530         final String fileName = (String) properties.get("authorizationDatabase");
531         if (fileName == null || fileName.equals("")) {
532             messageLogger.logWarn("The 'authorizationDatabase' property is not set or an empty string");
533             return;
534         }
535         File database = new File(fileName);
536         if (!database.exists()) {
537             messageLogger.logWarn("Authorization database file '" + fileName + "' does not exist");
538             return;
539         }
540 
541         // Parse authorization database file.
542         final SAXBuilder builder = new SAXBuilder();
543         try {
544 
545             final Document doc = builder.build(database);
546             final HashMap newClients = parseRootElem(doc.getRootElement());
547             setAuthzClients(newClients);
548 
549         } catch (JDOMException e) {
550             messageLogger.logWarn("Error parsing authorization database file '" + fileName + " - using old database", e);
551         } catch (IOException e) {
552             messageLogger.logWarn("Error reading authorization database file '" + fileName + " - using old database", e);
553         } catch (IllegalConfigException e) {
554             messageLogger.logWarn("Error generating authorization database - using old database", e);
555         }
556 
557     }
558 
559 
560     /***
561      * Returns the service properties for a given service.
562      * @param servicePrincipal
563      *            The principal of the service.
564      * @return A hashmap with properties for a given service.
565      * @throws UnknownServicePrincipalException
566      *             If the service principal does not exist.
567      * @throws IllegalArgumentException
568      *             If <code>servicePrincipal</code> is <code>null</code> or
569      *             an empty string.
570      * @see AuthorizationClient#getProperties()
571      */
572     public HashMap getServiceProperties(final String servicePrincipal)
573     throws UnknownServicePrincipalException {
574 
575         /* Validate parameters */
576         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
577 
578         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
579         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
580 
581         return authzClient.getProperties();
582     }
583 
584 
585     /***
586      * Returns the security level for a set of attributes for a given service.
587      * @param servicePrincipal
588      *            The service principal of the requested service.
589      * @param requestedAttributes
590      *            The requested attributes.
591      * @return Security level - an integer >= 0.
592      * @throws UnknownServicePrincipalException
593      *             If the service principal does not exist.
594      * @throws UnknownAttributeException
595      *             If one or more of the requested attributes does not exist.
596      * @throws IllegalArgumentException
597      *             If <code>servicePrincipal</code> is <code>null</code> or
598      *             an empty string, or if <code>requestedAttributes</code> is
599      *             <code>null</code>.
600      * @see AuthorizationClient#getSecLevel(java.lang.String[])
601      */
602     public int getSecLevel(final String servicePrincipal,
603                            final String[] requestedAttributes)
604     throws UnknownServicePrincipalException, UnknownAttributeException {
605 
606         /* Validate arguments */
607         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
608         if (requestedAttributes == null) { throw new IllegalArgumentException("requestedAttributes cannot be null"); }
609 
610         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
611         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
612 
613         /* Return lowest seclevel if no attributes are requested */
614         if (requestedAttributes.length == 0) { return 0; }
615 
616         return authzClient.getSecLevel(requestedAttributes);
617     }
618 
619 
620     /***
621      * Returns the configured attributes for a given service.
622      * @param servicePrincipal
623      *            The principal of the requested service.
624      * @return A string array with the attribute names that is configured for
625      *         the service.
626      * @throws UnknownServicePrincipalException
627      *             If the servicePrincipal does not exist.
628      * @throws IllegalArgumentException
629      *             If <code>servicePrincipal</code> is <code>null</code> or
630      *             an empty string.
631      * @see AuthorizationClient#getAttributes()
632      */
633     public HashSet getAttributes(final String servicePrincipal)
634     throws UnknownServicePrincipalException {
635 
636         /* Validate argument */
637         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
638 
639         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
640         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
641 
642         return new HashSet(authzClient.getAttributes().keySet());
643     }
644 
645 
646     /***
647      * Returns the organizations that can use this service.
648      * @param servicePrincipal
649      *            The principal of the requested service.
650      * @return A string array with the names of the allowed organizations for
651      *         the service.
652      * @throws UnknownServicePrincipalException
653      *             If the servicePrincipal does not exist.
654      * @throws IllegalArgumentException
655      *             If <code>servicePrincipal</code> is <code>null</code> or
656      *             an empty string.
657      * @see AuthorizationClient#getOrgsAllowed()
658      */
659     public HashSet getOrgsAllowed(final String servicePrincipal)
660     throws UnknownServicePrincipalException {
661 
662         /* Validate argument */
663         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
664 
665         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
666         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
667 
668         return authzClient.getOrgsAllowed();
669     }
670 
671 
672     /***
673      * Returns the configured subsystems for a given service.
674      * @param servicePrincipal
675      *            The principal of the requested service,
676      * @return A string array with the subsystem names that is configured for
677      *         the service.
678      * @throws UnknownServicePrincipalException
679      *             If the servicePrincipal does not exist.
680      * @throws IllegalArgumentException
681      *             If <code>servicePrincipal</code> is <code>null</code> or
682      *             an empty string.
683      * @see AuthorizationClient#getSubsystems()
684      */
685     public HashSet getSubsystems(final String servicePrincipal)
686     throws UnknownServicePrincipalException {
687 
688         /* Validate argument */
689         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
690 
691         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
692         if (authzClient == null) { throw new UnknownServicePrincipalException("Service principal does not exist: '" + servicePrincipal + "'"); }
693 
694         return authzClient.getSubsystems();
695     }
696 
697 
698     /***
699      * Returns the configured operations for a given service.
700      * @param servicePrincipal
701      *            The principal of the requested service.
702      * @return A string array with the operation names that is configured for
703      *         the service.
704      * @throws UnknownServicePrincipalException
705      *             If the servicePrincipal does not exist.
706      * @throws IllegalArgumentException
707      *             If <code>servicePrincipal</code> is <code>null</code> or
708      *             an empty string.
709      * @see AuthorizationClient#getOperations()
710      */
711     public HashSet getOperations(final String servicePrincipal)
712     throws UnknownServicePrincipalException {
713 
714         /* Validate argument */
715         if (servicePrincipal == null || servicePrincipal.equals("")) { throw new IllegalArgumentException("servicePrincipal must be a non-empty string"); }
716 
717         final AuthorizationClient authzClient = getAuthzClient(servicePrincipal);
718         if (authzClient == null)
719             throw new UnknownServicePrincipalException("Unknown service principal: '" + servicePrincipal + "'");
720 
721         return authzClient.getOperations();
722     }
723 
724 
725     /***
726      * Returns the set of SSO attributes names (the attributes that can be
727      * cached).
728      * @return A set of attributes that can be cached.
729      */
730     public HashSet getCachableAttributes() {
731 
732         return new HashSet(cachableAttributes);
733     }
734 
735 
736     /***
737      * Get the list of attribute names not allowed for use in an SSO context for
738      * a given service.
739      * @param servicePrincipal
740      *            The service principal. Must be a non-empty string.
741      * @return An array of attribute names. May be an empty array, but never
742      *         <code>null</code>.
743      * @throws UnknownServicePrincipalException
744      *             If the <code>servicePrincipal</code> is unknown.
745      * @throws IllegalArgumentException
746      *             If <code>servicePrincipal</code> is <code>null</code> or
747      *             an empty string.
748      */
749     public String[] getNonSSOAttributeNames(final String servicePrincipal)
750     throws UnknownServicePrincipalException, IllegalArgumentException {
751 
752         // Sanity check.
753         if (servicePrincipal == null || servicePrincipal.equals(""))
754             throw new IllegalArgumentException("Service principal must be a non-empty string");
755 
756         // Resolve client.
757         final AuthorizationClient client = getAuthzClient(servicePrincipal);
758         if (client == null)
759             throw new UnknownServicePrincipalException("Unknown service principal: '" + servicePrincipal + "'");
760 
761         return client.getNonSSOAttributeNames();
762     }
763 }