//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// MicroSafe source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 17.10.2006 by eso
//
// MicroSafe 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.
//
// MicroSafe 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
// MicroSafe; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA or use the contact information
// from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.microsafe.ui;
import de.esoco.j2me.model.HierarchyNode;
import de.esoco.j2me.resource.ResourceBundle;
import de.esoco.j2me.ui.BasicNodeController;
import de.esoco.j2me.ui.Dialog;
import de.esoco.j2me.ui.ExecutableCommand;
import de.esoco.j2me.ui.MessageScreen;
import de.esoco.j2me.ui.NodeCommand;
import de.esoco.j2me.ui.NodeController;
import de.esoco.j2me.util.Executable;
import de.esoco.j2me.util.TextUtil;
import de.esoco.j2me.util.UserNotificationException;
import de.esoco.microsafe.crypto.CryptoHandler;
import de.esoco.microsafe.crypto.InvalidKeyException;
import de.esoco.microsafe.model.EncryptableNode;
import de.esoco.microsafe.model.MicroSafeModel;
import de.esoco.microsafe.model.MicroSafeNode;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.TextField;
/********************************************************************
* BasicNodeController subclass with enhancements to handle encryption specific
* tasks.
*
* @author eso
*/
public class MicroSafeNodeController extends BasicNodeController
{
//~ Instance fields --------------------------------------------------------
MicroSafeModel rModel;
private ExecutableCommand aChangePasswordCommand;
private NodeCommand aCryptoCommand;
//~ Constructors -----------------------------------------------------------
/***************************************
* Creates a new instance for a certain model.
*
* @param rModel The data model that this controller operates on
*/
public MicroSafeNodeController(MicroSafeModel rModel)
{
this.rModel = rModel;
initCommands();
}
//~ Methods ----------------------------------------------------------------
/***************************************
* If the queried node is encrypted and no password has been set before,
* this method will first query the password from the user and, if the
* password is correct, grant access to it. If the password is already set
* the call will be forwarded to <code>super.checkNodeAccess()</code> (which
* will simply invoke <code>rInvokeIfGranted.execute()</code>).
*
* @param rNode The node to check
* @param rInvokeIfGranted The Executable instance to invoke if access is
* allowed
*
* @see de.esoco.j2me.ui.BasicNodeController#checkNodeAccess(HierarchyNode,
* Executable)
*/
public void checkNodeAccess(final HierarchyNode rNode,
final Executable rInvokeIfGranted)
{
if ((rNode instanceof MicroSafeNode) &&
((MicroSafeNode) rNode).isEncrypted() &&
(((MicroSafeNode) rNode).getCryptoHandler() == null))
{
queryPassword("MsPassword", false, (MicroSafeNode) rNode, false,
rInvokeIfGranted);
}
else
{
super.checkNodeAccess(rNode, rInvokeIfGranted);
}
}
/***************************************
* To return the image for a certain Node.
*
* @param sNodeType The node type code (always available)
* @param rNode The node for which the image shall be returned (may be
* NULL during node creation)
*
* @return The Image for the node or NULL if no Image shall be displayed
*
* @see NodeController#getNodeImage(String, HierarchyNode)
*/
public Image getNodeImage(String sNodeType, HierarchyNode rNode)
{
if ((rNode instanceof EncryptableNode) &&
((EncryptableNode) rNode).isEncrypted())
{
sNodeType += "E";
}
return super.getNodeImage(sNodeType, rNode);
}
/***************************************
* To return an array with commands that can be invoked on editable nodes.
*
* @param rNode The node for which the command list shall be returned
*
* @return An array of (Runnable)Command instances
*/
public Command[] getNodeListEditCommands(HierarchyNode rNode)
{
if (rNode.getParent() == null)
{
return new Command[] { aCryptoCommand, aChangePasswordCommand };
}
else
{
return new Command[] { aCryptoCommand };
}
}
/***************************************
* Internal method as a shortcut to ResourceBundle.getInstance.getString().
*
* @see de.esoco.j2me.util.ResourceBundle#getString(String)
*/
public String getString(String sKey)
{
return ResourceBundle.getCurrent().getString(sKey);
}
/***************************************
* To change the encryption of a particular node. If the node is encrypted
* already the encryption will be removed (if the user confirms it). Else
* the encryption of the node and all it's children will be activated. If no
* password has been set before it will be queried first.
*
* @param rNode The node for which the encryption shall be changed
*
* @throws UserNotificationException If an error occurs
*/
void changeNodeEncryption(final MicroSafeNode rNode)
throws UserNotificationException
{
CryptoHandler rCrypto = rNode.getCryptoHandler();
final boolean bEncrypt = !rNode.isEncrypted();
if (rCrypto == null)
{
boolean bNewEncryption = !rModel.usesEncryption();
String sMsgKey = bNewEncryption ? "MsNewPasswd"
: "MsPassword";
Executable aEncryptSet = new Executable()
{
public void execute(Object rArg)
throws UserNotificationException
{
setNodeEncryption(rNode, bEncrypt);
}
};
queryPassword(sMsgKey, bNewEncryption, rNode, false, aEncryptSet);
}
else
{
setNodeEncryption(rNode, bEncrypt);
}
}
/***************************************
* To change the encryption of a particular node. If the node is encrypted
* already the encryption will be removed (if the user confirms it). Else
* the encryption of the node and all it's children will be activated. If no
* password has been set before it will be queried first.
*
* @throws UserNotificationException If an error occurs
*/
void changePassword() throws UserNotificationException
{
CryptoHandler rCrypto = rModel.getCryptoHandler();
if (rModel.usesEncryption() && rCrypto == null)
{
Executable aNewPwQuery = new Executable()
{
public void execute(Object rArg)
{
queryPassword("MsNewPasswd", true, rModel, true, null);
}
};
queryPassword("MsOldPasswd", false, rModel, false, aNewPwQuery);
}
else
{
queryPassword("MsNewPasswd", true, rModel, true, null);
}
}
/***************************************
* Queries a password from the user and invokes a PasswordSetter on the
* given node if the password input is confirmed.
*
* @param sMsgKey The resource key of the message to display
* @param bVerify TRUE if the password input shall be verified; the
* input dialog result will be NULL if the
* verification fails
* @param rNode The node on which the password shall be set
* @param bPasswordChange TRUE if the password is changed by this call. This
* means that empty passwords shall be set as NULL
* (to remove the password) and the view must be
* reset afterwards.
* @param rInvokeIfOk Will be invoked if the user confirms the input
*/
void queryPassword(String sMsgKey,
boolean bVerify,
MicroSafeNode rNode,
boolean bPasswordChange,
Executable rInvokeIfOk)
{
PasswordSetter aSetter = new PasswordSetter(rNode, bPasswordChange,
rInvokeIfOk);
MessageScreen.showInput(getString("MtPassword"), getString(sMsgKey), null,
CryptoHandler.getMaxKeyLength(),
TextField.PASSWORD, bVerify, aSetter);
}
/***************************************
* Internal method to set the encryption password on the model to enable
* access to an encrypted node.
*
* @param rNode The node that shall be accessed
* @param sPassword The password input by the user
*
* @throws UserNotificationException If the password is wrong or another
* encryption error occurs
*/
void setEncryptionPassword(MicroSafeNode rNode, String sPassword)
throws UserNotificationException
{
try
{
rModel.setEncryptionKey((sPassword == null) ? null
: sPassword.getBytes());
}
catch (Exception e)
{
String msg;
if (e instanceof InvalidKeyException)
{
msg = getString("MsPassWrong");
}
else
{
msg = TextUtil.formatMessage(getString("MsPassErr"),
new Object[] { e.getMessage() });
}
throw new UserNotificationException(getString("MtPassErr"), msg, e);
}
}
/***************************************
* Set the encryption flag of a particular node (and by that of all it's
* children too) and handle all possible errors by wrapping them into a
* UserNotificationException.
*
* @param rNode The node to set the encryption flag on
* @param bEncrypted The new value of the encryption flag
*
* @throws UserNotificationException If changing the flag fails
*/
void setNodeEncryption(MicroSafeNode rNode, boolean bEncrypted)
throws UserNotificationException
{
try
{
rNode.setEncryption(bEncrypted);
getView().updateNodeItem(rNode, null, null);
}
catch (Exception e)
{
String msg = TextUtil.formatMessage(getString("MsCryptErr"),
new Object[] { e.getMessage() });
throw new UserNotificationException(getString("JL_MtError"), msg,
e);
}
}
/***************************************
* Initializes the command instances that are specific to MicroSafe nodes.
*/
private void initCommands()
{
aCryptoCommand = new NodeCommand(getString("Crypto"), Command.ITEM, 10,
true, this)
{
public void processNode(HierarchyNode rNode)
throws UserNotificationException
{
changeNodeEncryption((MicroSafeNode) rNode);
}
};
aChangePasswordCommand = new ExecutableCommand(getString("ChgPasswd"),
Command.ITEM, 100)
{
public void execute(Object rArg)
throws UserNotificationException
{
changePassword();
}
};
}
//~ Inner Classes ----------------------------------------------------------
/********************************************************************
* Helper class that implements a dialog listener which queries a password
* from an input dialog and applies it to a certain MicroSafeNode instance.
*/
private class PasswordSetter implements Executable
{
//~ Instance fields ----------------------------------------------------
boolean bPasswordChange;
Executable rExec;
MicroSafeNode rNode;
//~ Constructors -------------------------------------------------------
/***************************************
* Creates a new PasswordSetter object.
*
* @param rNode The node to set the password on
* @param bPasswordChange TRUE, if the model's password will be changed
* with this invocation
* @param rExec An optional Executable to invoke finally or
* NULL
*/
PasswordSetter(MicroSafeNode rNode,
boolean bPasswordChange,
Executable rExec)
{
this.rNode = rNode;
this.rExec = rExec;
this.bPasswordChange = bPasswordChange;
}
//~ Methods ------------------------------------------------------------
/***************************************
* dialogFinished implementation that sets the password and optionally
* invokes an Executable afterwards. If the input dialog was setup for
* password verification (i.e. with two input fields) and the
* verification failed (result will be NULL), a corresponding error
* message will be displayed.
*
* @param rInputDialog The dialog instance that contains the password
*
* @throws UserNotificationException If an exception occurs
*/
public void execute(Object rInputDialog)
throws UserNotificationException
{
String sPassword = (String) ((Dialog) rInputDialog).getResult();
if (sPassword == null)
{
// Password NULL means that the input verification failed
ResourceBundle rRsrc = ResourceBundle.getCurrent();
String sTitle = rRsrc.getString("MtPassErr");
String sText = rRsrc.getString("MsPassComp");
MessageScreen.showAlert(sTitle, sText, AlertType.ERROR);
return;
}
if (bPasswordChange && (sPassword.length() == 0))
{
sPassword = null;
}
setEncryptionPassword(rNode, sPassword);
// refresh display and invalidate application clipboard when
// password changes because because all nodes will be decrypted
// (icon change) and encryption of nodes in the clipboard becomes
// invalid
if (bPasswordChange)
{
getView().reset();
}
if (rExec != null)
{
rExec.execute(null);
}
}
}
}