001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/providers/tags/sakai_2-4-1/federating/src/java/org/sakaiproject/provider/user/FilterUserDirectoryProvider.java $
003: * $Id: FilterUserDirectoryProvider.java 14739 2006-09-15 19:48:17Z jholtzman@berkeley.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.provider.user;
021:
022: // imports
023: import java.security.SecureRandom;
024: import java.util.ArrayList;
025: import java.util.Collection;
026: import java.util.Iterator;
027: import java.util.Vector;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import org.sakaiproject.user.api.User;
033: import org.sakaiproject.user.api.UserDirectoryProvider;
034: import org.sakaiproject.user.api.UserEdit;
035: import org.sakaiproject.user.api.UserFactory;
036: import org.sakaiproject.user.api.UsersShareEmailUDP;
037:
038: /**
039: * <p>
040: * Filter User Directory Provider, calls a configure provider, and if that fails
041: * calls the next provider in the chain. It does this for all methods. If the
042: * response is a boolean, then true stop processing, false continues processon.
043: * It is the reponsibility of the injected user directory provider to ignore
044: * those calls that have nothing to do with it, either by reference to the
045: * session, or by refernce to something in the environment or request. To Use,
046: * add one or more of these beans to Spring, in a chain, marking the first one
047: * in the chain as the 'official' userdirectory provider used by Sakai.
048: * Construct the chain by setting the next FilterUserDirectorProvider to the
049: * nextProvider and the real User Directory Provider to myProvider eg
050: *
051: * <pre>
052: *
053: * <bean
054: * id="org.sakaiproject.user.api.UserDirectoryProvider"
055: * class="org.sakaiproject.provider.user.FilterUserDirectoryProvider"
056: * init-method="init" destroy-method="destroy" singleton="true">
057: * <property name="myProvider">
058: * <ref bean="org.sakaiproject.user.api.UserDirectoryProvider.provider1" />
059: * </property>
060: * <property name="nextProvider">
061: * <ref bean="org.sakaiproject.user.api.UserDirectoryProvider.chain1" />
062: * </property>
063: * </bean>
064: * <bean
065: * id="org.sakaiproject.user.api.UserDirectoryProvider.chain1"
066: * class="org.sakaiproject.provider.user.FilterUserDirectoryProvider"
067: * init-method="init" destroy-method="destroy" singleton="true">
068: * <property name="myProvider">
069: * <ref bean="org.sakaiproject.user.api.UserDirectoryProvider.provider2" />
070: * </property>
071: * </bean>
072: * </pre>
073: *
074: * @author Ian Boston, Andrew Thornton, Daniel Parry, Raad
075: * @version $Revision: 14739 $
076: */
077: public class FilterUserDirectoryProvider implements
078: UserDirectoryProvider, UsersShareEmailUDP {
079: /** Our log (commons). */
080: private static Log m_logger = LogFactory
081: .getLog(FilterUserDirectoryProvider.class);
082:
083: private static ThreadLocal authenticatedProvider = new ThreadLocal();
084:
085: /**********************************************************************************************************************************************************************************************************************************************************
086: * Dependencies and their setter methods
087: *********************************************************************************************************************************************************************************************************************************************************/
088: // The spring injected user provider we call
089: /**
090: * The underlying UserDirectoryProvider
091: */
092: private UserDirectoryProvider myProvider;
093: /**
094: * The Next directory provider in the chain
095: */
096: private UserDirectoryProvider nextProvider;
097:
098: private Long providerID = null;
099:
100: /**
101: * Final initialization, once all dependencies are set.
102: */
103: public void init() {
104: // Initalise the providerID as a random Long. SecureRandom is guareneed to
105: // to give a seperate id, however its not entirely thread safe, so I've reused
106: // the thread local. It gets thrown away on the first auth attempt, so the
107: // secure random wont hand arround in production.
108:
109: SecureRandom sr = (SecureRandom) authenticatedProvider.get();
110: if (sr == null) {
111: sr = new SecureRandom();
112: authenticatedProvider.set(sr);
113: }
114: providerID = new Long(sr.nextLong());
115: try {
116: m_logger.info("init() FILTER as " + providerID);
117: } catch (Throwable t) {
118: m_logger.info(".init(): ", t);
119: }
120:
121: } // init
122:
123: /**
124: * Returns to uninitialized state. You can use this method to release resources thet your Service allocated when Turbine shuts down.
125: */
126: public void destroy() {
127:
128: m_logger.info("destroy()");
129:
130: } // destroy
131:
132: /**********************************************************************************************************************************************************************************************************************************************************
133: * UserDirectoryProvider implementation
134: *********************************************************************************************************************************************************************************************************************************************************/
135:
136: /**
137: * Construct.
138: */
139: public FilterUserDirectoryProvider() {
140: } // Switch
141:
142: /**
143: * See if a user by this id exists.
144: *
145: * @param userId
146: * The user id string.
147: * @return true if a user by this id exists, false if not.
148: */
149: public boolean userExists(String userId) {
150: if (m_logger.isDebugEnabled()) {
151: m_logger.debug("userExists() as " + providerID + " on "
152: + userId);
153: }
154: if (myProvider.userExists(userId)) {
155: return true;
156: } else if (nextProvider != null) {
157: return nextProvider.userExists(userId);
158: }
159: return false;
160: } // userExists
161:
162: /**
163: * Access a user object. Update the object with the information found.
164: *
165: * @param edit
166: * The user object (id is set) to fill in.
167: * @return true if the user object was found and information updated, false if not.
168: */
169: public boolean getUser(UserEdit edit) {
170: m_logger.debug("FUDP: getUser(" + edit.getId() + " eid:"
171: + edit.getEid() + ") as " + providerID);
172: m_logger.debug("FUDP: doing myProvider.getUser() as "
173: + providerID);
174: if (myProvider.getUser(edit)) {
175: return true;
176: } else if (nextProvider != null) {
177: m_logger.debug("FUDP: doing nextProvider.getUser() as "
178: + providerID);
179: return nextProvider.getUser(edit);
180: }
181: return false;
182:
183: } // getUser
184:
185: /**
186: * Access a collection of UserEdit objects; if the user is found, update the information, otherwise remove the UserEdit object from the collection.
187: * @param users The UserEdit objects (with id set) to fill in or remove.
188: */
189: public void getUsers(Collection users) {
190: m_logger.debug("getUsers() size()=" + users.size() + " as "
191: + providerID);
192: if (nextProvider != null) {
193: // We need to be clever and wrap the collection
194: GetUsersCollectionWrapper wrapper = new GetUsersCollectionWrapper(
195: users);
196: if (m_logger.isDebugEnabled()) {
197: m_logger.debug("using wrapper on " + myProvider
198: + " as " + providerID);
199: }
200: myProvider.getUsers(wrapper.firstPassCollection());
201: m_logger
202: .debug("Passing myProvider removals collection to nextProvider size()="
203: + wrapper.secondPassCollection.size()
204: + " as " + providerID);
205: if (m_logger.isDebugEnabled()) {
206: m_logger.debug("using second wrapper on "
207: + nextProvider);
208: }
209:
210: nextProvider.getUsers(wrapper.secondPassCollection());
211: m_logger
212: .debug("Total number of removals from users collection="
213: + wrapper.realRemovals.size()
214: + " as "
215: + providerID);
216: if (m_logger.isDebugEnabled()) {
217: m_logger.debug("Applying Changes");
218: }
219: wrapper.apply();
220:
221: } else {
222: myProvider.getUsers(users);
223: }
224: }
225:
226: private class GetUsersCollectionWrapper {
227: Collection inner;
228:
229: public GetUsersCollectionWrapper(Collection c) {
230: this .inner = c;
231: }
232:
233: ArrayList secondPassCollection = new ArrayList();
234: ArrayList realRemovals = new ArrayList();
235:
236: public Collection firstPassCollection() {
237: return createStoreRemovalsCollection(inner,
238: secondPassCollection);
239: }
240:
241: public Collection secondPassCollection() {
242: return createStoreRemovalsCollection(secondPassCollection,
243: realRemovals);
244: }
245:
246: public Collection createStoreRemovalsCollection(
247: final Collection originals, final Collection removals) {
248: return new Collection() {
249:
250: public boolean add(Object o) {
251: throw new UnsupportedOperationException(
252: "UDP should not add to the collection passed into getUsers()");
253: }
254:
255: public boolean addAll(Collection c) {
256: throw new UnsupportedOperationException(
257: "UDP should not add to the collection passed into getUsers()");
258: }
259:
260: public void clear() {
261: for (Iterator it = originals.iterator(); it
262: .hasNext();) {
263: removals.add(it.next());
264: }
265: }
266:
267: public boolean contains(Object o) {
268: return originals.contains(o)
269: && !removals.contains(o);
270: }
271:
272: public boolean containsAll(Collection col) {
273: for (Iterator it = removals.iterator(); it
274: .hasNext();) {
275: if (col.contains(it.next()))
276: return false;
277: }
278:
279: return originals.containsAll(col);
280: }
281:
282: public boolean isEmpty() {
283: return originals.isEmpty()
284: || removals.containsAll(originals);
285: }
286:
287: public Iterator iterator() {
288: return new Iterator() {
289:
290: Iterator internal = originals.iterator();
291: Object next = internal.hasNext() ? internal
292: .next() : null;
293: Object current;
294:
295: public boolean hasNext() {
296: return (next != null);
297: }
298:
299: public Object next() {
300: current = next;
301: next = null;
302: while (next == null && internal.hasNext()) {
303: next = internal.next();
304: if (removals.contains(next)) {
305: next = null;
306: }
307: }
308: return current;
309: }
310:
311: public void remove() {
312: if (m_logger.isDebugEnabled()) {
313: User u = (User) current;
314: m_logger
315: .debug("Removing object from internal collection :"
316: + u.getEid());
317: }
318: if (originals.contains(current)
319: && !removals.contains(current)) {
320: removals.add(current);
321: }
322: }
323:
324: };
325: }
326:
327: /*
328: * If remove is called for object o
329: */
330: public boolean remove(Object o) {
331: if (m_logger.isDebugEnabled()) {
332: User u = (User) o;
333: m_logger
334: .debug("Removing object from internal collection :"
335: + u.getEid());
336: }
337:
338: if (originals.contains(o) && !removals.contains(o)) {
339: removals.add(o);
340: return true;
341: }
342: return false;
343: }
344:
345: public boolean removeAll(Collection c) {
346: boolean doneSomething = false;
347: for (Iterator it = c.iterator(); it.hasNext();) {
348: Object o = it.next();
349: if (m_logger.isDebugEnabled()) {
350: User u = (User) o;
351: m_logger
352: .debug("Removing object from internal collection :"
353: + u.getEid());
354: }
355:
356: if (originals.contains(o)
357: && !removals.contains(o)) {
358: removals.add(o);
359: doneSomething = true;
360: }
361: }
362: return doneSomething;
363: }
364:
365: public boolean retainAll(Collection c) {
366: boolean doneSomething = false;
367: for (Iterator it = originals.iterator(); it
368: .hasNext();) {
369: Object o = it.next();
370:
371: if (m_logger.isDebugEnabled()) {
372: User u = (User) o;
373: m_logger
374: .debug("Removing object from internal collection :"
375: + u.getEid());
376: }
377:
378: if (!c.contains(o) && !removals.contains(o)) {
379: removals.add(o);
380: doneSomething = true;
381: }
382: }
383: return doneSomething;
384: }
385:
386: public int size() {
387: int i = 0;
388: for (Iterator it = originals.iterator(); it
389: .hasNext();) {
390: if (!removals.contains(it.next())) {
391: i++;
392: }
393: }
394: return i;
395: }
396:
397: public Object[] toArray() {
398: return toList().toArray();
399: }
400:
401: private ArrayList toList() {
402: ArrayList l = new ArrayList();
403: for (Iterator it = originals.iterator(); it
404: .hasNext();) {
405: Object o = it.next();
406: if (!removals.contains(o)) {
407: l.add(o);
408: }
409: }
410: return l;
411: }
412:
413: public Object[] toArray(Object[] a) {
414: return toList().toArray(a);
415: }
416:
417: };
418: }
419:
420: public void apply() {
421: for (Iterator it = inner.iterator(); it.hasNext();) {
422: Object o = it.next();
423: if (m_logger.isDebugEnabled()) {
424: User u = (User) o;
425: m_logger
426: .debug("Actually removing object from collection :"
427: + u.getEid());
428: }
429:
430: if (realRemovals.contains(o)) {
431: it.remove();
432: }
433: }
434:
435: }
436:
437: }
438:
439: /**
440: * Find a user object who has this email address. Update the object with the information found.
441: *
442: * @param email
443: * The email address string.
444: * @return true if the user object was found and information updated, false if not.
445: */
446: public boolean findUserByEmail(UserEdit edit, String email) {
447: if (m_logger.isDebugEnabled()) {
448: m_logger.debug("findUserByEmail() edit.getId()="
449: + edit.getId() + " email=" + email + " as "
450: + providerID);
451: }
452:
453: if (myProvider.findUserByEmail(edit, email)) {
454: return true;
455: } else if (nextProvider != null) {
456: return nextProvider.findUserByEmail(edit, email);
457: }
458: return false;
459:
460: } // findUserByEmail
461:
462: /**
463: * Find all user objects which have this email address.
464: *
465: * @param email
466: * The email address string.
467: * @param factory
468: * Use this factory's newUser() method to create all the UserEdit objects you populate and return in the return collection.
469: * @return Collection (UserEdit) of user objects that have this email address, or an empty Collection if there are none.
470: */
471: public Collection findUsersByEmail(String email, UserFactory factory) {
472: Collection rv = new Vector();
473: if (myProvider instanceof UsersShareEmailUDP) {
474: UsersShareEmailUDP ushare = (UsersShareEmailUDP) myProvider;
475: m_logger.debug("myProvider Multiple lookup on " + email
476: + " for " + ushare + " as " + providerID);
477: rv.addAll(ushare.findUsersByEmail(email, factory));
478: m_logger.debug("myProvider - got " + rv.size() + " matches"
479: + " as " + providerID);
480: } else {
481: UserEdit edit = factory.newUser();
482: if (myProvider.findUserByEmail(edit, email)) {
483: m_logger.debug("myProvider - found user "
484: + edit.getId() + " for " + email + " as "
485: + providerID);
486: rv.add(edit);
487: }
488: }
489:
490: if (nextProvider instanceof UsersShareEmailUDP) {
491: UsersShareEmailUDP ushare = (UsersShareEmailUDP) nextProvider;
492: m_logger.debug("nextProvider Multiple lookup on " + email
493: + " for " + ushare + " as " + providerID);
494: rv.addAll(ushare.findUsersByEmail(email, factory));
495: m_logger.debug("nextProvider - got " + rv.size()
496: + " matches" + " as " + providerID);
497: } else if (nextProvider != null) {
498: UserEdit edit = factory.newUser();
499: if (myProvider.findUserByEmail(edit, email)) {
500: m_logger.debug("nextProvider - found user "
501: + edit.getId() + " for " + email + " as "
502: + providerID);
503: rv.add(edit);
504: }
505: }
506:
507: return rv;
508: }
509:
510: /**
511: * Authenticate a user / password. If the user edit exists it may be modified, and will be stored if...
512: *
513: * @param id
514: * The user id.
515: * @param edit
516: * The UserEdit matching the id to be authenticated (and updated) if we have one.
517: * @param password
518: * The password.
519: * @return true if authenticated, false if not.
520: */
521: public boolean authenticateUser(String userId, UserEdit edit,
522: String password) {
523: if (m_logger.isDebugEnabled()) {
524: m_logger.debug("authenticateUser() userId=" + userId
525: + " as " + providerID);
526: }
527:
528: authenticatedProvider.set(null);
529: if (myProvider.authenticateUser(userId, edit, password)) {
530: authenticatedProvider.set(providerID);
531: return true;
532: } else if (nextProvider != null) {
533: return nextProvider
534: .authenticateUser(userId, edit, password);
535: }
536: return false;
537:
538: } // authenticateUser
539:
540: /**
541: * Will this provider update user records on successfull authentication? If so, the UserDirectoryService will cause these updates to be stored.
542: *
543: * @return true if the user record may be updated after successfull authentication, false if not.
544: */
545: public boolean updateUserAfterAuthentication() {
546: m_logger.debug("updateUserAfterAuthnetication() as "
547: + providerID);
548:
549: // This is called directly after authenticateUser, so we should be able to
550: // register which used we are talking about, provided none of the underlying
551: // directroy providers calls this. It is also only called once, so we must remove
552: // the thread local flag once the call has completed.
553: if (providerID.equals(authenticatedProvider.get())) {
554: authenticatedProvider.set(null);
555: return myProvider.updateUserAfterAuthentication();
556: } else if (nextProvider != null) {
557: return nextProvider.updateUserAfterAuthentication();
558: }
559: return false;
560: }
561:
562: /**
563: *
564: * {@inheritDoc}
565: */
566: public boolean createUserRecord(String id) {
567: if (myProvider.createUserRecord(id)) {
568: return true;
569: } else if (nextProvider != null) {
570: return nextProvider.createUserRecord(id);
571: }
572: return false;
573: }
574:
575: /**
576: * {@inheritDoc}
577: */
578: public void destroyAuthentication() {
579: m_logger.warn("destroyAuthentciation() as " + providerID);
580:
581: // the provider must only destroy authentications for their own provider
582: // source.
583: myProvider.destroyAuthentication();
584: if (nextProvider != null) {
585: nextProvider.destroyAuthentication();
586: }
587: }
588:
589: /**
590: * The UserDirectoryProvider used by this filter
591: * @return
592: */
593: public UserDirectoryProvider getMyProvider() {
594: return myProvider;
595: }
596:
597: /**
598: * The UserDirectoryProvider used by this filter
599: * @param myProvider
600: */
601: public void setMyProvider(UserDirectoryProvider myProvider) {
602: this .myProvider = myProvider;
603: }
604:
605: /**
606: * The Next Directory Provider in the chain
607: * @return
608: */
609: public UserDirectoryProvider getNextProvider() {
610: return nextProvider;
611: }
612:
613: /**
614: * The Next Directory Provider in the chain
615: * @param nextDirectoryProvider
616: */
617: public void setNextProvider(
618: UserDirectoryProvider nextDirectoryProvider) {
619: this .nextProvider = nextDirectoryProvider;
620: }
621:
622: /**
623: * {@inheritDoc}
624: */
625: public boolean authenticateWithProviderFirst(String id) {
626: return false;
627: }
628:
629: } // FilterUserDirectoryProvider
|