/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/examples/org/openeai/portlets/SelfServicePasswordChange.java,v $
$Revision: 1.3 $
*******************************************************************************/
/**********************************************************************
This file is part of the OpenEAI sample, reference implementation,
and deployment management suite created by Tod Jackson
(tod@openeai.org) and Steve Wheat (steve@openeai.org) at
the University of Illinois Urbana-Champaign.
Copyright (C) 2002-2006 The OpenEAI Software Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For specific licensing details and examples of how this software
can be used to implement integrations for your enterprise, visit
http://www.OpenEai.org/licensing.
*/
package org.openeai.portlets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.util.Map;
import java.util.Properties;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletURL;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.any_openeai_enterprise.moa.jmsobjects.coreapplication.v1_0.EnterpriseUser;
import org.any_openeai_enterprise.moa.jmsobjects.coreapplication.v1_0.EnterpriseUserPassword;
import org.any_openeai_enterprise.moa.jmsobjects.coreapplication.v1_0.NetId;
import org.any_openeai_enterprise.moa.objects.resources.v1_0.LightweightPerson;
import org.any_openeai_enterprise.moa.objects.resources.v1_0.Password;
import org.apache.log4j.Category;
import org.apache.log4j.PropertyConfigurator;
import org.jdom.Document;
import org.jdom.Element;
import org.openeai.config.AppConfig;
import org.openeai.config.EnterpriseConfigurationObjectException;
import org.openeai.config.EnterpriseFieldException;
import org.openeai.config.LoggerConfig;
import org.openeai.jms.producer.PointToPointProducer;
import org.openeai.moa.EnterpriseObjectCreateException;
import org.openeai.moa.EnterpriseObjectQueryException;
import org.openeai.moa.EnterpriseObjectUpdateException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* <p>
* SelfServicePasswordChange is a portlet that is used to change the portal user's EnterpriseUserPasssword
* in the sample enterprise.
* </p>
* <p>
* It is designed to JSR-168 portlet spec to be independent of portlet containers <em> with one exception</em>,
* passwordMatchesUPortalPassword(). This method works only with uPortal.
* </p>
* <p>
* SelfServicePasswordChange uses the OpenEAI framework and needs to be supplied with a deployment document. A
* properties file must also be supplied that contains the location of the deployment document. The name location
* of the properties file is specified in the portlet deployment doc, portlet.xml.
* </p>
* @author tcerven *
*/
public class SelfServicePasswordChange extends GenericPortlet {
private Category logger;
private AppConfig appConfig;
private PointToPointProducer p2p;
private String viewXSL;
private String portletName;
static final String ENTERPRISE_USER = "EnterpriseUser.v1_0";
static final String LIGHTWEIGHT_PERSON = "LightweightPerson.v1_0";
static final String NET_ID = "NetId";
static final String SELF_SERVICE_PRODUCER = "SelfServicePortletProducer";
private boolean createBasicPersonIfNotFound=false;
static final String ENTERPRISE_USER_PASSWORD="EnterpriseUserPassword.v1_0";
/**
* Initializes various objects needed by the portlet.
* <p>
* <ol>
* <li>
* Reads the location of the stylesheet used to translate the XML output from the portlet
* into an HTML fragment for the portal. This parameter is stored in the portlet's descriptor, portlet.xml,
* as an init-param.
* </li>
* <li>
* Reads the location of the properties file used by the OpenEAI framework to initalize an AppConfig. This
* parameter is al stored in the portlet's descriptor, portlet.xml,
* as an init-param.
* </li>
* </p>
* @param config PortletConfig object
* @see javax.portlet.Portlet#init(javax.portlet.PortletConfig)
*/
public void init(PortletConfig config) throws PortletException {
super.init(config);
portletName = config.getPortletName();
/* get location of stylesheet */
viewXSL = config.getInitParameter("viewXSL");
if (!(viewXSL.indexOf(File.pathSeparator)==1 || viewXSL.indexOf(":")==2)) {
viewXSL = config.getPortletContext().getRealPath("/")+config.getInitParameter("viewXSL");
}
// Get the path to the properties file.
String propertyFilePath=config.getInitParameter("propertyFilePath");
if (!(propertyFilePath.indexOf(File.pathSeparator)==1 || propertyFilePath.indexOf(":")==2)) {
propertyFilePath=config.getPortletContext().getRealPath("/")+config.getInitParameter("propertyFilePath");
}
// Load the initial properties from the properties file.
Properties initProps = new Properties();
try {
InputStream in = new FileInputStream(propertyFilePath);
initProps.load(in);
in.close();
}
catch (FileNotFoundException fnfe) {
String errMsg = "["+portletName+"] Initial properties file not " +
"found. The exception is: " + fnfe.getMessage();
throw new PortletException(errMsg);
}
catch (IOException ioe) {
String errMsg = "["+portletName+"] Error loading initial " +
"properties from the properties file. The exception is: " +
ioe.getMessage();
throw new PortletException(errMsg);
}
// Initialize an AppConfig using the initial properties.
appConfig = null;
try {
appConfig = new AppConfig(initProps);
}
catch (EnterpriseConfigurationObjectException ecoe) {
String errMsg = "["+portletName+"] Error initializing AppConfig. " +
"The exception is: " + ecoe.getMessage();
throw new PortletException(errMsg);
}
// Get the logger from AppConfig.
try {
LoggerConfig lConfig = new LoggerConfig();
lConfig = (LoggerConfig)appConfig.getObjectByType(lConfig.getClass()
.getName());
logger = Category.getInstance("org.openeai.SelfService.SelfServiceProducer");
PropertyConfigurator.configure(lConfig.getProperties());
}
catch (Exception e) {
logger = org.openeai.OpenEaiObject.logger;
}
// Get the producer from AppConfig.
try {
p2p = (PointToPointProducer) appConfig.getObject(SELF_SERVICE_PRODUCER);
logger.info("p2p is "+p2p.getClass());
} catch (EnterpriseConfigurationObjectException e) {
String errMsg = "["+portletName+"] Error configuring "+SELF_SERVICE_PRODUCER+": " +
"The exception is: " + e.getMessage();
logger.error(errMsg);
throw new PortletException(errMsg);
}
// Get the value of createBasicPersonIfNotFound
createBasicPersonIfNotFound = Boolean.valueOf(
config.getInitParameter("createBasicPersonIfNotFound"))
.booleanValue();
logger.info("["+portletName+"] createBasicPersonIfNotFound="+createBasicPersonIfNotFound);
logger.info("["+portletName+"] ViewXSL="+viewXSL);
}
/**
* Processes requests to update or create the EnterpriseUserPassword for the portal user.
* <p>
* If the following conditions are met, the portlet will either create or update the EnterpriseUserPassword
* for the portal user.
* <ol>
* <li>
* The new password can't be empty or null
* </li>
* <li>
* The new password must match the confirmation password
* </li>
* <li>
* The old password must match the uPortal password.
* </li>
* </ol>
* </p>
* SelfServicePasswordChange uses the following request parameters:
* </p>
* <h2>Request Parameters</h2>
* <table>
* <thead>
* <tr>
* <th align="left">Name</th>
* <th align="left">Type</th>
* <th align="left">Value</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>oldPassword</td>
* <td>String</td>
* <td>The current uPortal password</td>
* </tr>
* <tr>
* <td>newPassword</td>
* <td>String</td>
* <td>The new value for the password</td>
* </tr>
* <tr>
* <td>oldPassword</td>
* <td>String</td>
* <td>The current uPortal password</td>
* </tr>
* <tr>
* <td>confirmNewPassword</td>
* <td>String</td>
* <td>Must match newPassword</td>
* </tr>
* <tr>
* <td>UpdateEUP</td>
* <td>String</td>
* <td>This has to exist (its value doesn't matter) or processAction() will ignore the request.</td>
* </tr>
* </tbody>
* </table>
* <p>
* The uPortal password is not stored, rather, a hash of the password is stored by uPortal. This portlet
* must hash the oldPassword using the same algorithm as uPortal in order to check to see if they are
* equal. SelfServicePassword assumes that the portal container will pass this value in the
* PortletRequest.USER_INFO map in a key named "encryptedPassword".
* </p><p>
* The InstitutionalId is also needed to create or update the EnterpriseUserPassword object for the portal
* user. SelfServicePasswordChange assumes that the portal container will pass this value in the
* PortletRequest.USER_INFO map in a key named "user.id".
* </p><p>
* The EnterpriseUserPassword also required an EnterpriseUser. Rather than query for this object,
* SelfServicePasswordChange builds it from information gleaned from the portal container via the PortletRequest.
* EnterpriseUser requires a NetId and InstitutionalId. SelfServicePasswordChange assumes that the portal
* container will pass the InstitutionalId value in the PortletRequest.USER_INFO map in a key named "user.id".
* </p><p>
* The NetId requires a Principal and Domain. Principal is obtained from the portlet contaner via the
* PortletRequest.getUserPrincipal() method. Domain is hard coded "any-openeai-enterprise.org".
* TODO: Parameterize domain.
* </p>
*
* @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest, javax.portlet.ActionResponse)
*/
public void processAction(ActionRequest request, ActionResponse response) throws PortletException {
logger.info("["+portletName+"] \n\nProcess Action Started.\n\n");
logger.info(request.getParameterMap());
String oldPassword=request.getParameter("oldPassword");
String newPassword=request.getParameter("newPassword");
String confirmNewPassword=request.getParameter("confirmNewPassword");
String updateSubmitEUP = request.getParameter("UpdateEUP");
if (updateSubmitEUP!=null) {
Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
Principal p = request.getUserPrincipal();
String encryptedPassword=(String)userInfo.get("encryptedPassword");
String instID=(String)userInfo.get("user.id");
try {
if (passwordMatchesUPortalPassword(oldPassword,encryptedPassword)) {
// Old password EQUALS uPortal password
if (newPassword==null || newPassword.equals("")) {
response.setRenderParameter("actionMessage","New password not specified");
} else {
if (confirmNewPassword==null || !confirmNewPassword.equals(newPassword)) {
response.setRenderParameter("actionMessage","New password and confirm password don't match");
} else {
// update the password
// query for an EnterpriseUserPassword
String principal=null;
principal=p.getName();
String domain=null;
domain="any-openeai-enterprise.org";
logger.info("["+portletName+"] "+"principal="+principal);
logger.info("["+portletName+"] "+"domain="+domain);
EnterpriseUserPassword eup=updateEnterpriseUserPassword(instID, principal, domain, newPassword);
logger.info("["+portletName+"] "+"EnterpriseUserPassword="+eup);
response.setRenderParameter("actionMessage","Password changed. Logout of uPortal.");
}
}
} else {
response.setRenderParameter("actionMessage","Old password DOES NOT EQUAL uPortal password");
}
} catch (EnterpriseConfigurationObjectException e1) {
e1.printStackTrace();
} catch (EnterpriseFieldException e) {
e.printStackTrace();
} catch (EnterpriseObjectQueryException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new PortletException("NoSuchAlgorithmException");
} catch (EnterpriseObjectCreateException e) {
e.printStackTrace();
} catch (EnterpriseObjectUpdateException e) {
e.printStackTrace();
}
}
}
/**
* This just creates a presentation for transformation into a form that targets processAction() with
* the correct parameters. It also displays messages generated by processAction().
* @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest, javax.portlet.RenderResponse)
*/
protected void doView (RenderRequest request, RenderResponse response)throws PortletException, IOException{
logger.info("["+portletName+"] doView");
Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
Principal p = request.getUserPrincipal();
PortletURL portletUrl = response.createActionURL();
logger.info("["+portletName+"] "+userInfo);
response.setContentType("text/html");
Writer out = response.getWriter();
// create a document to hold the response
Document data;
Element dataRoot;
String nameSpace = "http://openeai.org/examples/SelfServicePortlet";
dataRoot = new Element("SelfServicePasswordChangeResponse", "", nameSpace);
dataRoot.setAttribute("portletUrl",portletUrl.toString());
String actionString = request.getParameter("actionMessage");
if (actionString!=null) {
Element actionMessage = new Element("ActionMessage","",nameSpace);
actionMessage.addContent(actionString);
dataRoot.addContent(actionMessage);
}
data = new Document(dataRoot);
String outMsg=outXML(data);
logger.info("["+portletName+"] "+"data:\n"+outMsg);
//org.jdom.Document xmlResp = makeDoc(outMsg);
org.jdom.Document xmlResp = data;
if (xmlResp!=null){
//transform the sucker
try {
logger.info("["+portletName+"] "+"Attempting to transform...");
String s = outTransform(xmlResp, viewXSL);
out.write(s); // send output along to client
logger.info("["+portletName+"] "+"Transformation complete.");
} catch (TransformerConfigurationException e) {
logger.error("Transformer Configuration Error: "+e.toString());
response.setContentType("text/plain");
out.write("The transformation file, '"+viewXSL+"', can not be found.");
} catch (Exception e) {
logger.error("Error: "+e.toString());
} finally {
out.close();
}
} else {
//return output from servlet
logger.fatal("["+portletName+"] Can't create XML from model.");
out.write("<p>Can't create XML from model.</p>"); // send output along to client
out.write("<p>"+outMsg+"</p>"); // send output along to client
out.close();
}
// response.setContentType(request.getResponseContentType());
}
/**
* Updates or creates an EnterpriseUserPassword object for the portal user.
*
* @param instID required by EnterpriseUser
* @param principal required by NetId
* @param domain required by NetId
* @param newPassword the new value of the password, in the case of an update, or the inital value in
* the case of a create
* @return
* @throws EnterpriseConfigurationObjectException
* @throws EnterpriseFieldException
* @throws EnterpriseObjectCreateException
* @throws EnterpriseObjectQueryException
* @throws EnterpriseObjectQueryException
* @throws EnterpriseObjectUpdateException
*/
private EnterpriseUserPassword updateEnterpriseUserPassword(String instID, String principal, String domain, String newPassword)
throws EnterpriseConfigurationObjectException, EnterpriseFieldException,
EnterpriseObjectCreateException, EnterpriseObjectQueryException, EnterpriseObjectUpdateException {
EnterpriseUser eu = (EnterpriseUser)appConfig.getObject(ENTERPRISE_USER);
logger.info("["+portletName+"] Got " + ENTERPRISE_USER + " from AppConfig, performing Query...");
LightweightPerson lPerson = eu.newLightweightPerson();
logger.info("["+portletName+"] Got LightweightPerson...");
lPerson.setInstitutionalId(instID);
logger.info("["+portletName+"] Set instid on lightweight person...");
NetId netId = eu.newNetId();
logger.info("["+portletName+"] Got NetId...");
netId.setPrincipal(principal);
netId.setDomain(domain);
eu.setLightweightPerson(lPerson);
eu.addNetId(netId);
EnterpriseUserPassword eup=(EnterpriseUserPassword)appConfig.getObject(ENTERPRISE_USER_PASSWORD);
eup.setEnterpriseUser(eu);
Password p = eup.newPassword();
p.setValue(newPassword);
p.setType("secure credential");
p.setEncryption("cleartext");
java.util.List returnedEup = eup.query(eu, p2p);
if (returnedEup.size() == 0) {
//create the password
eup.setPassword(p);
eup.create(p2p);
return eup;
} else {
//update the password
EnterpriseUserPassword baselineEup = (EnterpriseUserPassword) returnedEup.get(0);
logger.info("["+portletName+"] baseline="+baselineEup);
eup.setBaseline(baselineEup);
eup.setPassword(p);
eup.update(p2p);
return eup;
}
}
/**
* Creates a Document from a string source.
* @param sXml
* @return document equvalent to string source.
* @throws IOException
*/
private org.jdom.Document makeDoc(String sXml) throws IOException {
org.jdom.Document jdomDoc=null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(sXml));
org.w3c.dom.Document doc = db.parse(is);
//convert to jdom
org.jdom.input.DOMBuilder jdomBuilder = new org.jdom.input.DOMBuilder();
jdomDoc = jdomBuilder.build(doc);
//convert to string
org.jdom.DocType dt = null;
jdomDoc.setDocType(dt);
} catch (ParserConfigurationException pce) {
logger.warn("[makeDoc] ParserConfigurationException: " + pce.getMessage());
} catch (SAXException saxe) {
logger.warn("[makeDoc] SAXException: " + saxe.getMessage());
} catch (IOException ioe) {
logger.warn("[makeDoc] IOException: " + ioe.getMessage());
}
return jdomDoc;
}
/**
* <p>Performs a transformation on a document.
* Uses xalan. Sends output to specified destination.<p>
*
* @param doc the jdom document to transform
* @param xslName the FQN of the the file that contains the
* transformation stylesheet.
* @param out the destination for the results of the transformation.
* @exception Throws up all exceptions.
*/
private String outTransform(org.jdom.Document doc, String xslName) throws Exception {
try {
TransformerFactory tFactory = TransformerFactory.newInstance();
logger.debug("[outTransform] xslName="+xslName);
Templates templates = tFactory.newTemplates(new StreamSource(xslName));
Transformer transformer = templates.newTransformer();
String contentType = null;
String docstring = outXML(doc);
StringWriter out = new StringWriter();
transformer.transform(new StreamSource(new StringReader(docstring)),
new StreamResult(out));
return out.toString();
}
catch (Exception e) {
throw e;
}
}
/**
* Creates a string from a Document
* @param doc the source document
* @return string equvalent to doucment source
*/
private String outXML(org.jdom.Document doc) {
StringWriter sw=new StringWriter();
try {
org.jdom.output.XMLOutputter outputter = new org.jdom.output.XMLOutputter();
outputter.output(doc, sw);
}
catch (Exception e) {
if (e!=null) e.printStackTrace();
}
return new String(sw.getBuffer());
}
/**
* Hashes the supplied password using the same algoritm as uPortal (2.5.1) and compares with the hash
* supplied in the other parameter.
* @param oldPassword
* @param md5_passwd
* @return true if the password are equal, false otherwise.
* @throws NoSuchAlgorithmException
*/
private boolean passwordMatchesUPortalPassword(String oldPassword, String md5_passwd) throws NoSuchAlgorithmException{
boolean same = true;
String txthash = md5_passwd.substring(5);
byte[] whole, salt = new byte[8], compare = new byte[16], dgx;
whole = decode(txthash);
System.arraycopy(whole, 0, salt, 0, 8);
System.arraycopy(whole, 8, compare, 0, 16);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(salt);
dgx = md.digest(oldPassword.getBytes());
int i;
for (i = 0; i < dgx.length; i++) {
if (dgx[i] != compare[i])
same = false;
}
if (same) {
logger.info("[PasswordCheck] credentials match");
}
else {
logger.info("[PasswordCheck] Credentials DO NOT match");
}
return same;
}
/**
* This was originally Jonathan B. Knudsen's Example from his book
* Java Cryptography published by O'Reilly Associates (1st Edition 1998)
*
* @param base64
* @return
*/
private static byte[] decode(String base64) {
int pad = 0;
for (int i = base64.length() - 1; base64.charAt(i) == '='; i--)
pad++;
int length = base64.length()*6/8 - pad;
byte[] raw = new byte[length];
int rawIndex = 0;
for (int i = 0; i < base64.length(); i += 4) {
int block = (getValue(base64.charAt(i)) << 18) + (getValue(base64.charAt(i + 1)) << 12) + (getValue(base64.charAt(
i + 2)) << 6) + (getValue(base64.charAt(i + 3)));
for (int j = 0; j < 3 && rawIndex + j < raw.length; j++)
raw[rawIndex + j] = (byte)((block >> (8*(2 - j))) & 0xff);
rawIndex += 3;
}
return raw;
}
/**
* This was originally Jonathan B. Knudsen's Example from his book
* Java Cryptography published by O'Reilly Associates (1st Edition 1998)
* @param c
* @return
*/
private static int getValue(char c) {
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+')
return 62;
if (c == '/')
return 63;
if (c == '=')
return 0;
return -1;
}
}