001: /**
002: * Copyright 2002 Sun Microsystems, Inc. All
003: * rights reserved. Use of this product is subject
004: * to license terms. Federal Acquisitions:
005: * Commercial Software -- Government Users
006: * Subject to Standard License Terms and
007: * Conditions.
008: *
009: * Sun, Sun Microsystems, the Sun logo, and iPlanet
010: * are trademarks or registered trademarks of Sun Microsystems,
011: * Inc. in the United States and other countries.
012: */package com.sun.ssoadapter;
013:
014: import java.util.*;
015: import java.net.*;
016: import java.lang.reflect.*;
017: import javax.servlet.http.*;
018: import javax.mail.*;
019:
020: import com.iplanet.sso.*;
021:
022: import com.sun.ssoadapter.config.Configuration;
023: import com.sun.ssoadapter.config.ConfigurationEvent;
024: import com.sun.ssoadapter.config.ConfigurationException;
025: import com.sun.ssoadapter.config.ConfigurationFactory;
026: import com.sun.ssoadapter.config.ConfigurationListener;
027:
028: import com.sun.ssoadapter.SSOAdapterLogger;
029: import java.util.logging.Logger;
030: import java.util.logging.Level;
031:
032: /**
033: * This is a simplified framework for creating Connections to different Backend
034: * It has Java APIs to store/retrieve connetion related configurations and create Connections to different backend.
035: * This class is a Java interface to create and cache instances of SSOAdapter/Connection objects.
036: * These APIs depends on AM Session and Service. This requires the AM SDK APIs to be available with in the webapp.
037: *
038: * <p>An SSOAdapterFactory is associated with the following:
039: * <ul>
040: * <li>An AM service: SSOAdapterConstant.SERVICE_NAME . This AM service has </li>
041: * <li>ATTR_TEMPLATES_NAME as a Global Attribute: known as SSOAdapterTemplate </li>
042: * <li>ATTR_CONFIGURATIONS_NAME as a dynamic Attribue: known as SSOAdapterConfiguration </li>
043: * <li>ATTR_CONFIGURATIONS_NAME as a User Attribute </li>
044: * </ul>
045: * <p><pre><CODE>
046: * These attributes in the above service stores the configuration information, which are stored as multiple URLs.
047: * An Example of sunConfigurationTemplates as a Global Attribute
048: * <Attribute name="sunConfigurationTemplates"/>
049: * <Value>default|undef:///?configName=GENERIC_TEMPLATE&encoded=password&default=protocol&....</Value>
050: * <Value>default|imap:///?configName=SUN-ONE-MAIL&encoded=password&default=protocol&....</Value>
051: * <Value>default|http:///?configName=SUN-ONE-CALENDAR&encoded=password&default=protocol&.....</Value>
052: * .....
053: * An Example of SSOAdapterConfiguration as a Dynamic Attribute.
054: * <Attribute name="sunSSOAdapterConfigurations"/>
055: * <Value>default|imap:///?configName=sunOneMail&configDesc=SUN-ONE-MAIL&port=143&smtpPort=25</Value>
056: * <Value>default|http:///?configName=sunOneCalendar&configDesc=SUN-ONE-CALENDAR</Value>
057: * ......
058: *
059: * An Example of SSOAdapterConfiguration as the User attribue.
060: * sunSSOAdapterConfigurations: default|undef://b:AQIC%3D@plato.red.iplanet.com:143/?configName=sunOneMail_Mail&configDesc=SUN-ONE-MAIL....
061: * sunSSOAdapterConfigurations: default|undef://b:AQIC%3D@plato.red.iplanet.com:82/?configName=sunOneCalendar_Calendar&configDesc=SUN-ONE-CALENDAR....
062: *
063: * </CODE></pre>
064: * </p>
065: *
066: * The SSOAdapterFactory has the mechanisms to obtain an instance of SSOAdapter when provided with a ConfigName and SSOAdapterSession.
067: *
068: * PreRequiste:
069: * <ul>
070: * <li>Write your own Java Class which extends SSOAdapter and implements all the required methods </li>
071: * <li>Create a SSOAdapterTemplate, Either by AdminConsole or Using amadmin/psadmin Commands and provide the above derived java className as "ssoClassName" </li>
072: * <li>Create a SSOAdapterConfiguration, Either by AdminConsole or using amadmin/psadmin Command </li>
073: * <li>The AM SDK's jar files are available inside webapp. (as it is placed in the webContainer classpath)</li>
074: * <li>This ssoadapter.jar is also available inside the webapp (as it is placed in the webContainer classpath) </li>
075: * </ul>
076: *
077: * <p>
078: * <pre>Usage: It can be used Inside a Portlet/Provider
079: * /To Get
080: * HttpServletRequest httpReq = (HttpServletRequest)request.getAttribute("javax.portlet.portletc.httpServletRequest");
081: * SSOAdapterSession = new SSOAdapterSession(httpReq);
082: * SSOAdapterFactory factory = SSOAdapterFactory.getInstance();
083: * PortletPreferences pref = request.getPreferences();
084: * String ssoAdapterName = pref.getValue("ssoAdapter","sunOneMail");
085: * SSOAdapter ssoAdapter = factory.getSSOAdapter(ssoAdapterName , session );
086: * Properties props = ssoAdapter.getProperties();
087: * Object obj = newSSOAdapter.getConnection();
088: * if ( object != null ) {
089: * //Cast it to your known type and invoke the APIs on it.
090: * }
091: *
092: * //To Edit & Save
093: * props.setProperty("channelName", "MyPersonalMailChannel");
094: * SSOAdapter newSSOAdapter = factory.setSSOAdapter(ssoAdapter, props , session );
095: *
096: *
097: * </pre>
098: * </p>
099: * @version 1.0
100: * @see com.sun.ssoadapter.SSOAdapter
101: * @see com.sun.ssoadapter.SSOAdapterException
102: * @see com.sun.ssoadapter.SSOAdapterConstants
103: */
104: //TBD: Implement ConfigurationListener in future when required.
105: public class SSOAdapterFactory implements SSOAdapterConstants {
106:
107: /**
108: *SSO Adapter Service Name
109: */
110: public static final String SERVICE_NAME = "SunSSOAdapterService";
111:
112: /**
113: * SSO Adapter Attribute for Templates
114: */
115: public static final String ATTR_TEMPLATES_NAME = "sunConfigurationTemplates";
116:
117: /**
118: * SSO Adapter Attribute for Configurations
119: */
120: public static final String ATTR_CONFIGURATIONS_NAME = "sunSSOAdapterConfigurations";
121:
122: /**
123: * Reference to SSOAdapterFactory instance
124: */
125: protected static SSOAdapterFactory ssoAdapterInstance = new SSOAdapterFactory();
126:
127: /**
128: * The ConfigurationFactory bound to this SSOAdapterFactory.
129: */
130: private ConfigurationFactory configurationFactory = null;
131:
132: /**
133: * SSOAdapters and configurations are tracked/cached on a
134: * per user basis. Keys typically include SSOTokenId string values.
135: */
136: private Hashtable adapterHashtable = new Hashtable(100);
137:
138: /**
139: * Debug class name
140: */
141: private final String this Class = this .getClass().getName();
142:
143: /**
144: * authless authentication session uid
145: */
146: private static final String authlessSUID = "ssoadapter.authless.suid";
147:
148: /**
149: * Generic SSOAdapter implementation class
150: */
151: public static final String GENERIC_SSOADAPTER_CLASS = "com.sun.ssoadapter.GenericSSOAdapter";
152:
153: /**
154: * The Logger
155: */
156: private static Logger logger = SSOAdapterLogger
157: .getLogger("com.sun.portal.ssoadapter");
158:
159: /**
160: * Private Default Constructor.
161: */
162: private SSOAdapterFactory() {
163:
164: this .configurationFactory = ConfigurationFactory.getInstance(
165: SERVICE_NAME, ATTR_CONFIGURATIONS_NAME,
166: ATTR_TEMPLATES_NAME);
167: configurationFactory.setMergeDynamicConfigurations(true);
168:
169: }
170:
171: /**
172: * Returns an instance of SSOAdapterFactory.
173: *
174: * @return SSOAdapterFactory instance
175: */
176: public static SSOAdapterFactory getInstance() {
177: return ssoAdapterInstance;
178: }
179:
180: /**
181: * Returns an SSOAdapter object of the appropriate configuration and of the specified
182: * instanceName.
183: * <p>If "configName" is not specified, then the first configuration found
184: * will be used.
185: * <p>The "instanceName" parameter provides a means of selectively
186: * sharing (or not), a particular instance of an SSOAdapter amongst
187: * cooperating sections of code. If two sections of code specify the
188: * same instanceName, then they will receive the same SSOAdapter instance.
189: * If they each specify different names, each will receive an instance
190: * of an SSOAdapter unique to that instanceName.
191: * <p>Once an SSOAdapter is instantiated, it is stored in a cache within
192: * this SSOAdapterFactory. All future invocations of this method
193: * requesting an SSOAdapter of this particular configuration will be retrieved
194: * from the cache. The SSOAdapter will be "uninitialized" and removed
195: * from the cache when the user's session is terminated.
196: * @return ssoAdapter: an instance of SSOAdapter
197: * @param session a SSOAdapterSession
198: * @param configName The name of the configuration to which an SSOAdapter
199: * should be bound.
200: * @throws com.sun.ssoadapter.SSOAdapterException incase of failure
201: */
202: public SSOAdapter getSSOAdapter(String configName,
203: SSOAdapterSession session) throws SSOAdapterException {
204:
205: //
206: // Fail if session not specified...
207: //
208: if (session == null) {
209: logger.log(Level.SEVERE, "PSSA_CSS0001");
210: String msg = this .getClass().getName()
211: + ".getSSOAdapter(): "
212: + "requires session be set.";
213: throw new SSOAdapterException(msg);
214: }
215:
216: //
217: // If configName not specified, try using first in list...
218: //
219: if (configName == null || configName.length() == 0) {
220: Enumeration nameEnum = configurationFactory
221: .getConfigurationNames(session);
222: if (nameEnum == null || !nameEnum.hasMoreElements()) {
223: logger.log(Level.SEVERE, "PSSA_CSS0002");
224: String msg = this Class + "getSSOAdapter(): "
225: + "could not determine configName.";
226: throw new SSOAdapterException(msg);
227: }
228: String tmpConfigName = (String) nameEnum.nextElement();
229:
230: if (logger.isLoggable(Level.WARNING)) {
231: String[] param = new String[2];
232: param[0] = configName;
233: param[1] = tmpConfigName;
234: logger.log(Level.WARNING, "PSSA_CSS0008", param);
235: }
236:
237: configName = tmpConfigName;
238: }
239:
240: SSOAdapter ssoAdapter = null;
241: String tokenString = session.getSessionID();
242:
243: //
244: // The Session is used for IS connectivity and object caching. If the
245: // Session is not found, then try to use the 'authlessUid' to retrieve
246: // attributes for an authless authentication connection. The session
247: // is set with the 'authlessUid' for use later.
248: //
249: if (!session.isSessionValid()) {
250: // session not found valid
251: if (session.getAuthlessUID() != null) {
252: tokenString = session.getAuthlessUID();
253: //session.setAttribute(authlessSUID, tokenString);
254: } else {
255: logger.log(Level.SEVERE, "PSSA_CSS0003", configName);
256: String msg = this Class
257: + ".getSSOAdapter() failed. Reason: "
258: + " Session Invalid for sso adapter "
259: + configName;
260: throw new SSOAdapterException(msg);
261: }
262: }
263:
264: try {
265:
266: //
267: // See if SSOAdapter already loaded...
268: // ... and has valid Configuration.
269: //
270: String hashKey = tokenString + configName;
271:
272: if (session.getInstanceName() != null) {
273: hashKey += session.getInstanceName();
274: }
275:
276: ssoAdapter = (SSOAdapter) adapterHashtable.get(hashKey);
277:
278: if (ssoAdapter != null
279: && ssoAdapter.getProperties() != null) {
280: return ssoAdapter;
281: }
282:
283: //
284: // If not..., find its configuration...
285: //
286: Configuration config = configurationFactory
287: .readConfiguration(configName, true, session);
288:
289: if (config == null) {
290: String msg = this Class + ".getSSOAdapter(): "
291: + "could not find sso adapter \"" + configName
292: + "\"";
293: throw new SSOAdapterException(msg);
294: }
295:
296: //
297: // create SSO Adapter
298: //
299: ssoAdapter = createSSOAdapter(config, session);
300:
301: //
302: // Save reference in cache...
303: //
304: adapterHashtable.put(hashKey, ssoAdapter);
305:
306: //
307: // Register the SSOAdapter as a SSOToken listener...
308: // TODO
309: //
310: if (session.isSessionValid()) {
311: session.addSSOTokenListener(ssoAdapter);
312: }
313:
314: //
315: // Register the SSOAdapterFactory as a Configuration listener...
316: // TODO
317: //
318: //config.addListener(ssoAdapter);
319:
320: //
321: // Register cache reaper for this SSOAdapter...
322: //
323: HashtableReaper reaper = new HashtableReaper(hashKey,
324: adapterHashtable);
325:
326: if (session.isSessionValid()) {
327: session.addSSOTokenListener(reaper);
328: }
329:
330: } catch (SSOAdapterException ssoe) {
331: if (ssoe.isInvalid()) {
332: throw ssoe;
333: } else {
334: String msg = this .getClass().getName()
335: + ".getSSOAdapter() failed. Reason: "
336: + ssoe.toString();
337: logger.log(Level.SEVERE, "PSSA_CSS0005", ssoe);
338: throw new SSOAdapterException(msg);
339: }
340: } catch (Exception e) {
341: logger.log(Level.SEVERE, "PSSA_CSS0005", e);
342: String msg = this .getClass().getName()
343: + ".getSSOAdapter() failed. Reason: "
344: + e.toString();
345: throw new SSOAdapterException(msg);
346: }
347:
348: return ssoAdapter;
349: }
350:
351: /**
352: * This method either generates a new SSOAdapter if one does not exist or updates
353: * the existing SSOAdapter. The SSOAdapter is referenced by the Channel/Provider's
354: * 'ssoAdapter' display profile property which is used to match the Service Configurations
355: * 'configName'
356: * @return an instance of SSOAdapter
357: * @param session a SSOAdapterSession
358: * @param immutableAdapter The immutable SSOAdapter object to update
359: * @param newProperties SSOAdapter Properties to update
360: * @throws com.sun.ssoadapter.SSOAdapterException incase of failure
361: */
362: public SSOAdapter setSSOAdapter(SSOAdapter immutableAdapter,
363: Properties newProperties, SSOAdapterSession session)
364: throws SSOAdapterException {
365:
366: SSOAdapter returnAdapter = null;
367: Configuration config = null;
368: Configuration configOrig = null;
369: String configName = null;
370: String channelName = null;
371: String hashKey = "";
372:
373: if (immutableAdapter == null) {
374: return returnAdapter;
375: }
376:
377: // If the immutable SSOAdapter is not null generate a copy of the adapter's
378: // Configuration. The copy is necessary to avoid updating the ConfigurationFactory
379: // mergedConfigCache. Update the Configuration copy with newProperties and set
380: // unique configuration name based on appending the channel name.
381: //
382: // Then instantiate the SSOAdapter based on the manipulated Configuration.
383: // If the SSOAdapter fails to instantiate, probably because the validate() errors,
384: // do not write the Configuration, otherwise write the configuration and return
385: // the new instantiated SSOAdapter
386: //
387: configName = immutableAdapter.getName();
388: channelName = newProperties.getProperty("channelName");
389:
390: try {
391: // get original and create copy
392: configOrig = configurationFactory.readConfiguration(
393: configName, true, session);
394:
395: if (configOrig == null) {
396: logger.log(Level.SEVERE, "PSSA_CSS0004", configName);
397: String msg = this Class + ".getSSOAdapter(): "
398: + "could not find sso adapter \"" + configName
399: + "\"";
400: throw new SSOAdapterException(msg);
401: }
402:
403: config = new Configuration(configOrig.getConfigurationURL());
404:
405: SSOTokenManager tokenManager = SSOTokenManager
406: .getInstance();
407: String tokenString = session.getSessionID();
408: hashKey = tokenString;
409:
410: } catch (ConfigurationException ce) {
411: String msg = this Class
412: + ".getSSOAdapter() failed. Reason: "
413: + ce.toString();
414: logger.log(Level.SEVERE, "PSSA_CSS0005", ce);
415: throw new SSOAdapterException(msg);
416: } catch (SSOException ssoe) {
417: logger.log(Level.WARNING, "PSSA_CSS0006", ssoe);
418: }
419:
420: if (config != null) {
421: Enumeration propsEnum = newProperties.propertyNames();
422:
423: // set new configuration properties
424: while (propsEnum.hasMoreElements()) {
425: String key = (String) propsEnum.nextElement();
426: config.setProperty(key, (String) newProperties
427: .getProperty(key));
428: }
429:
430: // construct unique name for user configuration
431: if (channelName != null) {
432:
433: if (!configName.endsWith("_" + channelName)) {
434: String uniqueName = configName + "_" + channelName;
435:
436: if (!configName.equals(uniqueName)) {
437: configName = uniqueName;
438: config.setConfigurationName(configName);
439: }
440: }
441: }
442:
443: try {
444: // instantiate adapter to test for validation
445: returnAdapter = createSSOAdapter(config, session);
446:
447: // write configuration
448: configurationFactory
449: .writeConfiguration(config, session);
450:
451: // remove cached adpater
452: hashKey += configName;
453:
454: if (session.getInstanceName() != null) {
455: hashKey += session.getInstanceName();
456: }
457:
458: if (adapterHashtable.containsKey(hashKey)) {
459: adapterHashtable.remove(hashKey);
460: }
461: } catch (ConfigurationException ce) {
462: String msg = this Class
463: + ".getSSOAdapter() failed. Reason: "
464: + ce.toString();
465: logger.log(Level.SEVERE, "PSSA_CSS0005", ce);
466: throw new SSOAdapterException(msg);
467: } catch (SSOAdapterException ssoe) {
468: if (ssoe.isInvalid()) {
469: throw ssoe;
470: } else {
471: logger.log(Level.SEVERE, "PSSA_CSS0005", ssoe);
472: String msg = this Class
473: + ".getSSOAdapter() failed. Reason: "
474: + ssoe.toString();
475: throw new SSOAdapterException(msg);
476: }
477: }
478:
479: }
480:
481: return returnAdapter;
482: }
483:
484: /**
485: * Helper method to instantiate the SSOAdapter based on the Configuration's
486: * SSOAdapter Class Name. The SSOAdapter is created with the Configuration
487: * name, SSOToken, and Configuration Properties.
488: * @return an SSOAdapter based on the Configuration
489: * @param session a SSOAdapterSession
490: * @param config The Configuration
491: * @throws SSOAdapterException In case it is not able to create
492: */
493: private SSOAdapter createSSOAdapter(Configuration config,
494: SSOAdapterSession session) throws SSOAdapterException {
495:
496: SSOAdapter ssoAdapter = null;
497: String configName = config.getConfigurationName();
498: String className = config.getProperty(PROP_SSO_CLASS_NAME_NAME);
499:
500: if (className == null || (className.trim().length() == 0)) {
501: // use generic ssoadapter implementation if class is not specified
502: className = GENERIC_SSOADAPTER_CLASS;
503:
504: if (logger.isLoggable(Level.INFO)) {
505: logger.log(Level.INFO, "PSSA_CSS0007", configName);
506: }
507: }
508:
509: if (logger.isLoggable(Level.FINEST)) {
510: String[] param = new String[2];
511: param[0] = configName;
512: param[1] = className;
513: logger.log(Level.FINEST, "PSSA_CSS0011", param);
514: }
515:
516: try {
517: //
518: // Create an instance of the correct SSOAdapter class...
519: //
520: Class ssoAdapterClass = Thread.currentThread()
521: .getContextClassLoader().loadClass(className);
522: Class[] argConst = {};
523: Constructor ssoAdapterConst = ssoAdapterClass
524: .getConstructor(argConst);
525: Object[] argConstObj = {};
526: ssoAdapter = (SSOAdapter) ssoAdapterConst
527: .newInstance(argConstObj);
528:
529: //
530: // Initialize the new SSOAdapter...
531: //
532: //ssoAdapter.locale = locale;
533: //ssoAdapter.userPropertyList = config.getUserPropertiesList();
534: //ssoAdapter.encodedPropertyList = config.getEncodedPropertiesList();
535: ssoAdapter.init(configName, session.getSSOToken(), config
536: .getProperties(), config.getUserPropertiesList(),
537: config.getEncodedPropertiesList(), session
538: .getLocale());
539:
540: } catch (SSOAdapterException ssoe) {
541: logger.log(Level.SEVERE, "PSSA_CSS0012", ssoe);
542: throw ssoe;
543: } catch (Exception e) {
544: logger.log(Level.SEVERE, "PSSA_CSS0012", e);
545: throw new SSOAdapterException("Could not create SSOAdapter");
546: }
547:
548: return ssoAdapter;
549: }
550:
551: /**
552: * Gets all the Configurations defined for the session
553: * @param session a SSOAdapterSession
554: * @return an enumeration of all the configs
555: */
556: public Enumeration getConfigurationNames(SSOAdapterSession session) {
557: return configurationFactory.getConfigurationNames(session);
558: }
559:
560: /**
561: * remove the configuration for the user
562: * @param userConfigName an userConfiguration
563: * @param session a SSOAdapterSession
564: * @throws com.sun.ssoadapter.SSOAdapterException an Exception incaseof failure
565: */
566: public void removeConfiguration(String userConfigName,
567: SSOAdapterSession session) throws SSOAdapterException {
568: try {
569: configurationFactory.removeConfiguration(userConfigName,
570: session);
571: String tokenString = session.getSessionID();
572: String hashKey = tokenString + userConfigName;
573: if (session.getInstanceName() != null) {
574: hashKey += session.getInstanceName();
575: }
576: adapterHashtable.remove(hashKey);
577:
578: } catch (ConfigurationException configEx) {
579: throw new SSOAdapterException(configEx.getMessage(),
580: configEx, false);
581: }
582:
583: }
584:
585: /*
586: * When Configuration has changed the SSOAdapterFactory is notified
587: * and it flushes the SSOAdapter objects that are using that Configuration object.
588: * Users of the SSOAdapter will pick up the change the next time they call
589: * SSOAdapterFactory.getInstance()
590: *
591: * @see com.sun.ssoadapter.config.ConfigurationListener "configurationChanged" method.
592: * @see com.sun.ssoadapter.config.ConfigurationEvent
593: */
594: /**
595: *
596: * @param evt
597: * @return
598: */
599: private boolean configurationChanged(ConfigurationEvent evt) {
600:
601: // TODO: process ConfigurationEvent and remove SSOAdapter objects from hash
602: return true;
603:
604: }
605:
606: /*
607: * This class is used to purge the caches of per-session entries.
608: */
609: /**
610: * The Session Listener HashtableReaper
611: */
612: private class HashtableReaper implements SSOTokenListener {
613:
614: /**
615: * key in the hashtable
616: */
617: private String key;
618: /**
619: * The hashtable which contains all the Configs
620: */
621: private Hashtable hashTable;
622:
623: /**
624: * Constructor
625: * @param key
626: * @param hashTable
627: */
628: HashtableReaper(String key, Hashtable hashTable) {
629: this .key = key;
630: this .hashTable = hashTable;
631: }
632:
633: /**
634: * Implements SSOTokenListener "ssoTokenChanged" method.
635: * @param evt Invoked by AM.
636: */
637: public void ssoTokenChanged(com.iplanet.sso.SSOTokenEvent evt) {
638: try {
639: int evtType = evt.getType();
640:
641: if (evtType != evt.SSO_TOKEN_DESTROY
642: && evtType != evt.SSO_TOKEN_IDLE_TIMEOUT
643: && evtType != evt.SSO_TOKEN_MAX_TIMEOUT) {
644: return;
645: }
646:
647: hashTable.remove(key);
648:
649: if (logger.isLoggable(Level.FINEST)) {
650: logger.log(Level.FINEST, "PSSA_CSS0009",
651: new String[] { key });
652: }
653: } catch (Exception e) {
654: logger.log(Level.WARNING, "PSSA_CSS0010", e);
655: }
656: }
657:
658: }
659:
660: }
|