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   *
19   * $Id: AuthorizationClient.java,v 1.29 2005/11/24 14:28:45 catoolsen Exp $
20   */
21  
22  package no.feide.moria.authorization;
23  
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Stack;
28  
29  import no.feide.moria.log.MessageLogger;
30  
31  /***
32   * Represents a web service. A web service has a name, id, url and attributes.
33   * The attributes are flattened (for optimization) from a set of profiles,
34   * allowed and denied attributes.
35   */
36  final class AuthorizationClient {
37  
38      /*** Used for logging. */
39      private final MessageLogger log = new MessageLogger(AuthorizationClient.class);
40  
41      /***
42       * Cached hash code.
43       */
44      private volatile int hashCode = 0;
45  
46      /***
47       * Unique identifier (principal) for the client.
48       */
49      private final String name;
50  
51      /***
52       * Common name of the service.
53       */
54      private final String displayName;
55  
56      /***
57       * Home page URL for web service. Used for creating hyperlinks (together
58       * with the name of the web service).
59       */
60      private final String url;
61  
62      /***
63       * Language preferred by the web service.
64       */
65      private final String language;
66  
67      /***
68       * The organization the webservice sets as default. Typically this is set to
69       * the organization that the web service belongs to.
70       */
71      private final String home;
72  
73      /***
74       * The organizations that the service belongs to.
75       */
76      private final HashSet affiliation;
77  
78      /***
79       * The organizations that can use the service.
80       */
81      private final HashSet orgsAllowed;
82  
83      /***
84       * The operations the client can perform.
85       */
86      private final HashSet operations;
87  
88      /***
89       * The subsystems the client can use proxy authentication for.
90       */
91      private final HashSet subsystems;
92  
93      /***
94       * Attributes the client can query.
95       */
96      private final HashMap attributes;
97  
98      /***
99       * The properties of this object. Used to transport internal data outside of
100      * the package.
101      */
102     private final HashMap properties = new HashMap();
103 
104 
105     /***
106      * Constructor. Creates a new object describing a Moria service client, used
107      * for authorization purposes.
108      * @param name
109      *            serviceID The unique client ID assigned to this service.
110      *            Cannot be <code>null</code> or an empty string.
111      * @param displayName
112      *            Full name of the service, for display purposes. Cannot be
113      *            <code>null</code> or an empty string.
114      * @param url
115      *            URL to the service main page, where information on the service
116      *            should be found. Cannot be <code>null</code> or an empty
117      *            string.
118      * @param language
119      *            Default language for the service. Must match one of the
120      *            configured languages. Cannot be <code>null</code> or an
121      *            empty string.
122      * @param home
123      *            Service home organization. Must match one of the configured
124      *            organizations. Cannot be <code>null</code> or an empty
125      *            string.
126      * @param affiliation
127      *            The organizations affiliated to the service. Cannot be
128      *            <code>null</code>.
129      * @param orgsAllowed
130      *            The organizations that are allowed to use the service. Cannot
131      *            be null.
132      * @param operations
133      *            Operations that the service can perform. Cannot be
134      *            <code>null</code>.
135      * @param subsystems
136      *            Subsystems the service can create proxy tickets for. May be
137      *            <code>null</code>.
138      * @param attributes
139      *            Attributes the service can access. Cannot be <code>null</code>.
140      * @throws IllegalArgumentException
141      *             If any of <code>name</code>,<code>displayName</code>,
142      *             <code>url</code>,<code>language</code>,
143      *             <code>home</code>,<code>affiliation</code>,
144      *             <code>allowedOrg</code>, <code>operations</code>, or
145      *             <code>attributes</code> are <code>null</code> or an empty
146      *             string (where applicable).
147      */
148     AuthorizationClient(final String name, final String displayName,
149                         final String url, final String language,
150                         final String home, final HashSet affiliation,
151                         final HashSet orgsAllowed, final HashSet operations,
152                         final HashSet subsystems, final HashMap attributes) {
153 
154         if (name == null || name.equals("")) { throw new IllegalArgumentException("Name must be a non empty string."); }
155 
156         if (displayName == null || displayName.equals("")) { throw new IllegalArgumentException("displayName must be a non empty string."); }
157 
158         if (url == null || url.equals("")) { throw new IllegalArgumentException("URL must be a non empty string."); }
159 
160         if (language == null || language.equals("")) { throw new IllegalArgumentException("Language must be a non empty string."); }
161 
162         if (home == null || home.equals("")) { throw new IllegalArgumentException("Home must be a non empty string."); }
163 
164         if (affiliation == null) { throw new IllegalArgumentException("Affiliation cannot be null."); }
165 
166         if (orgsAllowed == null) { throw new IllegalArgumentException("OrgsAllowed cannot be null."); }
167 
168         if (operations == null) { throw new IllegalArgumentException("Operations cannot be null."); }
169 
170         if (attributes == null) { throw new IllegalArgumentException("Attributes cannot be null."); }
171 
172         // Assign.
173         this.name = name;
174         this.displayName = displayName;
175         this.url = url;
176         this.language = language;
177         this.home = home;
178         this.affiliation = affiliation;
179         this.orgsAllowed = orgsAllowed;
180         this.operations = operations;
181         this.subsystems = subsystems;
182         this.attributes = attributes;
183 
184         // TODO: At least "language" is referenced directly in LoginServlet,
185         // through constant in RequestUtil. Same constant should be used there
186         // and here!
187         properties.put("displayName", new String(displayName));
188         properties.put("url", new String(url));
189         properties.put("language", new String(language));
190         properties.put("home", new String(home));
191         properties.put("name", new String(name));
192 
193     }
194 
195 
196     /***
197      * Checks if all the requested attributes are legal for this web service.
198      * @param requestedAttributes
199      *            Names of all requested attributes.
200      * @return true if access to the attributes is granted, else false.
201      * @throws IllegalArgumentException
202      *             If <code>requestedAttributes</code> is <code>null</code>.
203      */
204     boolean allowAccessTo(final String[] requestedAttributes) {
205 
206         boolean allow = true;
207 
208         if (requestedAttributes == null) { throw new IllegalArgumentException("RequestedAttributes cannot be null"); }
209 
210         if (requestedAttributes.length == 0) { return true; }
211 
212         for (int i = 0; i < requestedAttributes.length; i++) {
213             if (!attributes.containsKey(requestedAttributes[i])) {
214                 allow = false;
215                 break;
216             }
217         }
218         return allow;
219     }
220 
221 
222     /***
223      * Checks attributes for use with single sign-on (SSO). If all attributes
224      * are registered in the web service's attributes list and all attributes
225      * are allowed to use with SSO, then so be it.
226      * @param requestedAttributes
227      *            The names of all requested attributes.
228      * @return true if the attributes can be used with SSO, else false.
229      * @throws IllegalArgumentException
230      *             If <code>requestedAttributes</code> is <code>null</code>.
231      */
232     boolean allowSSOForAttributes(final String[] requestedAttributes) {
233 
234         // Sanity check.
235         if (requestedAttributes == null)
236             throw new IllegalArgumentException("Requested attributes cannot be NULL");
237 
238         // Check all requested attributes against available attributes.
239         for (int i = 0; i < requestedAttributes.length; i++) {
240             final String attrName = requestedAttributes[i];
241             if (!attributes.containsKey(attrName) || !((AuthorizationAttribute) attributes.get(attrName)).getAllowSSO())
242                 // SSO not allowed for these attributes.
243                 return false;
244         }
245 
246         // SSO allowed for these attributes.
247         return true;
248     }
249 
250 
251     /***
252      * Get the attributes not allowed for use in an SSO context for this client.
253      * @return An array of attribute names. May be an empty array, but never
254      *         <code>null</code>.
255      */
256     protected String[] getNonSSOAttributeNames() {
257 
258         Stack buffer = new Stack();
259         Iterator i = attributes.keySet().iterator();
260         AuthorizationAttribute attribute;
261         while (i.hasNext()) {
262             attribute = (AuthorizationAttribute) attributes.get(i.next());
263             if (!attribute.getAllowSSO())
264                 buffer.push(attribute.getName());
265         }
266         return (String[]) buffer.toArray(new String[] {});
267 
268     }
269 
270 
271     /***
272      * Returns true if the supplied organization name is affiliated with the
273      * client.
274      * @param organization
275      *            Name of the organization to match.
276      * @return true if the supplied organization name is affiliated with the
277      *         client.
278      * @throws IllegalArgumentException
279      *             If <code>organization</code> is <code>null</code> or an
280      *             empty string.
281      */
282     boolean hasAffiliation(final String organization) {
283 
284         if (organization == null || organization.equals("")) { throw new IllegalArgumentException("Organization must be a non empty string"); }
285 
286         return affiliation.contains(organization);
287     }
288 
289 
290     /***
291      * Returns true if all elements in the requestedOperations array are
292      * represented in the objects operations set.
293      * @param requestedOperations
294      *            A string array of operation names
295      * @return true if all operations are allowed, else false.
296      * @throws IllegalArgumentException
297      *             If <code>requestedOperations</code> is <code>null</code>.
298      */
299     boolean allowOperations(final String[] requestedOperations) {
300 
301         if (requestedOperations == null) { throw new IllegalArgumentException("RequestedOperations cannot be null"); }
302 
303         if (requestedOperations.length == 0) { return true; }
304 
305         for (int i = 0; i < requestedOperations.length; i++) {
306             if (!operations.contains(requestedOperations[i])) { return false; }
307         }
308 
309         return true;
310     }
311 
312 
313     /***
314      * Returns true for the organizations that are allowed to use this service.
315      * @param organization
316      *            The organization requesting authorization.
317      * @return true if the organization can use this service.
318      * @throws IllegalArgumentException
319      *             If <code>organization</code> is <code>null</code>.
320      */
321     boolean allowUserorg(final String organization) {
322 
323         // Sanity check.
324         if (organization == null) {
325             log.logInfo("organization = " + organization);
326             throw new IllegalArgumentException("Organization cannot be null");
327         }
328         // If no allowed organizations are defined, then all denied
329         if (orgsAllowed == null) {
330             log.logInfo("orgsAllowed = " + orgsAllowed);
331             return false;
332         }
333         return orgsAllowed.contains(organization);
334     }
335 
336 
337     /***
338      * Used to decide whether subsystems are allowed for this particular client,
339      * based on its configuration.
340      * @param requestedSubsystems
341      *            A string array of subsystem names. Cannot be <code>null</code>.
342      * @return <code>true</code> if subsystems are allowed, otherwise
343      *         <code>false</code>.
344      * @throws IllegalArgumentException
345      *             If <code>requestedSubsystems</code> is <code>null</code>.
346      */
347     boolean allowSubsystems(final String[] requestedSubsystems) {
348 
349         // Sanity checks.
350         if (requestedSubsystems == null) {
351             log.logInfo("requestedSubsystems = " + requestedSubsystems);
352             throw new IllegalArgumentException("RequestedSubsystems cannot be null");
353         }
354 
355         // No subsystems requested or defined? Then we won't allow 'em!
356         if ((requestedSubsystems.length == 0) || (subsystems == null)) {
357             log.logInfo("requestedSubsystems.length = " + requestedSubsystems.length);
358             log.logInfo("subsystems = " + subsystems);
359             return false;
360         }
361 
362         // If all the requested subsystems are defined for this service, we're
363         // allowing it.
364         for (int i = 0; i < requestedSubsystems.length; i++) {
365             if (!subsystems.contains(requestedSubsystems[i]))
366                 return false; // Ouch! A requested subsystem wasn't defined!
367         }
368         return true;
369     }
370 
371 
372     /***
373      * Compares object with another, returnes true if all fields are equal.
374      * @param object
375      *            The object to compare with.
376      * @return true if objects are equal.
377      */
378     public boolean equals(final Object object) {
379 
380         if (object == this) { return true; }
381         if (object instanceof AuthorizationClient) {
382             final AuthorizationClient client = (AuthorizationClient) object;
383             if (client.getName().equals(name) && client.getDisplayName().equals(displayName) && client.getURL().equals(url) && client.getLanguage().equals(language) && client.getHome().equals(home) && client.getAffiliation().equals(affiliation) && client.getOrgsAllowed().equals(orgsAllowed) && client.getOperations().equals(operations) && client.getSubsystems().equals(subsystems) && client.getAttributes().equals(attributes)) { return true; }
384         }
385         return false;
386     }
387 
388 
389     /***
390      * Generate a hash code for the object. The hash code is computed from all
391      * fields.
392      * @return The hash code.
393      */
394     public int hashCode() {
395 
396         if (hashCode == 0) {
397             int result = 17;
398             result = 37 * result + name.hashCode();
399             result = 37 * result + displayName.hashCode();
400             result = 37 * result + url.hashCode();
401             result = 37 * result + language.hashCode();
402             result = 37 * result + home.hashCode();
403             result = 37 * result + affiliation.hashCode();
404             result = 37 * result + orgsAllowed.hashCode();
405             result = 37 * result + operations.hashCode();
406             result = 37 * result + subsystems.hashCode();
407             result = 37 * result + attributes.hashCode();
408             hashCode = result;
409         }
410         return hashCode;
411 
412     }
413 
414 
415     /***
416      * Returns a string representation of this object.
417      * @return A string representation of this object: Name: NAME DisplayName:
418      *         DISPLAYNAME URL: URL Language: LANGUAGE Home: HOME Affiliations:
419      *         AFFILIATION Operations: OPERATIONS Attributes: ATTRIBUTES
420      */
421     public String toString() {
422 
423         return "Name: " + name + " DisplayName: " + displayName + " URL: " + url + " Language: " + language + " Home: " + home + " Affiliation: " + affiliation + " Operations: " + operations + "Attributes: " + attributes;
424     }
425 
426 
427     /***
428      * Returns the URL for this client.
429      * @return The URL for the main page of the client service.
430      */
431     public String getURL() {
432 
433         return new String(url);
434     }
435 
436 
437     /***
438      * Returns the principal of this client.
439      * @return Client's principal.
440      */
441     public String getName() {
442 
443         return new String(name);
444     }
445 
446 
447     /***
448      * Returns the display name for this client.
449      * @return Name of the client, to be displayed to the user.
450      */
451     public String getDisplayName() {
452 
453         return new String(displayName);
454     }
455 
456 
457     /***
458      * Returns the language for this client.
459      * @return Language of the client service.
460      */
461     public String getLanguage() {
462 
463         return new String(language);
464     }
465 
466 
467     /***
468      * Returns the home organization for this client.
469      * @return The home organization of the client service.
470      */
471     public String getHome() {
472 
473         return new String(home);
474     }
475 
476 
477     /***
478      * Returns the affiliation for this client.
479      * @return Returns the affiliation.
480      */
481     HashSet getAffiliation() {
482 
483         return new HashSet(affiliation);
484     }
485 
486 
487     /***
488      * Returns the organizations that are allowed to use the client.
489      * @return The organizations.
490      */
491     HashSet getOrgsAllowed() {
492 
493         return new HashSet(orgsAllowed);
494     }
495 
496 
497     /***
498      * Returns the operations for this client.
499      * @return Returns the operations.
500      */
501     HashSet getOperations() {
502 
503         return new HashSet(operations);
504     }
505 
506 
507     /***
508      * Returns the subsystems for this client, if any are defined.
509      * @return A new <code>HashSet</code> object containing the defined
510      *         subsystems, or <code>null</code> if no subsystems are defined
511      *         for this client.
512      */
513     HashSet getSubsystems() {
514 
515         if (subsystems == null) { return null; }
516         return new HashSet(subsystems);
517     }
518 
519 
520     /***
521      * Returns the attributes for this client.
522      * @return Returns the attributes.
523      */
524     HashMap getAttributes() {
525 
526         return new HashMap(attributes);
527     }
528 
529 
530     /***
531      * Gets the properties for this client. The properties object contains the
532      * data that should be transferred to other packages.
533      * @return The properties for this object.
534      */
535     public HashMap getProperties() {
536 
537         return properties;
538     }
539 
540 
541     /***
542      * Returns the highest secLevel of the requested attributes.
543      * @param requestedAttributes
544      *            The requested attributes.
545      * @return The highest of the attributes seclevel, 0 if no attributes are
546      *         requested.
547      * @throws UnknownAttributeException
548      *             if one (or more) of the requested attributes are not present
549      *             in the authorization client.
550      * @throws IllegalArgumentException
551      *             If <code>requestedAttributes</code> is <code>null</code>.
552      */
553     int getSecLevel(final String[] requestedAttributes)
554     throws UnknownAttributeException {
555 
556         if (requestedAttributes == null) { throw new IllegalArgumentException("requestedAttributes cannot be null"); }
557 
558         if (requestedAttributes.length == 0) { return 0; }
559 
560         int res = 0;
561         for (int i = 0; i < requestedAttributes.length; i++) {
562             final AuthorizationAttribute authzAttribute = (AuthorizationAttribute) attributes.get(requestedAttributes[i]);
563             if (authzAttribute == null) { throw new UnknownAttributeException("Attribute '" + authzAttribute + "' does not exist."); }
564             if (authzAttribute.getSecLevel() > res) {
565                 res = authzAttribute.getSecLevel();
566             }
567         }
568 
569         return res;
570     }
571 }