woensdag 19 augustus 2009

Overriding default CSS properties in GWT

For some applications you may want to change CSS properties for some standard UI elements in the google web toolkit. For instance, you may want all buttons to use a large font, displayed in blue. Now, if we look at the standard css poperties of a gwt button, as defined in the `standard.css' file included in the GWT-user.jar file, it says:

.gwt-Button {
margin: 0;
padding: 3px 5px;
text-decoration: none;
font-size: small;
cursor: pointer;
cursor: hand;
background: url("images/hborder.png") repeat-x 0px -27px;
border: 1px outset #ccc;
}


Here, the font size is set to small and no color is set. So, an obvious attempt would be to (re)define the font-size and color properties in our application css:

gwt-button {
color: blue;
font-size: x-large;
}


This way, you can make any changes you want. However, you may be surprised that in some cases your changes are ignored! This has to do with the order in which the css files are processed, and the css precedence rules: Obviously, we have two conflicting styling rules here, and in that case the FIRST RULE that was processed TAKES PRECEDENCE.

Now, it depends on how you include your css file whether it is processed before or after the GWT system css file:
  • If you include your application css file from the host html page, it will be processed *after* the GWT system css file. Any changes that conflict with the default css values will be ignored. In this case, only the color change has been picked up, since it was not defined before, whereas the font-size has been ignored.
  • If you include your application css file form your module xml file using a element, your css file will be processed *before* the system css file, and *all* the changes will take effect. (At least, that's my empirical observation.)
To see which styles are applied and in which order, you can use the invaluable firebug debugger from within firefox.

Of course, the wisest thing to do is not to tamper with GWT standard styles, but to define and assign a secondary style, although this does require java code.

donderdag 30 april 2009

Howto: external tomcat in GWT 1.6, eclipse 3.4

When you develop apps with GWT, these apps often involve RPC calls. The server functionality accessed through the RPC calls may require extensive server configuration, for instance, it may
require access to a data source. It is next to impossible to configure the built-in jetty server in these cases, and the solution is to use your own servlet container for handling the server requests.

In 1.6 is has become easier to use an external servlet container than in previous versions, because the GWT compiler places its output in a war directory that has identical structure as the
directories holding web application in tomcat and other servlet containers. This makes it easier to `copy' the project to an external servlet container.

These are the steps you must take to use an external tomcat instance that runs within eclipse instead of the GWT-built-in Jetty instance.

1. Create GWT project and host page., assuming it does not exist already. Look up the host page and put a landmark in there (some text or html) so that you can recognize it later on. The host page is located in the war directory of the project.

Compile the project by pressing the `compile/browse' button in the hosted mode browser. This will create a new subdirectory in the war directory of the project that holds your compiled code. The name of the subdirectory is identical to the project name in lowercase.

2. Create a `dynamic web project' in Eclipse. Instruct eclipse to use `war' as content folder, by changing the content folder form `WebContent' into `war'. (This can be done in the 2nd screen of the `create new web project' dialog.) It is smart to give this project a helpful name, such as YourGWTProjectName-ExternalServer.

3. Make a symbolic link to GWT war folder from the dynamic web project.

The war directories play an identical role in both projects.
(If you look at the directory form which a regular
tomcat runs its applications (e.g. $CATALINA_HOME/webapps/) you will see that all subdirectories have identical structure to the war directories in both projects.) They both hold the contents for the web application. Hence, we can plug the GWT-war folder into the dynamic web project.

First, note the file-system location of the `war' folder in the GWT project by looking at the properties of the folder in the project explorer. Then, delete the war folder in the dynamic web project. Create a new folder in the new project, called `war'. Press the `advanced' button, and have the new directory link to the war directory of the GWT project you noted earlier.

After this step, there will probably still be some errors in the project.

4. Remove errors. Add gwt-servlet.jar to the dynamic web project though the `configure build path' option in eclipse. This will get rid of some of the errors. Possibly there are more and you may need to include more jar's, either in the WEB-INF/lib directory of the dynamic web project, or in the lib directory of the tomcat home directory. (Usually called $CATALINA_HOME/lib .) Besides adding the JAR's, in my case, the order of the elements in the web.xml file was incorrect, so that gave a validation error. Changing the order fixed that. (I had to place the welcome-file-list element just before the closing tag.)

4. Deploy the `dynamic web project' you created in step 2 to the tomcat instance in your workspace. If you do not have a tomcat instance in your workspace yet, you must create one first through new | other | server etcetera.

5. Start the tomcat server. Easiest by pressing the `run' button in the servers tab in the JavaEE view, but there may be dozens of other ways to start tomcat from eclipse.

6. Look at your landmark page. Navigate your browser to your host page on the tomcat server. The url will be something like
http://localhost:8080/YourWebbProjectName/YourHostFileName

You should see host html page with the landmark you placed in it earlier.

7. Use external tomcat from GWT project. Instruct GWT hosted mode to use your tomcat server instead of the embedded Jetty server. This can be done by opening the run configuration for the GWT project (run | run configurations). Then, In the 'Main' tab, under the Embedded Server section, uncheck the "Run built-in server" checkbox.

Next, in the "GWT" tab, change the URL to point to the URL you used in the previous step.

8. Start the gwt app in hosted mode. You will see a reference to your tomcat server in the address bar.

9. Re-organize packages. Of course, your client code stays in the GWT project, so you can run it with the hosted mode, which enables debugging of the client code from within eclipse.

However, there is no need to have the server code in the GWT project anymore, so that can be moved to the dynamic web project project. (Move the packages into the src directory on the server project.)

The classes that are common to client and server can either be copied into both projects. This is the easy way: it works but it leads to code duplication. If you want to do it the neat way, the
classes can be placed in a separate `common' package in its own project. From there, you can create a GWT module for the common classes that can be inherited by your client module. for the
server, you can just add a project dependency in eclipse.

10. CONFIGURE TOMCAT. NOW THAT YOU ARE USING YOUR OWN TOMCAT INSTANCE, YOU CAN CONFIGURE THE TOMCAT INSTANCE ANY WAY YOU WANT, I.E. ADD DATA SOURCES TO IT.

Also, because the symbolic link in the file system exists, there is no need to explicitly copy the contents of the GWT-war folder to the tomcat server. (In a previous version of this blog post, this directory was copied manually, and this copy-step had to be repeated periodically.) However, you *should* re-compile the GWT project into javascript and press the refresh button (F5) on the dynamic web project now and then.

woensdag 8 april 2009

Declare element to be space preserving in XSD

Today i was working with a self-defined XML format. One element type in my xml files is the <scriptblock> element. This element contains snippets of script code.

When i was finished composing my XML file in my XML editor, told the editor to autoformat the XML document. Autoformatting is a tool, i use it about every 10 seconds when writing source code. However, in this case the autoformatter screwed up my <scriptblock> elements: It treated the contents like plain text, just pasting all code in a single text paragraph. New lines started halfway comment strings or String arguments, and all indentation was lost.

Obviously, for source code, layout is important and i wanted a way to make sure that XML processors like the autoformatter would keep their hands off my carefully applied manual formatting in the <scriptblock> elements...

That turned out to be pretty easy: in the .xsd (schema definition) for my xml format, i had to add an attribute called "xml:space" with the "scriptblock" element type, and set it to fixed value "preserve".


<xs:element
name="scriptblock"
minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension
base="xs:string">
<xs:attribute
ref="xml:space"
fixed="preserve" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>


This attribute refers to an externally defined attribute (xml:space). Hence, you must import the xml namespace definition in the top of your document, and also declare the xml namespace in your schema element:

<xs:schema
targetNamespace="http://www.yourcompany.com"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns="http://www.yourcompany.com">

<xs:import
namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd" />



After that, the autoformatter left my code blocks alone, and formatting was preserved. (Although i had to re-start the XML editor first.)

Hope this is useful for some.

Mike

donderdag 5 maart 2009

What should be in the XML header for validation?

Today i was looking for a way to let users configurate the application that i'm writing. The obvious way to do that is by having the users create an XML file with configuration information.

However, to avoid errors in these user-XML files, they should ve validated against a schema definition or .XSD file. The headers of the XML files i saw in the wild always looked a bit daunting to me: The `schema' element just has so many cryptic attributes. So here's an explanation of what they mean and how to set them.

First of all, you should create a schema definition for your XML files. In my case, i want to specify an XML format for configuring the `proxy' component of my application. Hence i call the main element `proxyConfig'. Below, you see a snippet from the XSD file with comments to clarify what the header lines mean.


<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.mycompany.com"
targetNamespace="http://www.mycompany.com"
elementFormDefault="qualified">

<xs:element name="proxyConfig">
<xs:complexType>
[stuff omitted]
</xs:complexType>
</xs:element>
<xs:schema>


The first line xmlns:xs="http://www.w3.org/2001/XMLSchema" links namespace `xs' to the schema namespace http://www.w3.org/2001/XMLSchema. Now, if elements are prefixed with `xs:' they are assumed to refer to elements in the said namespace.

The second line xmlns="http://www.mycompany.com" defines the default namespace for THIS document. (I.e. the .xsd document you see above.) If an element used in this document does not have a namespace prefix, it is assumed to refer to the namespace http://www.mycompany.com.

The third line targetNamespace="http://www.mycompany.com" defines the namespace that the elements defined in this .xsd file should be placed in. This line is important, because we'll refer to it in our XML file below!

(Aside: You see that the target namespace and all referred namespaces here are URL's. As far as i understand, this is not actually necessary, it is merely a convention. This is a source for confusion, since it suggests that the URL's can be accessed to fetch information about syntax of the namespace. This is not true, it is just an arbitrary name.)

Finally, after creating the XSD file, we place it somewhere on the internet, e.g. on http://www.mycompany.com/download XML/proxyConfig.xsd .

Now for the XML file. A simple XML file is shown below:

<proxyConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.mycompany.com"
xsi:schemaLocation="http://www.mycompany.com http://www.mycompany.com/download/XML/proxyConfig.xsd">

[stuff omitted]

</proxyConfig>


You see that in the first line xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" we say that namespace xsi refers to namespace "http://www.w3.org/2001/XMLSchema-instance". This namespace contains a number of attributes, among wchich the attribute xsi:schemaLocation. This attribute can be used to indicate the location for a namespace. Here, we use it in line 3:

xsi:schemaLocation="http://www.mycompany.com http://www.mycompany.com/download/XML/proxyConfig.xsd"

to indicate that the location of the .XSD file that defined namespace http://www.mycompany.com is http://www.mycompany.com/download/XML/proxyConfig.xsd. Remember that we set the namespace name (http://www.mycompany.com) and the xsd location http://www.mycompany.com/download /XML/proxyConfig.xsd ourselves when we created the .xsd file.

Finally, the second line xmlns="http://www.mycompany.com" defines the default namespace for the XML document.

Another source of confusion (to me) was the fact that your `own' namespaces need to be pointed at by a schemaLocation attribute. However, when referring to the http://www.w3.org/2001/XMLSchema or the http://www.w3.org/2001/XMLSchema-instance namespaces above, it is not necessary to explicitly give the location of the defining .XSD. This does not seem very consequent to me. (Presumably, this is because these latter namespaces are W3C namespaces somehow.) Maybe someone can explain this?

donderdag 26 februari 2009

Authentication when using RMI

Today, i was working on an RMI application and i ran into the following problem: I wanted my RMI clients to authenticate themselves when accessing the RMI service, so that clients cannot access eachother's data and the service cannot be abused by un-authenticated clients.

So, how do you implement client authentication? I wanted a SIMPLE solution without setting up a public key infrastructure and without using SSL/TLS, as this is just too heavy for my simple requirements.

Then i ran into an authentication mechanism called CRAM-MD5, which is an abbreviation of Challenge Response Authentication Method. It operates as follows:



Each user (client) has a password that is also known to the server but secret otherwise.


When the client wants to authenticate itself to the server, it requests a `challenge' which is just a random sequence. The challenge is prepended with the password and the result is transformed with a `MessageDigest' transformation, e.g. MD5. This is a one-way transformation: it is (in practice) impossible to recover the input value from the transformed value... However, the transformed value is unique to the input value. The client then sends the transformed value over the network to the server.


The server performs the same computation as the client and compares the two transformed values. When they are equal, the server concludes that the client must have known the password and the client is authenticated.




To implement this in a toy example, you need the following classes/interfaces:

The RMI service definition interface:
package demo.authenticate;


import java.math.BigInteger;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Authenticate extends Remote {

String sayHello() throws RemoteException;

BigInteger challenge(String username) throws RemoteException;

boolean authenticate(String username, BigInteger digestClient)
throws RemoteException;
}



The RMI service:
package demo.authenticate;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

/**
* Class for testing MD5 authentication in RMI connection.
*
* Each user (client) has a password that is also known to the server but secret
* otherwise.
*
* When the client wants to authenticate itself to the server, it requests a
* `challenge' which is just a random sequence. The challenge is prepended with
* the password and the result is transformed with a `MessageDigest'
* transformation, e.g. MD5. This is a one-way transformation: it is (in
* practice) impossible to recover the input value from the transformed value...
* However, the transformed value is unique to the input value. The client then
* sends the transformed value over the network to the server.
*
* The server performs the same computation as the client and compares the two
* transformed values. When they are equal, the server concludes that the client
* must have known the password and the client is authenticated.
*/
public class Server implements Authenticate {

private static Map<String, String> passwords;

private static Map<String, BigInteger> serverChallenges;

private static SecureRandom secureRandom;

public Server() {
}

public String sayHello() throws RemoteException {
return "Hello. Please authenticate!";
}

public static void main(String args[]) {

try {
secureRandom = new SecureRandom();
passwords = new HashMap<String, String>();
serverChallenges = new HashMap<String, BigInteger>();

// put some data in passwords map
passwords.put("John", "JohnsSecret");
passwords.put("Jane", "JanesSecret");

// instantiate the rmi service
Server obj = new Server();
Authenticate stub = (Authenticate) UnicastRemoteObject
.exportObject(obj, 0);

// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.rebind("Authenticate", stub);

System.err.println("Server ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}

public boolean authenticate(String username, BigInteger digestClient)
throws RemoteException {

if (!passwords.containsKey(username))
throw new RemoteException("Unknown user trying to authenticate.");
if (serverChallenges.get(username) == null)
throw new RemoteException("Unable to authenticate " + username
+ ". Missing authentication challenge.");

try {
// First compute the server-side digest
MessageDigest messageDigest = java.security.MessageDigest
.getInstance("SHA-256");
// feed stored challenge and stored password
messageDigest.reset();
messageDigest.update(serverChallenges.get(username).toByteArray());
messageDigest.update(passwords.get(username).getBytes("UTF-16"));
// and read the digest into a BigInteger
BigInteger digestServer = new BigInteger(1, messageDigest.digest());

System.out.println("Digest server: " + digestServer);
System.out.println("Digest client: " + digestClient);

// The see if match. If so, client must have known password.
if (digestClient.equals(digestServer)) {
System.out.println("Authentication succeeded");
return true;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

System.out.println("Authentication failed");
return false;
}

public BigInteger challenge(String username) throws RemoteException {
byte[] bytes = new byte[100];

if (!passwords.containsKey(username))
throw new RemoteException("Unknown user trying to authenticate.");

// generate a random sequence of bytes
secureRandom.nextBytes(bytes);
// transform the bytes into a BigInteger
BigInteger serverChallenge = new BigInteger(1, bytes);
serverChallenges.put(username, serverChallenge);
// and return it...
return serverChallenge;
}
}


The RMI service sends the challenge in the form of a BigInteger. The challenge is initially created as an array of random bytes using the SecureRandom class. But this array cannot be transported over the network: When an array is given as an argument just its starting address in memory is passed. An easy way around it is to creat a BigInteger based on the array. (The byte array is then interpreted as a twos complement integer of arbitrary length.) The BigInteger can be given as argument since it is passed by value.

The RMI client:
package demo.authenticate;

import java.math.BigInteger;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.MessageDigest;

public class Client {

private static String password = "JohnsSecret";

private Client() {}

public static void main(String[] args) {
MessageDigest digest;

String host = (args.length < 1) ? null : args[0];
try {
Registry registry = LocateRegistry.getRegistry(host);
Authenticate stub = (Authenticate) registry.lookup("Authenticate");

// Then authenticate myself...
digest = java.security.MessageDigest.getInstance("SHA-256");
digest.reset();
digest.update(stub.challenge("John").toByteArray());
digest.update(password.getBytes("UTF-16"));
BigInteger digestClient = new BigInteger(1,digest.digest());

boolean success = stub.authenticate("John", digestClient);
System.out.println("Client digest: "+ digestClient);
System.out.println("Client: authentication success: "+ success);

} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}



There seems to be a security problem with the SHA algorithm and similar digest algorithms, so the method is not watertight. (See http://www.win.tue.nl/hashclash/rogue-ca/ ). But if your security requirements are not very strict, as in my case, you should keep most crooks outdoor!

(Don't take my word for it, i'm not an expert.)