Amazing Hoops to Jump Through for the Username
I've been working on the idea of getting the logged in user name into JavaScript so that I can make my little web app allow (and disallow) people without having to resort to JSP pages and login pages with security profiles, etc. Basically, I wanted to get the username, send it to the server for verification, and then if it's OK, show the data, if not, then don't.
I had no idea it was going to be this hard. In retrospect, I should have known, it's not something that Google or Firefox wants to allow as it's a security hole, but come on, folks there are a ton of legitimate intranets where, like us, it'd be ideal to have the username picked off and then used as the basis for simple permissioning. That way, we can use the XP login as the 'rules' for passwords, duration, etc., and if they are logged on, then they have to be who they say they are.
Anyway, I looked at a lot of different techniques. Turns out, the one that's the easiest to do is IE, but that's the one that I can't use because it's got a horrible JavaScript engine, and it's simply not stable enough to use. For IE the JavaScript is as simple as:
var info = new ActiveXObject("WScript.network"); alert('user is: ' + info.UserName);
which is a security risk, yes, but it's something that can be handled in the trust system within IE. It's clean, and I wish Chrome and Firefox had been as nice.
For these guys, I had to use a small Java applet. The code was simple enough:
import java.awt.*; import java.applet.*; public class Bridge extends Applet { /** * This method returns the user that's logged into the box this * applet (and therefore, web browser) is running on. It's a quick * way to get a basic authentication going. */ public String getUsername() { String who = "unknown"; try { who = System.getProperty("user.name"); } catch (Exception e) { // any exception means we get the default } return who; } }
Because I'm using Tomcat 6, and there are security restrictions on the accessing of things within the WEB-INF directory, I needed to place the source code (notice, there's no package statement at the top of the file) in the root of the src/ directory in the project. The ant target compile would build it, but then deposit it in the directory WEB-INF/classes - no good to leave it there - the browser won't be able to get at it. Additionally, there's no reason to package this one class into a jar file - it can be used as-is, as a .class file - we just need to make it available.
So I made a slight addition to the compile target in my build.xml file:
<!-- Copy special applet class files to public area --> <copy todir="${build.home}"> <fileset dir="${build.home}/WEB-INF/classes/" includes="*.class"/> </copy>
so that when all the source java files are compiled into their class files, the ones that do not sit in a package are copied to the root of the web app, and therefore, are publically accessible.
Once we have this all built, we need to have the applet incorporated on the page. Since the class file is at the root of the web app, we can simply say:
<applet code="Bridge.class" name="bridgeApplet" height=10 width=10> </applet>
and then in the JavaScript initialize() method for the page, we can have:
var username = document.bridgeApplet.getUsername(); if (username == 'joe') { ... }
where we're allowing 'joe' to do something, or not, as the logic may be. Unfortunately, we're not all done yet. There's the little problem of the security manager for the Java applets. It's not going to allow us to do this quite yet.
What we need to do is to have a java policy file placed into the 'home' directory on each OS that reads:
grant { permission java.util.PropertyPermission "user.name", "read"; };
and any other properties you'd like to allow to be read. For this example, it's just the 'user.name', but there are others, and you can add them as you like.
The last trick is where to put this file. On linux, it's easy - 'home' is 'home', and you place it at ~/.java.policy and restart Firefox and you're good to go. The policy file will allow the applet to read the property, the JavaScript will get it from the tiny applet, and everything will be OK.
On Windows/XP it's a little different - C:\Documents and Settings\username\.java.policy but it's the exact same file. Since this is the roaming profile for a user, it's not horrible, and it's only the one property.
This doesn't look like much, but it's taken me several hours on the weekend to come up with ideas that didn't work, and then most of the day today to try to come up with something that actually did work. But, in the end, I have something that works, and I can use the simple approved list in a database to drive who can see the tool. Not bad.
[4/21] UPDATE: I was thinking about this morning, and I really came to the conclusion that the .java.policy file wasn't the right thing to do. It was going to be a lot better if I just created the self-signed certificate and then signed the jar. Then, once they accepted the certificate, they were good to go for all the things I write. Much better.
So, to make the certificate, I did the following in the root of the project I was working on:
$ mkdir cert $ cd cert $ keytool -genkey -keystore key.store -alias RATcert -validity 3650 ...answer questions...
the -validity 3650 says that the certificate should be valid for 10 years (more than enough) and the questions just need to be answered. Don't forget to write down the keystore password and then the alias key password - if it's different from the keystore password.
Now that you have the certificate created, you need to sign the jar. I added the following to the ant target compile in place of the 'copy' action that I mentioned above:
<!-- Create applet JAR file --> <jar jarfile="${build.home}/firecache.jar" basedir="${build.home}/WEB-INF/classes/" includes="*.class" /> <signjar alias="RATcert" keystore="${cert.home}/key.store" storepass="keystore-password" keypass="cert-password"> <path> <fileset dir="${build.home}" includes="*.jar"/> </path> </signjar>
where I'd added the property in the build.xml file:
<property name="cert.home" value="${basedir}/cert"/>
so that I can reference the location of the certificate keystore that I created above.
There's a nasty wrinkle with calling even a signed java applet from an unsigned JavaScript method. Turns out that the security model falls back to the lesser of the two. In this case, it means that even if I sign the jar, I don't get anywhere. What I found was that I needed to add a little code to the Java applet and then everything worked:
import java.awt.*; import java.applet.*; import java.security.*; public class Bridge extends Applet { /** * This method returns the user that's logged into the box this * applet (and therefore, web browser) is running on. It's a quick * way to get a basic authentication going. */ public String getUsername() { String who = "unknown"; who = (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { who = System.getProperty("user.name"); } }); return who; } }
The use of the doPrivileged() method seems to be allowing the applet to run at it's security level and not that of the less-secure JavaScript that is calling it. Interesting, but it was a pain to find this one.
The final thing is the change in the applet tag in the HTML to reference the class file in the signed jar:
<applet archive="firecache.jar" code="Bridge" name="bridgeApplet" height=10 width=10> </applet>
Note that because the Bridge class file is in the root of the jar file, you don't want to have the ".class" on the end. This makes it possible to deploy the jar as signed and then the user can just accept it and we don't have to have the .java.policy file. This is a lot better.