This section introduces a security pattern that synchronizes authentication and authorization credentials across different Provisioning Service Targets (application systems). Many application systems have their own user authentication and access control mechanisms. They may not share common user credentials. Users may have to use different user passwords for different application systems. Thus, it would be useful to synchronize all user credentials specific to application systems. Password Synchronizer PatternProblemYou want to synchronize the user passwords (or user credentials used for authentication and authorization) across different application systems using a programmatic interface. In a heterogeneous application security environment, different application systems use different user account management mechanisms. If security administrators need to modify the user account management policies or to reset user passwords for all application systems to which a user is entitled, it requires considerable administrative effort. This applies to user credentials (such as certificates, smart card tokens or even biometrics samples) used for authentication and authorization as well. As discussed earlier, the administrative effort to reset user passwords is expensive. In earlier days, some security administrators built a proprietary programmatic interface with a text file to store the user passwords and the application system identifier. However, this is not easily scalable and maintainable for a system with a large number of user accounts and growing application systems. Additionally, it is highly insecure to store sensitive user account information in text files. If all applications are home-grown, security architects and developers may want to standardize all user account management mechanisms and centralize the user account policy system. By standardizing and centralizing the user account policy, architects and developers can easily manage provisioning user accountsthey will not have a password synchronization issue. In reality, many organizations have a mix of home-grown applications, off-the-shelf packages, and legacy systems. Thus, this user account centralization approach would not be sufficient to process user account provisioning or password synchronization in the legacy systems without heavy customization. Single sign-on security allows a user to sign on once and access application systems across a diverse security infrastructure using unique security assertions. However, this does not provide any mechanism to provision user accounts or to synchronize user passwords. If security administrators need to reset the user passwords for a user, they need to automate resetting user passwords in a programmatic manner instead of using a manual user password reset. Forces
SolutionUse a Password Synchronizer to centralize management of synchronizing user credentials (including user passwords) across different application systems via programmatic interfaces. A Password Synchronizer is a convenient control center for synchronizing user account services across multiple application systems. It acts like a hub that issues user account password service commands (including password setting, password resetting, and synchronizing the user passwords) to all application systems that are connected to it. Each of the application systems receives the user account password service request, verifies the authenticity, and processes the request. If the request is successful, the application system will respond with a positive return code. Otherwise, it will return a response with the details of any unsuccessful condition or failure reason. A Password Synchronizer can manage user credential (such as user account password) activities in a programmatic manner. All user account password service requests are logged and tracked. If the target application system is unavailable, the Password Synchronizer can reissue the service request. A Password Synchronizer is extremely important when there are a large number of target application systems and administrators need to synchronize the user account passwords within a short time window. Operational efficiency and accuracy are keys to success. To provide a flexible user account password service, architects and developers may need a number of logical architecture components, as discussed earlier in this chapter. Figure 13-8 shows a simple adaptation of the logical components as they appear in the Password Synchronizer. These logical components are depicted as shown in the figure. Figure 13-8. Password Synchronizer components
For a large-scale deployment environment with a high volume of user account service requests, architects and developers would probably require the Password Synchronizer Manager to handle a large number of requests simultaneously using multithread processing. Typically, asynchronous messaging would be a good design approach. User account password service requests can be placed in a queue, where the Password Synchronizer Manager can create multiple threads to process these requests simultaneously. Because each target application system may be running a different application protocol, the Password Synchronizer Manager must be flexible enough to handle multiple protocols by shielding the client tier from the underlying protocol. Doing this may require the use of connectors or adapters that can transform different underlying protocols. StructureFigure 13-9 shows a class diagram for the Password Synchronizer pattern. The core Password Synchronizer service consists of three important classes: PasswordSyncManager, ServiceConfig, PasswordSyncListener and PasswordSyncLedger. Figure 13-9. Password Synchronizer class diagramThe PasswordSyncManager class is the main process engine that handles user account password requests. The user account password request is created by the class PasswordSyncRequest, which loads the user profile (for example, user name, password) via the class ProvisioningUserProfile. The PasswordSyncManager creates a secure session, connects to each provisioning service target, and issues the relevant user account password request. The ServiceConfig class loads the PSTID mapping file, which stores a list of the provisioning service targets and the underlying application protocol (in a context object called ServiceConfigContext for each provisioning target system) used to process the user account password service requests, such as RMI-IIOP and SOAP/HTTP. This avoids tightly coupling the data transport layer processing logic with the application processing logic in the program codes. The PSTID Mapping file defines the mapping between the unique provisioning service target ID and the user IDs in each application system. Flexibility is increased by using a unique provisioning service target ID, which may be an arbitrary, system-generated reference number that references other user IDs. Using a unique provisioning service target ID allows user IDs to be added or removed from the mapping table without those actions impacting other systems or the application infrastructure. If architects and developers use any of the existing user IDs to map to other user IDs, any change to the user IDs will break the referential integrity (or mapping relationship). In that case, the PasswordSyncManager will not be able to complete the user account password service requests. The PasswordSyncListener class resembles the target provisioning system that receives and processes the user account service request. The PasswordSyncLedger class denotes the system entity that checks whether all user account service requests have been completed. Participants and ResponsibilitiesFigure 13-10 depicts a use case scenario for synchronizing user account passwords across multiple provisioning service targets. The ProvisioningServicePoint denotes an administrative client that initiates a password synchronization request. The PasswordSyncManager is the core engine that issues user account password service requests to all provisioning service targets. The ServiceConfig is an internal table that indicates the underlying application protocol. The PasswordSyncManager writes unsuccessful service requests to a ledger, for example, when the provisioning service targets are offline and unavailable to process the service request. The Ledger records the outstanding service requests that need to be reprocessed, for example, after the provisioning service targets resume operation. The following sequence describes the password synchronization process:
Figure 13-10. Password Synchronizer sequence diagramFigure 13-11 shows how the Password Synchronizer pattern can reissue or reprocess the user account password service requests until they are successfully completed. This is useful if architects and developers require the reliability and resilience of handling provisioning requests. The capability of reprocessing service requests is essential for ensuring that all user passwords are synchronized, even if some of the target systems are offline. It is also important that the Password Synchronizer have the capability to roll back to the original user account password after any unsuccessful password synchronization operation. The following sequence shows the reprocessing capability of synchronizing user account passwords. Figure 13-11. Reprocessing user account password requests after target system resumes operation
StrategiesA Password Synchronizer pattern provides a consistent and structured way to handle service provisioning functions and a flexible way to handle multiple protocol bindings. The following are scenarios discussing important design strategies for use with the Password Synchronizer pattern.
ConsequencesBy employing the Password Synchronizer pattern, developers can benefit in the following ways:
Sample CodeThis section introduces sample program code for creating a Password Synchronizer to initiate user account password requests. The Password Synchronizer consists of two key components: PasswordSyncManager (administrative client that initiates a number of user password synchronization requests to the provisioning service targets) and PasswordSyncLedger (a manager component that monitors the status of the service provisioning requests from a predefined JMS topic). Each of the service provisioning requests is intercepted and processed by PasswordSyncListener, which resides in each provisioning service target. JMS is used because it provides a reliable message delivery mechanism and allows better scalability with multiple listeners processing the requests simultaneously. PasswordSyncManager resembles a slight adaptation of the PasswordSyncManager in the Password Synchronizer Pattern section earlier in this chapter. Similarly, PasswordSyncLedger takes the role of Ledger. pstidMapping.xml is an adaptation of PSTIDMapping and is used by the methods in the class ServiceConfig. Figure 13-12 shows the logical architecture for the sample program codes. In Step 1, PasswordSyncManager reads from the provisioning service target mapping table pstidMapping.xml and publishes user password synchronization requests to different JMS topics. It renders the service provisioning request in SPML message format if the provisioning service target supports SOAP, according to the service configuration information defined in the mapping table using the methods defined in the class ServiceConfig. Otherwise, it generates a delimited text. PasswordSyncManager uses the utility PasswordSyncRequest to transform the SOAP message (or the delimited text) to an object and writes to the JMS topic name. Currently, the JMS topic name uses the application resource name of the provisioning service target. Figure 13-12. Sample code logical architectureIn Step 2, each provisioning service target uses a JMS listener, PasswordSyncListener. PasswordSyncListener intercepts any JMS objects published to the associated JMS topic name. Upon receipt, the listener processes the service provisioning requests in the takeAction method and notifies the PasswordSyncLedger of successfully synchronized requests. In Step 3, PasswordSyncLedger is a Password Synchronization Manager ledger process that listens to a predefined JMS topic (such as PASSWORDSYNC_PROVIDER_OK_LIST). It keeps track of the original list of provisioning service targets (from the mapping table pstidMapping.xml). If all passwords are synchronized, then PasswordSyncLedger displays a message stating the completion of the user password synchronization requests. The core component of the Password Synchronizer is the PasswordSyncManager. Example 13-4 shows a program excerpt for PasswordSyncManager. It uses a hash table (LinkedHashMap) to store the user password profile. Upon initialization and loading the system configuration, the PasswordSyncManager retrieves a list of applications from pstidMapping.xml using the class ServiceConfig. Then it publishes the user password synchronization requests in either SOAP or delimited text based on the service configuration information. Example 13-4. Sample PasswordSyncManagerpackage com.csp.provisioning; import java.util.Date; import java.util.LinkedHashMap; public class PasswordSyncManager { protected ProvisioningUserProfile userProfile = null; protected String protocolBinding; protected String topicName; protected String fullName; protected String firstName; protected String lastName; protected String emailAddress; protected String userId; protected String password; protected ServiceConfig serviceConfig; protected ServiceConfigContext context; protected LinkedHashMap<String,ServiceConfigContext> serviceConfigHashMap = new LinkedHashMap(); protected String timeStamp; /** Creates a new instance of PasswordSyncManager */ public PasswordSyncManager() { setupDefaultUserProfile(); this.serviceConfig = new ServiceConfig(); this.serviceConfigHashMap = serviceConfig.getAllConfigContext(); processPasswordSyncRequests(); } /** * set up default user profile for the password sync requests * */ private void setupDefaultUserProfile() { // set up default user profile String fullName = "Mary Jo Parker"; String firstName = "Mary Jo"; String lastName = "Parker"; String userId = "mjparker"; String emailAddress = "mjparker@namredips.com"; String password = "secret"; this.userProfile = new ProvisioningUserProfile(fullName, firstName, lastName, userId, emailAddress, password); } /** * process password sync requests * */ private void processPasswordSyncRequests() { this.timeStamp = new Date().toString(); for(ServiceConfigContext configContext: this.serviceConfigHashMap.values()) { this.topicName = configContext.getTopicName(); this.protocolBinding = configContext.getProtocolBinding(); System.out.println(this.timeStamp + "- " + configContext.getApplicationId() + " is being processed under " + this.topicName + " using " + this.protocolBinding); new PasswordSyncRequest(this.userProfile, this.topicName, this.protocolBinding); } } public static void main(String args[]) { new PasswordSyncManager(); } } The service configuration for the Password Synchronizer allows different data transportation protocols to be used. Example 13-5 shows a program excerpt for ServiceConfig. The program first retrieves a list of applications from pstidMapping.xml using the class ServiceConfig. The service configuration is stored in a system properties file and is used to indicate the underlying data transport protocol for the password synchronization service, for example, SOAP or JMS. Example 13-5. Sample ServiceConfigpackage com.csp.provisioning; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class ServiceConfig { protected LinkedHashMap<String,ServiceConfigContext> serviceConfigHashMap = new LinkedHashMap(); protected Document doc = null; private static final String configFile = "config/pstidMapping.xml"; protected String requesterId = "passwordSynManagerUser"; // default // service requester id for audit log protected Log log; /** Creates a new instance of ServiceConfig */ public ServiceConfig() { log = LogFactory.getLog(ServiceConfig.class .getPackage().getName()); try { if (configFile == null) { log.fatal("Invalid Password Synchronizer" + " configuration file name"); } else { SAXBuilder builder = new SAXBuilder(false); doc = builder.build(new File(this.getConfigFile())); initConfig(doc); // ensure we can get components config even components not initialized } } catch (IOException ie) { log.fatal("ServiceConfig constructor cannot" + " find file/file not readable"); ie.printStackTrace(); } catch (JDOMException je) { log.fatal("cannot parse Password Synchronizer" + " config file"); je.printStackTrace(); } } /** * Get config file from JVM options * if the file does not exist, use the default one under config/config.xml * * @return String config file */ private String getConfigFile() { String localConfigFile = new String(); localConfigFile = System.getProperty("config.file"); if (localConfigFile == null) { localConfigFile = this.configFile; return localConfigFile; } else { return localConfigFile; } } /** * Initialize configuration by loading the ulyssesConfig.xml into the * LinkedHashMap * This will include: * 1. Load configFile * 2. Extract global config * 3. Extract private config for each component into UlyssesConfig * 4. Store private config info in LinkedHashMap * * @param String configFile Ulysses config file */ private void initConfig(Document doc) { setComponentsConfig(doc); } /** * Create pstidMapping.xml from LinkedHashMap * * Assumption - must load pstd mapping file and create LinkedHashMap first * * @param Document doc */ private synchronized void setComponentsConfig(Document doc) { String parentElement = "service"; String applicationIdElement = "applicationId"; String applicationClassNameElement = "applicationClassName"; String applicationURIElement = "applicationURI"; String protocolBindingElement = "protocolBinding"; String topicNameElement = "topicName"; int state = com.csp.provisioning.ServiceConfigContext.UNKNOWN_STATE; String applicationId = new String(); String applicationClassName = new String(); String applicationURI = new String(); String protocolBinding = new String(); String topicName = new String(); //String requesterId = new String(); String requesterId = this.requesterId; ServiceConfigContext context = null; Element root = doc.getRootElement(); List components = root.getChildren(parentElement); Iterator i = components.iterator(); while (i.hasNext()) { Element component = (Element)i.next(); applicationId = component.getChild (applicationIdElement).getText(); applicationClassName = component.getChild (applicationClassNameElement).getText(); applicationURI = component.getChild (applicationURIElement).getText(); protocolBinding = component.getChild (protocolBindingElement).getText(); topicName = component.getChild (topicNameElement).getText(); //System.out.println("topic name = " + topicName); context = new ServiceConfigContext(applicationId, applicationClassName, applicationURI, protocolBinding, state, requesterId, topicName); this.serviceConfigHashMap.put(applicationId, context); } } /** * Retrieve private config in a list * * @param String componentName * @return List a list containing the private config of a Ulysses component */ public ServiceConfigContext getContext (String applicationId) { ServiceConfigContext context; if (this.serviceConfigHashMap == null) { try { if (configFile == null) { log.fatal("Invalid configuration " + "file name"); } else { SAXBuilder builder = new SAXBuilder(false); Document doc = builder.build (new File(configFile)); initConfig(doc); // ensure we can get components config even components not initialized //dumpComponentMap(); context = this.serviceConfigHashMap.get(applicationId); return context; } } catch (IOException ie) { log.fatal("ServiceConfig constructor " + " cannot find file/file not readable"); ie.printStackTrace(); } catch (JDOMException je) { log.fatal("cannot parse config file"); je.printStackTrace(); } return null; } else { context = this.serviceConfigHashMap.get(applicationId); return context; } } /** * Fetch service config context of all components * * @return LinkedHashMap serviceConfigHashMap * **/ public LinkedHashMap getAllConfigContext() { return this.serviceConfigHashMap; } } The Password Synchronizer pattern uses the SPML addRequest message to create a new user account and synchronize user passwords across application systems. Example 13-6 shows a program excerpt of PasswordSyncRequest, which creates a SPML service request. The method createSPMLRequest constructs a SOAP message encapsulating the SPML request. It can be modified to add or change user account details. Example 13-6. PasswordSyncRequestpackage com.csp.provisioning; import java.net.URL; import java.util.Hashtable; import javax.activation.DataHandler; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.TopicConnection; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.xml.soap.AttachmentPart; import javax.xml.soap.MessageFactory; import javax.xml.soap.Name; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPBodyElement; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import com.sun.messaging.xml.MessageTransformer; import com.sun.messaging.TopicConnectionFactory; import com.csp.provisioning.ProvisioningUserProfile; public class PasswordSyncRequest { protected TopicConnectionFactory topicConnectionFactory = null; protected TopicConnection topicConnection = null; protected TopicSession topicSession = null; protected Topic topic = null; protected TopicPublisher topicPublisher = null; protected Message msg = null; protected TextMessage textMsg = null; protected ProvisioningUserProfile userProfile = null; protected String protocolBinding; protected String topicName; protected String fullName; protected String firstName; protected String lastName; protected String emailAddress; protected String userId; protected String password; /** Constructor - Creates a new instance of PasswordSyncRequest * Default constructor to call. This default will use a default user profile Mary Jo Parker for demo. * */ public PasswordSyncRequest() { // default values if not specified this.protocolBinding = "SOAP"; this.topicName = "PROD_FINANCIAL_FRONTOFFICE"; this.fullName = "Mary Jo Parker"; this.firstName = "Mary Jo"; this.lastName = "Parker"; this.userId = "mjparker"; this.emailAddress = "mjparker@namredips.com"; this.topicName = "prod_application1"; this.password = "secret"; init(topicName); try { createSPMLRequest(); start(); } catch (Exception ex) { ex.printStackTrace(); } } /** Constructor - Creates a new instance of PasswordSyncRequest */ public PasswordSyncRequest(ProvisioningUserProfile userProfile, String topicName, String protocolBinding) { this.protocolBinding = protocolBinding; this.topicName = topicName; this.fullName = userProfile.getFullName(); this.firstName = userProfile.getFirstName(); this.lastName = userProfile.getLastName(); this.userId = userProfile.getUserId(); this.emailAddress = userProfile.getEmailAddress(); this.password = userProfile.getToken(); init(this.topicName); try { createSPMLRequest(); start(); } catch (Exception ex) { ex.printStackTrace(); } } /** * Initializes JMS settings * * @param String topicName JMS topic name * @exception JMSException ex JMSException */ private void init(String topicName) { try { // Create JMS topic and settings // Can be replaced by ServiceLocator pattern when available topicConnectionFactory = new TopicConnectionFactory(); topicConnection = topicConnectionFactory.createTopicConnection(); topicSession = topicConnection .createTopicSession(false, Session.AUTO_ACKNOWLEDGE); // topic = null; topic = topicSession.createTopic(topicName); } catch (JMSException je) { je.printStackTrace(); System.out.println("Cannot create topics " + " or topic names"); System.out.println ("Connection problem: " + je.toString()); if (topicConnection != null) { try { topicConnection.close(); } // try catch (JMSException moreEx) { moreEx.printStackTrace(); } } // catch System.exit(1); } // catch } // init() /** * Create SPML add request message in SOAP, and bind to JMS * * This example uses SPML 1.0 syntax for illustration * * @param TopicSession session TopicSession JMS topic session * @param Message message Message JMS message * @param Hashtable PasswordUserProfile Hashtable user password info for * the SPML message * @exception JMSException ex Exception JAXM/SAAJ exception */ private void createSPMLRequest() throws Exception { try { // Create a SOAP envelope MessageFactory mf = MessageFactory.newInstance(); SOAPMessage soapMessage = mf.createMessage(); SOAPPart soapPart = soapMessage.getSOAPPart(); SOAPEnvelope soapEnvelope = soapPart.getEnvelope(); SOAPHeader soapHeader = soapMessage.getSOAPHeader(); SOAPBody soapBody = soapEnvelope.getBody(); // create addRequest SPML message Name name = soapEnvelope.createName("addRequest", "spml", "http://www.coresecuritypattern.com"); SOAPElement element = soapBody.addChildElement(name); SOAPBodyElement addRequest = soapBody.addBodyElement(name); Name childName = soapEnvelope.createName("xmlns"); addRequest.addAttribute(childName, "urn:oasis:names:tc:SPML:1:0"); childName = soapEnvelope.createName("spml"); addRequest.addAttribute(childName, "urn:oasis:names:tc:DSML:2:0:core"); // create identifier childName = soapEnvelope.createName("identifier"); SOAPElement spmlIdentifier = addRequest.addChildElement(childName); childName = soapEnvelope.createName("type"); spmlIdentifier.addAttribute(childName, "urn:oasis:names:tc:SPML:1:0#GUID"); // create user account id childName = soapEnvelope.createName("id"); SOAPElement spmlID = spmlIdentifier.addChildElement(childName); spmlIdentifier.addTextNode(this.userId); // create user account password childName = soapEnvelope.createName("attributes"); SOAPElement attributes = addRequest.addChildElement(childName); childName = soapEnvelope.createName("attr", "dsml", "http://www.sun.com/imq"); childName = soapEnvelope.createName("name"); SOAPElement attr1 = attributes.addChildElement(childName); attributes.addAttribute(childName, "objectclass"); childName = soapEnvelope.createName("value"); SOAPElement attrObjclass = attr1.addChildElement(childName); attrObjclass.addTextNode("user"); childName = soapEnvelope.createName("name"); SOAPElement attr2 = attributes.addChildElement(childName); attributes.addAttribute(childName, "fullname"); childName = soapEnvelope.createName("value"); SOAPElement attrFullname = attr2.addChildElement(childName); attrFullname.addTextNode(this.fullName); childName = soapEnvelope.createName("name"); SOAPElement attr3 = attributes.addChildElement(childName); attributes.addAttribute(childName, "email"); childName = soapEnvelope.createName("value"); SOAPElement attrEmail = attr3.addChildElement(childName); attrEmail.addTextNode(this.emailAddress); childName = soapEnvelope.createName("name"); SOAPElement attr4 = attributes.addChildElement(childName); attributes.addAttribute(childName, "password"); childName = soapEnvelope.createName("value"); SOAPElement attrPassword = attr4.addChildElement(childName); attrPassword.addTextNode(this.password); childName = soapEnvelope.createName("name"); SOAPElement attr5 = attributes.addChildElement(childName); attributes.addAttribute(childName, "lastname"); childName = soapEnvelope.createName("value"); SOAPElement attrLastname = attr5.addChildElement(childName); attrLastname.addTextNode(this.lastName); childName = soapEnvelope.createName("name"); SOAPElement attr6 = attributes.addChildElement(childName); attributes.addAttribute(childName, "firstname"); childName = soapEnvelope.createName("value"); SOAPElement attrFirstname = attr6.addChildElement(childName); attrFirstname.addTextNode(this.firstName); // Attach a local file URL url = new URL("http://localhost:8080"); DataHandler dHandler = new DataHandler(url); AttachmentPart soapAttach = soapMessage.createAttachmentPart(dHandler); soapAttach.setContentType("text/html"); soapAttach.setContentId("cid-001"); //soapMessage.addAttachmentPart(soapAttach); soapMessage.saveChanges(); // Convert SOAP message to JMS this.msg = MessageTransformer.SOAPMessageIntoJMSMessage(soapMessage, this.topicSession); } // try catch (Exception ex) { ex.printStackTrace(); System.out.println("Exception occurred: " + ex.toString()); } // catch } /** * Create a string in plain text to encapsulate password sync request * * @return String a string that concatenates userId, fullName, * emailAddress, password, lastName, firstName */ private String createPasswordRequest() { String PasswordRequest = null; PasswordRequest = this.userId + ":" + this.fullName + ":" + this.emailAddress + ":" + this.password + ":" + this.lastName + ":" + this.firstName; return PasswordRequest; } /** * Start processing SPML message, given the JMS topic name, * and the SPML user password info * It is intended that the start() will start and close JMS connection. * For better efficiency, create a batch loop to process multiple requests. * * @param String topicName String JMS topic name * @param Message message Message JMS message content * @param Hashtable PasswordUserProfile Hashtable user password info for * the SPML message * @param String protocol */ private void start() { String PasswordRequestText = null; try { topicPublisher = this.topicSession.createPublisher(topic); if (this.protocolBinding.equals("SOAP")) { try { createSPMLRequest(); this.topicPublisher.publish(this.msg); } catch (Exception ex) { System.out.println("Cannot create SOAP message"); System.out.println("Message creation error: " + ex.toString()); System.exit(1); } // catch } else if (this.protocolBinding.equals("JMS")) { this.textMsg = this.topicSession.createTextMessage(); PasswordRequestText = createPasswordRequest(); this.textMsg.setText(PasswordRequestText); this.topicPublisher.publish(textMsg); } else { System.out.println("Request protocol " + this.protocolBinding + " is not supported"); System.exit(1); } // if protocol } // try catch (JMSException je) { je.printStackTrace(); System.out.println("Cannot publish SOAP message"); System.out.println("Exception occurred: " + je.toString()); } finally { if (topicConnection != null) { try { topicConnection.close(); } // try topicConnection catch (JMSException jex) { jex.printStackTrace(); System.out.println("Cannot close topicConnection"); System.out.println("Connection problem: " + jex.toString()); } // catch } // if topicConnection } // finally } // try /** * set protocol binding for the service request * * @param String protocolBinding */ public void setProtocolBinding(String protocolBinding) { this.protocolBinding = protocolBinding; } } Using the JMS infrastructure, a small footprint listener program is required for each Provisioning Service Target to intercept the user password synchronization request. Example 13-7 shows a program excerpt of PasswordSyncListener. PasswordSyncListener listens to the predefined JMS topic. Once the password synchronization request is received, the listener processes the service request and notifies the PasswordSyncLedger when the service request is complete (or fails). Example 13-7. Sample PasswordSyncListenerpackage com.csp.provisioning; import java.io.IOException; import java.io.InputStreamReader; import javax.jms.JMSException; import javax.jms.TopicConnection; import javax.jms.TopicSession; import javax.jms.Message; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.TopicSubscriber; import javax.xml.soap.MessageFactory; import com.sun.messaging.TopicConnectionFactory; import com.sun.messaging.xml.MessageTransformer; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import javax.xml.soap.AttachmentPart; import javax.xml.soap.Name; import javax.xml.soap.Node; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPFactory; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.soap.Text; public class PasswordSyncListener implements javax.jms.MessageListener { protected TopicConnectionFactory topicConnectionFactory = null; protected TopicConnection topicConnection = null; protected TopicSession topicSession = null; protected Topic topic = null; protected TopicSubscriber topicSubscriber = null; protected TextMessage message = null; protected InputStreamReader inputStreamReader = null; protected String topicName = "prod_application1"; protected String notifyTopicName = "PASSWORDSYNC_PROVIDER_OK_LIST"; protected MessageFactory messageFactory = null; protected String timeStamp; protected ServiceConfig serviceConfig; protected ServiceConfigContext context; protected LinkedHashMap<String,ServiceConfigContext> serviceConfigHashMap = new LinkedHashMap(); /** * Constructor - create new instance of Password Synchronizer listener * This is a default constructor if no param is given at run-time */ public PasswordSyncListener() { serviceConfig = new ServiceConfig(); serviceConfigHashMap = serviceConfig.getAllConfigContext(); System.out.println("PasswordSyncListener - processing password synchronization requests from JMS topic '" + this.topicName + "'"); System.out.println("Note - completed request will be notified under the JMS topic '" + this.notifyTopicName + "'"); init(); // initialize environment snoop(); // listen for SPML requests } /** * Constructor - create new instance of Password Synchronizer listener */ public PasswordSyncListener(String newTopicName, String newNotifyTopicName) { serviceConfig = new ServiceConfig(); serviceConfigHashMap = serviceConfig.getAllConfigContext(); this.topicName = newTopicName; this.notifyTopicName = newNotifyTopicName; System.out.println("PasswordSyncListener - processing password synchronization requests from JMS topic '" + this.topicName + "'"); System.out.println("Note - completed request will be notified under the JMS topic '" + this.notifyTopicName + "'"); init(); snoop(); } /* * Initializes the JMS settings * @exception ex Exception */ public void init() { // for future enhancement, // use serviceLocator pattern here try { this.messageFactory = MessageFactory.newInstance(); this.topicConnectionFactory = new com.sun.messaging.TopicConnectionFactory(); this.topicConnection = this.topicConnectionFactory.createTopicConnection(); this.topicSession = this.topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); this.topic = this.topicSession.createTopic(this.topicName); } // try catch (Exception ex) { ex.printStackTrace(); System.out.println("Cannot create topics or topic names"); System.out.println("Connection problem: " + ex.toString()); if (topicConnection != null) { try { topicConnection.close(); } catch (JMSException moreEx) { moreEx.printStackTrace(); } } // if topicConnection System.exit(1); } // catch } // init() /* * Displays SOAP header * @param header SOAP header * @exception ex Exception */ private void dumpHeaderContents(SOAPHeader header) { try { Iterator allHeaders = header.examineAllHeaderElements(); while (allHeaders.hasNext()) { SOAPHeaderElement headerElement = (SOAPHeaderElement) allHeaders.next(); Name headerName = headerElement.getElementName(); System.out.print("<" + headerName.getQualifiedName() + ">"); System.out.print("actor='" + headerElement.getActor() + "' "); System.out.print("mustUnderstand='" + headerElement.getMustUnderstand() + "' "); System.out.println("</" + headerName.getQualifiedName() + ">"); } // while addHeaders.hasNext } catch (Exception ex) { ex.printStackTrace(); } // catch } // dumpHeaderContents /* * Retrieves SOAP message contents, and displays * in indented XML format * * @param iterator Iterator for the SOAP message node * @param indent indent space for displaying * XML messages on screen */ private void getContents(Iterator iterator, String indent) { while (iterator.hasNext()) { Node node = (Node) iterator.next(); SOAPElement element = null; Text text = null; if (node instanceof SOAPElement) { element = (SOAPElement)node; Name name = element.getElementName(); System.out.print(indent + "<" + name.getQualifiedName()); Iterator attrs = element.getAllAttributes(); while (attrs.hasNext()){ Name attrName = (Name)attrs.next(); System.out.print(" " + attrName.getQualifiedName() + "='" + element.getAttributeValue(attrName) + "'"); } // while attrs.hasNext System.out.println(">"); Iterator iter2 = element.getChildElements(); getContents(iter2, indent + " "); System.out.println(indent + "</" + name.getQualifiedName() + ">"); } // if node instanceof else { text = (Text) node; String content = text.getValue(); System.out.println(indent + " " + content); } // else } // while } // getContents /* * Processes each JMS message when received * from the JMS topic * @param message JMS message in SOAP format * @exception ex Exception */ public void onMessage(Message message) { try { this.timeStamp = new Date().toString(); MessageFactory messageFactory = MessageFactory.newInstance(); // Should invoke other Web services messages // to unmarshall encrypted SOAP messages, SOAPMessage soapMessage = MessageTransformer .SOAPMessageFromJMSMessage( message, messageFactory ); System.out.println(timeStamp + "- Message received! Converting the JMS message to SOAP message..."); SOAPFactory soapFactory = SOAPFactory.newInstance(); SOAPHeader thisSoapHeader = soapMessage.getSOAPHeader(); dumpHeaderContents(thisSoapHeader); SOAPBody thisSoapBody = soapMessage.getSOAPBody(); Iterator soapContent = thisSoapBody.getChildElements(); System.out.println(); System.out.println(timeStamp + "- Rendering SOAP Message Content"); getContents(soapContent, ""); System.out.println("Attachment counts: " + soapMessage.countAttachments()); Iterator iterator = soapMessage.getAttachments(); while ( iterator.hasNext() ) { AttachmentPart soapAttach = (AttachmentPart) iterator.next(); String contentType = soapAttach.getContentType(); String contentId = soapAttach.getContentId(); if ( contentType.indexOf("text") >=0 ) { String content = (String) soapAttach.getContent(); } // if contentType } // while // take action to notify Password Synchroniza //tion Manager about OK status TakeAction action = new TakeAction(); action.init(notifyTopicName); String tempTopicName = findApplicationId(topicName); if (tempTopicName != null) { action .publishPasswordSyncResult(tempTopicName, "SOAP"); } else { System.out.println("ERROR - Mismatch between applicationId and topicName. Please check pstidMapping.xml"); } } // try catch (Exception ex) { try { TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); System.out.println(timeStamp + "- Password sync request in delimited text: " + text); // take action to notify Password Synchronization Manager about OK status TakeAction action = new TakeAction(); action.init(notifyTopicName); String tempTopicName = findApplicationId(topicName); if (tempTopicName != null) { action.publishPasswordSyncResult(tempTopicName, "JMS"); } else { System.out.println("ERROR - Mismatch between applicationId and topicName. Please check pstidMapping.xml"); } } catch (Exception anotherEx) { anotherEx.printStackTrace(); } } // catch } /* * Starts listening to the JMS topic * @exception ex JMSException */ private void snoop() { char answer = '\0'; final boolean NOLOCAL = true; try { topicSubscriber = topicSession.createSubscriber(topic, null, NOLOCAL); topicSession.createSubscriber(topic); topicSubscriber.setMessageListener(this); topicConnection.start(); System.out.println("Command Option : Q=quit, then <return>"); System.out.println(); inputStreamReader = new InputStreamReader(System.in); while (!((answer == 'q') || (answer == 'Q'))) { try { answer = (char) inputStreamReader.read(); } catch (IOException e) { System.out.println("I/O exception: " + e.toString()); } // catch } // while !answer } // try catch (JMSException ex) { System.out.println("Cannot subscribe message"); System.out.println("Exception occurred: " + ex.toString()); System.exit(1); } finally { if (topicConnection != null) { try { topicConnection.close(); } catch (JMSException ex) { System.out.println("Cannot close topicConnection"); System.out.println("Connection problem: " + ex.toString()); System.exit(1); } } // if topicConnection }// finally } /** * Helper class to look up the applicationId * when given a topicName * * @param String targetTopicName * @return String applicationId */ private String findApplicationId(String targetTopicName) { for(ServiceConfigContext configContext: this.serviceConfigHashMap.values()) { if (configContext.getTopicName() .equals(targetTopicName)) { return configContext.getApplicationId(); } } return null; } public static void main(String[] args) { String newTopicName = new String(); String newNotifyTopicName = new String(); // Command syntax helper if (args.length != 2) { // take default topic name and notify topic //name if no param is given at runtime new PasswordSyncListener(); } else { newTopicName = args[0]; newNotifyTopicName = args[1]; new PasswordSyncListener(newTopicName, newNotifyTopicName); } } // public main } The listener (PasswordSyncListener) of each Provisioning Service Target uses a class called TakeAction to implement how the Provisioning Service Target should handle the user password synchronization request. This may include resetting the user password and notifying the service requester when completed. Example 13-8 shows an implementation of a simple notification action. The class TakeAction can be expanded and modified to include additional processes in the future. Example 13-8. Sample TakeActionpackage com.csp.provisioning; import javax.jms.JMSException; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.TopicConnection; import javax.jms.TopicConnectionFactory; import javax.jms.TopicPublisher; import javax.jms.TopicSession; public class TakeAction { protected TopicConnectionFactory topicConnectionFactory = null; protected TopicConnection topicConnection = null; protected TopicSession topicSession = null; protected Topic topic = null; protected String topicName = null; /** Constructor - Creates a new instance of TakeAction */ public TakeAction() { } /* * Set up the JMS topic connection * * @param String topicName String JMS topic name * @exception Exception ex */ public void init(String topicName) { try { this.topicConnectionFactory = new com.sun.messaging.TopicConnectionFactory(); this.topicConnection = this.topicConnectionFactory.createTopicConnection(); this.topicSession = this.topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); this.topic = this.topicSession.createTopic(topicName); } // try catch (Exception ex) { ex.printStackTrace(); System.out.println("Cannot create topics or topic names"); System.out.println("Connection problem: " + ex.toString()); if (this.topicConnection != null) { try { this.topicConnection.close(); } catch (JMSException moreEx) { moreEx.printStackTrace(); } System.exit(1); } // if topicConnection } // catch } // init /* * Publish successfully completed application * list to a pre-defined topic. * The structure of the message is simply <application> * * @param String application application name where * password is synch * @param String protocol either SOAP or JMS * @exception JMSException ex */ public void publishPasswordSyncResult(String application, String protocol) { TextMessage sentMessage = null; TopicPublisher topicPublisher = null; try { topicPublisher = this.topicSession.createPublisher(topic); sentMessage = this.topicSession.createTextMessage(); sentMessage.setText(application); topicPublisher.publish(sentMessage); } // try catch (JMSException jmsex) { jmsex.printStackTrace(); System.out.println("Cannot publish SOAP message"); System.out.println("Exception occurred: " + jmsex.toString()); } // catch finally { if (topicConnection != null) { try { this.topicConnection.close(); } catch (JMSException jmsex) { jmsex.printStackTrace(); System.out.println("Cannot close topicConnection"); System.out.println("Connection problem: " + jmsex.toString()); } // catch } // if topicConnection } // finally } } A ledger (PasswordSyncLedger) is required to track the status of user password synchronization requests. Example 13-9 shows a program excerpt of using Java Message Service to implement the ledger. Example 13-9. Sample PasswordSyncLedgerpackage com.csp.provisioning; import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedHashMap; import javax.jms.JMSException; import javax.jms.MessageListener; import javax.jms.TopicConnection; import javax.jms.TopicSession; import javax.jms.Message; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.TopicSubscriber; import javax.xml.soap.MessageFactory; import com.sun.messaging.TopicConnectionFactory; import java.util.Date; public class PasswordSyncLedger implements MessageListener { protected TopicConnectionFactory topicConnectionFactory; protected TopicConnection topicConnection; protected TopicSession topicSession; protected Topic topic; protected TopicSubscriber topicSubscriber; protected TextMessage inMessage; protected TextMessage message; protected Message receivedMessage; protected InputStreamReader inputStreamReader; protected String topicName; protected char answer = '\0'; protected final boolean NOLOCAL = true; protected ServiceConfig serviceConfig; protected ServiceConfigContext context; protected LinkedHashMap<String,ServiceConfigContext> serviceConfigHashMap = new LinkedHashMap(); protected MessageFactory messageFactory = null; protected String timeStamp; /** Creates a new instance of PasswordSyncLedger */ public PasswordSyncLedger() { // set default topic name this.topicName = "PASSWORDSYNC_PROVIDER_OK_LIST"; // load Password Synchronizer config file serviceConfig = new ServiceConfig(); serviceConfigHashMap = serviceConfig.getAllConfigContext(); System.out.println("Password Synchronizer Ledger starts."); // set up JMS connection factory init(this.topicName); start(); } /* Set up JMS topic connection, and * initialize the JMS set-up * * @exception ex Exception */ public void init(String topicName) { try { this.topicConnectionFactory = new com.sun.messaging.TopicConnectionFactory(); this.topicConnection = this.topicConnectionFactory.createTopicConnection(); this.topicSession = this.topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); this.topic = this.topicSession.createTopic(topicName); messageFactory = MessageFactory.newInstance(); } // try catch (Exception ex) { ex.printStackTrace(); System.out.println("Cannot create topics or topic names"); System.out.println("Connection problem: " + ex.toString()); if (topicConnection != null) { try { topicConnection.close(); } catch (JMSException moreEx) { // } // catch } // if topicConnection System.exit(1); } // catch } /* * Process each message received * from the listener. * * @exception ex JMSException */ public void onMessage(Message message) { int foundAny = -1; try { TextMessage textMessage = (TextMessage) message; String syncResult = textMessage.getText(); // assume the application is synchronized this.timeStamp = new Date().toString(); System.out.println(this.timeStamp + "- just complete password synchronization for '" + syncResult + "'"); if (this.serviceConfig.getContext(syncResult).getApplicationId().equals (syncResult)) { // set state to SYNC_STATE this.serviceConfig.getContext(syncResult).setState (ServiceConfigContext.SYNC_STATE); viewResult(); } } // try catch (JMSException jmsex) { jmsex.printStackTrace(); } // catch } /* * Start listening to the topic (passed in args[0]) * under a loop. It will only stop when users press Q * * @exception ex JMSException */ public void start() { try { topicSubscriber = this.topicSession.createSubscriber(topic, null, NOLOCAL); this.topicSession.createSubscriber(this.topic); this.topicSubscriber.setMessageListener(this); this.topicConnection.start(); System.out.println("Command Option : Q=quit, then <return>"); inputStreamReader = new InputStreamReader(System.in); while (!((answer == 'q') || (answer == 'Q'))) { try { answer = (char) inputStreamReader.read(); } catch (IOException e) { e.printStackTrace(); System.out.println("I/O exception: " + e.toString()); } // catch } // while } // try catch (JMSException ex) { ex.printStackTrace(); System.out.println("Cannot subscribe message"); System.out.println("Exception occurred: " + ex.toString()); } finally { if (topicConnection != null) { try { topicConnection.close(); } catch (JMSException ex) { ex.printStackTrace(); System.out.println("Cannot close topicConnection"); System.out.println("Connection problem: " + ex.toString()); } // catch } // if topicConnection }// finally } /** * verify whether all provisioning target systems are synchronized */ private void viewResult() { int totalSync = 0; for(ServiceConfigContext configContext: this.serviceConfigHashMap.values()) { if (configContext.getState() != (configContext.SYNC_STATE)) { totalSync++; } } this.timeStamp = new Date().toString(); if (totalSync == 0) { System.out.println(this.timeStamp + "- Notification - all passwords are synchronized in all systems."); System.out.println("Password Synchronizer Ledger is stopped."); System.exit(0); } else { System.out.println(this.timeStamp + "- " + totalSync + " applications need to be synchronized."); } } public static void main(String args[]) { new PasswordSyncLedger(); } } Example 13-10 shows the screen display messages when invoking PasswordSyncManager. The system properties file specifies there are four password synchronization requests. There are three Java Message Service topics (prod_application1, prod_application2, prod_application3 and prod_application4) defined. Example 13-10. Screen display message from PasswordSyncManagercircinus:~/work> java cp ./PasswordSync_Lib.jar;./PasswordSync.jar com. csp.provisioning.PasswordSyncManager Password synchronization requests start. Thu Jun 02 07:54:07 PDT 2005- application1 is being processed under prod_application1 using SOAP Thu Jun 02 07:54:07 PDT 2005- application2 is being processed under prod_application2 using SOAP Thu Jun 02 07:54:07 PDT 2005- application3 is being processed under prod_application3 using JMS Thu Jun 02 07:54:07 PDT 2005- application4 is being processed under prod_application4 using JMS Password synchronization requests completed. Example 13-11 shows the screen display messages from a local instance of PasswordSyncListener. In this example, PasswordSyncListener subscribes to the Java Message Service topic "prod_application1," which corresponds to a specific Provisioning Service Target. Upon receipt of the SPML service request, PasswordSyncListener will display the content of the SOAP message on the screen. Example 13-11. Screen display messages from PasswordSyncListenercircinus:~/work> java -cp ./PasswordSync_Lib.jar;./PasswordSync.jar com. csp.provisioning.PasswordSyncListener prod_application1 PASSWORDSYNC_PROVIDER_OK_LIST PasswordSyncListener - processing password synchronization requests from JMS topic 'prod_application1' Note - completed request will be notified under the JMS topic 'PASSWORDSYNC_PROVIDER_OK_LIST' Command Option : Q=quit, then <return> Thu Jun 02 07:51:18 PDT 2005- Message received! Converting the JMS message to SOAP message... Thu Jun 02 07:51:18 PDT 2005- Rendering SOAP Message Content <spml:addRequest> </spml:addRequest> <spml:addRequest spml='urn:oasis:names:tc:DSML:2:0:core' xmlns='urn:oasis:names: tc:SPML:1:0'> <identifier type='urn:oasis:names:tc:SPML:1:0#GUID'> <id> </id> mjparker </identifier> <attributes name='firstname'> <name> ... </spml:addRequest> Attachment counts: 1 Example 13-12 shows the notification messages from PasswordSyncLedger. This sample program excerpt acts as a console that shows the total number of Provisioning Service Targets whose passwords have been synchronized. Example 13-12. Notification messages from PasswordSyncLedgercircinus:~/work> java cp ./PasswordSync_Lib.jar;./PasswordSync.jar com. csp.provisioning.PasswordSyncLedger Password Synchronizer Ledger starts. Command Option : Q=quit, then <return> Thu Jun 02 07:51:19 PDT 2005- just complete password synchronization for 'application1' Thu Jun 02 07:51:19 PDT 2005- 3 applications need to be synchronized. Thu Jun 02 07:51:23 PDT 2005- just complete password synchronization for 'application2' Thu Jun 02 07:51:23 PDT 2005- 2 applications need to be synchronized. Thu Jun 02 07:51:25 PDT 2005- just complete password synchronization for 'application3' Thu Jun 02 07:51:25 PDT 2005- 1 application needs to be synchronized. Thu Jun 02 07:51:27 PDT 2005- just complete password synchronization for 'application4' Thu Jun 02 07:51:27 PDT 2005- Notification - all passwords are synchronized in all systems. Password Synchronizer Ledger is stopped. Security Factors and Risks
Reality Check
Related PatternsThere are other design patterns that are related to the Password Synchronizer pattern. These include:
|