Thursday, 9 September 2010

Installing MySQL on RHEL4 without a RHN account


I needed to install a recent MySQL version, 5.1.50 in this case, on a server Red Hat Enterprise Linux 4. As I normally do on the binary equivalent CentOS I tried the yum command, but this apparently isn't available in version 4 of the operating system. Some Googling made clear that on RHEL 4 you need to use the up2date command instead of yum.

When I tried this new command I was presented with a screen that ask for a valid Red Hat Network (RHN) account, which I don't have as the server is owned and was installed by another consultancy firm that works for this customer.


I know I could ask the customer to give me the necessary information or at least try to find it or get it from the other firm, but that would take too long. But, as is usually the case, there's always another way - in this case - do a more manual install using an RPM package.

Two RPMs are needed: MySQL-server-community-5.1.50-1.rhel4.i386.rpm and MySQL-client-community-5.1.50-1.rhel4.i386.rpm, both the 32-bit ones for RHEL4. Installing them should be easy using e.g. rpm -i MySQL-server-community-5.1.50-1.rhel4.i386.rpm, but as usual this isn't the case and I get an error:

error: Failed dependencies: MySQL conflicts with mysql-4.1.22-2.el4.i386

A bit more Googling turned up this link in the MySQL bug tracker that described the same issue and error message. After reading through the issue a solution was found: remove the old MySQL version with the following command: rpm -e mysql-4.1.22-2.el4 --nodeps.

After this I was able to issue two rpm -i commands for the RPM files I downloaded and the MySQL server and client were installed and the MySQL was started. After this you just need to change the MySQL root password for security reasons using mysqladmin -u root password newpassword and you're good to go.

Wednesday, 8 September 2010

The future of this blog

I know I haven't been blogging lately, but due to time constraints I can't find enough time for long post. That's why I've been trying out Twitter lately and I must say I like it. You can follow me on Twitter under the nickname planetsizebrain.

However this blog isn't exactly dead. Whenever I encounter something interesting in the future that's worth blogging about and that can't be expressed in a tweet, I'll be posting it here on the blog.

Monday, 10 May 2010

Authenticated downloads using WGET

Today I needed to download an Adobe Livecyle ES 2 trial version directly to a headless Amazon EC2 instance. So I wanted to use wget and for most downloads, direct downloads this is pretty easy: wget http://www.somesite.com/download.zip. The problem I had, was that you need to be logged into the Adobe site to be able to download the trial. The login basically means that cookies are created and because wget supports cookies I just needed a way to capture the cookies in the correct format to some file on disk.

This can be easily done using the Firefox Cookie Exporter add-on. I just needed to open the Adobe site in Firefox, log in and click Tools > Export Cookies... . This will save all the current cookies to a file on disk. This file can then be used together with the --load-cookies switch present in wget to submit all the cookies present in the file when doing a web request.

Wednesday, 28 April 2010

Find and replace in MySQL

This week I needed to do some find and replace on a column value in a MySQL table. I had a table that contained a column of the form name (id) and I needed to replace the id part with a fixed value.

When I started looking into a solution I found that MySQL supports
regular expressions, but sadly enough they aren't supported in the replace statement. Since I hadn't got a lot of time I had to do it quick and dirty by looking up the positions of the parentheses and then use those positions in combination with the substring function. That resulted in the following query:

UPDATE table SET column = REPLACE(column, SUBSTRING(column, LOCATE('(', column), LOCATE(')', column) - LOCATE('(', column) + 1), 'newvalue')
Just replace table, column and newvalue with the correct values and you're good to go. It also might be a good idea to try the query first in the following form just to be safe, as this won't change anything yet:

SELECT column, REPLACE(column, SUBSTRING(column, LOCATE('(', column), LOCATE(')', column) - LOCATE('(', column) + 1), 'newvalue') FROM table

Tuesday, 9 February 2010

Fixing session fixation in Liferay on Tomcat

The last months I've been working on a Liferay 5.2.5 (embedded Tomcat 6.0.18) portal implementation for a customer and about a week ago they had a security expert do a penetration test on it. One of the biggest remarks was that the portal had a problem with session fixation, a problem that can allow a malicious third party to hijack a portal session.

A short description of the problem is that it is possible for an attacker to provide someone with a URL that contains a session ID that he has retrieved. When the unsuspecting user clicks the URL and logs in, the portal will use the provided session ID, thus enabling the attacker to request URLs with the same session ID and get results as if he had logged in.

The cause of the problem is usually that the web application doesn't change the session ID for a successful login, but continues to use the provided one. The solution is simple, change the session ID just before or after a login, the implementation of this proved to be a bit more difficult.

I first looked at using the login.events.pre or login.events.post events (see portal.properties) that Liferay provides, but invalidating the session, creating a new one and copying over the old session information to the new session in one of these custom actions didn't solve the problem. When using a custom pre login action invalidating the session caused a problem in Liferay's MainServlet because that keeps a reference to the old session and tries to set an attribute on it after the pre login events have been executed causing an IllegalStateException. So I tried a custom post login action since the MainServlet doesn't do any session manipulation after the post login events, but eventhough the action ran without exceptions, the session ID didn't change.

So I switched to plan B: a servlet filter. I used my post login action code in a pretty straightforward servlet filter and configured Liferay to use it, but the result was the same: the code was being executed, but the session ID stubbornly stayed the same even after invalidating the old session contrary to what this post claims (no fingerpointing, just an observation, could be a difference in Tomcat versions).

So what to do now, plan C? Some Googling brought one more possibility: a custom Tomcat Valve. By this time I feld I was getting into 'obscure hack' territory. But I still decided to give it a twirl since we're using Tomcat, no change of web container is foreseen for the far future and we're in control of the enviroment. I quickly threw together a Maven project in Eclipse, put the code attached to the blog post I found in it, packaged it, put the resulting JAR file in tomcat/lib/ext, added the valve definition to tomcat/conf/context.xml and restarted Liferay. Again the code was being executed just fine, but ... the session ID remained the same. Unbe-f*cking-lievable. 3 strikes and I'm out.

Or maybe not. I complained to a collegue about my trials and tribulations trying to solve the session fixation problem and with the provided information he was able to find a blog post with a variation of the Tomcat Valve solution. The solution is largely similar to the previous one, except that it manipulates the request/session a bit more. Applying these changes to the code of the previous Valve, packaging it again and redeploying it brought me to my fourth test and this time 'the bar was green' so to speak. This time the custom Valve seemed to work as expected and produce a different session ID between opening the first page of the portal and going to the login page.

No only one task was left to do: take the best parts off the 2 custom Valves and create an uberValve. One Valve to rule them all:

package your.company.valves;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletException;

import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;

/**
* Valve to regenerate HTTP Session ID's. Based on information
* available in the following 2 links:
*
* http://mikusa.blogspot.com/2008/06/tomcat-authentication-session-fixation.html
* http://www.koelnerwasser.de/?p=11
*/
public class FixSessionFixationValve extends ValveBase {

private static final String INFO = "your.company.valves.FixSessionFixationValve/1.0";

private String url = null;

public String getInfo() {
return INFO;
}

public void setUrl(String url) {
this.url = url;
}

public String getUrl() {
return url;
}

public void invoke(Request request, Response response) throws IOException, ServletException {
Log logger = container.getLogger();

if (url != null && !"".equals(url) && request.getRequestURI().contains(getUrl())) {
// step 1: save old session
Session oldSession = request.getSessionInternal(true);
Map<String, Object> oldAttribs = new HashMap<String, Object>();
Map<String, Object> oldNotes = new HashMap<String, Object>();

if (logger.isDebugEnabled()) logger.debug("Old session ID: " + oldSession.getId());

// Save HTTP session data
Enumeration names = oldSession.getSession().getAttributeNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
oldAttribs.put(name, oldSession.getSession().getAttribute(name));
}

// Save Tomcat internal session data
Iterator it = oldSession.getNoteNames();
while (it.hasNext()) {
String name = (String) it.next();
oldNotes.put(name, oldSession.getNote(name));
}

// step 2: invalidate old session
request.getSession(true).invalidate();
request.setRequestedSessionId(null);
request.clearCookies();

// step 3: create a new session and set it to the request
Session newSession = request.getSessionInternal(true);
request.setRequestedSessionId(newSession.getId());

if (logger.isDebugEnabled()) logger.debug("New session ID: " + newSession.getId());

// step 4: copy data pointer from the old session
// to the new one. Restore HTTP session data
for (String name : oldAttribs.keySet()) {
newSession.getSession().setAttribute(name, oldAttribs.get(name));
}

// Restore Tomcat internal session data
for (String name : oldNotes.keySet()) {
newSession.setNote(name, oldNotes.get(name));
}
}

getNext().invoke(request, response);
}
}

This valve is configurable with one parameter, url, that is used to signal when the session ID needs to be invalidated and recreated. In the case of Liferay, I'm using /c/portal/login.

<?xml version='1.0' encoding='utf-8'?>
<Context useHttpOnly="true">

<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>

<Valve className="your.company.valves.FixSessionFixationValve" url="/c/portal/login" />

</Context>

And that's it for today folks. It was late enough yesterday due to one hell of a deploy that continued to well after midnight, so I'm calling it a night.

Update 30/03/2010: after testing the valve a bit it seemed that it didn't work exactly as wanted, because the URL I was using to detect a login isn't called in all cases. The fix for this is not to detect a URL, but a POST parameter. For this we need to do 2 things: change the code of the valve a bit and move the valve configuration from context.xml to server.xml.


package your.company.valves;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletException;

import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;

/**
* Valve to regenerate HTTP Session ID's. Based on information
* available in the following 2 links:
*
* http://mikusa.blogspot.com/2008/06/tomcat-authentication-session-fixation.html
* http://www.koelnerwasser.de/?p=11
*/
public class FixSessionFixationValve extends ValveBase {

private static final String INFO = "be.belgacom.enable.security.FixSessionFixationValve/1.0";

private String parameterName = null;
private String value = null;

public String getInfo() {
return INFO;
}

public String getParameterName() {
return parameterName;
}

public void setParameterName(String parameterName) {
this.parameterName = parameterName;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@SuppressWarnings("unchecked")
public void invoke(Request request, Response response) throws IOException, ServletException {
String param = request.getParameter(getParameterName());
if (param != null && getValue().equals(param)) {
Log logger = container.getLogger();

// Save old session
Session oldSession = request.getSessionInternal(true);
Map<String, Object> oldAttribs = new HashMap<String, Object>();
Map<String, Object> oldNotes = new HashMap<String, Object>();

if (logger.isDebugEnabled()) logger.debug("Old session ID: " + oldSession.getId());

// Save HTTP session data
Enumeration names = oldSession.getSession().getAttributeNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
oldAttribs.put(name, oldSession.getSession().getAttribute(name));
}

// Save Tomcat internal session data
Iterator it = oldSession.getNoteNames();
while (it.hasNext()) {
String name = (String) it.next();
oldNotes.put(name, oldSession.getNote(name));
}

// Invalidate old session
request.getSession(true).invalidate();
request.setRequestedSessionId(null);
request.clearCookies();

// Create a new session and set it to the request
Session newSession = request.getSessionInternal(true);
request.setRequestedSessionId(newSession.getId());

if (logger.isDebugEnabled()) logger.debug("New session ID: " + newSession.getId());

// Copy data pointer from the old session to the new one. Restore HTTP session data
for (String name : oldAttribs.keySet()) {
newSession.getSession().setAttribute(name, oldAttribs.get(name));
}

// Restore Tomcat internal session data
for (String name : oldNotes.keySet()) {
newSession.setNote(name, oldNotes.get(name));
}
}

getNext().invoke(request, response);
}
}

Use the following configuration in server.xml:



Friday, 5 February 2010

Recursive delete files/directories on OSX

When working with Java projects in Eclipse, or some other IDE, checked out from a source code repository such as CVS or SVN, you sometimes need to remove the IDE's project files or files created by the repository system. On the command line there's an easy way to do this using one of the following 2 code snippets:

  • Files: rm -rf `find . -name '*.project'`
  • Directories: rm -rf `find . -type d -name '.svn'`

Wednesday, 27 January 2010

Ubuntu 9.10 display resolution problem

1,5 years ago I switched from a Windows laptop to a MacBook Pro and haven't really looked back since, ... except at home I still have a water cooled desktop PC that's about 7 years old and still runs Windows XP and I absolutely loathe that operating system. In my honest opinion Windows 2000 Professional was the best OS Microsoft ever made. My desktop PC ran on it for 4 years without the dreaded BSOD and within a week of installing XP it started presenting me all too frequently with them.

Since I wasn't really using the PC that much I tolerated this for some time, but two weeks ago 2 of the 3 hard drives in it failed and while repairing it I decided to install Ubuntu 9.10 which completed my goal of getting rid of Windows. The installation of Ubuntu went pretty painless and quick, with just one hitch: I couldn't configure a higher screen resolution than 800x600. That really sucks ass, especially on a 19inch CRT.

Installing the hardware drivers didn't really solve the problem, in fact it only made it worse, because afterwards I could only select 640x480 as the highest resolution. After reverting to the previous settings I started farting around with the xorg.conf file, but it turned out that that wasn't really my forté as I fucked up the configuration in such a way that in the end I just got a black screen.

You'd think that after all these years a desktop linux distro such as Ubuntu would get configuring your screen resolution as good/user friendly/easy as it is in Windows! So I pretended I wanted to be a millionaire and phoned a friend. He came over and also messed around a bit with the xorg.conf, but also couldn't get a better resolution. What we did discover however was that Ubuntu recognized my ancient Geforce2 M X400 without a problem, but not my CRT. It's some cheap piece of crap from some long-defunct Taiwanese manufacturer and that doesn't help autodiscovering settings one bit.

So I almost gave up, but decided to Google one last time and found a forum post with a possible solution: download a different linux live cd, check if that one provides better resolutions and if it does find the xorg.conf and copy the contents of that one into Ubuntu. I tried several, Knoppix, Linux Mint and CentOS, and with the last one I hit pay dirt! In CentOS I was able to change my monitor and resolution to a significantly better one by using some menu's in System > Preferences and System > Administration.

So I emailed myself the file /etc/X11/xorg.conf from within the livecd, rebooted back into Ubuntu, got the file from my email, put it in the correct location and a log off/log on later I had the resolution I wanted.


And so others won't have to go through the same troubles, I present to you the xorg.conf I got:


# Xorg configuration created by system-config-display

Section "ServerLayout"
Identifier "single head configuration"
Screen 0 "Screen0" 0 0
InputDevice "Keyboard0" "CoreKeyboard"
EndSection

Section "InputDevice"
Identifier "Keyboard0"
Driver "kbd"
Option "XkbModel" "pc105"
Option "XkbLayout" "us"
EndSection

Section "Monitor"

### Comment all HorizSync and VertSync values to use DDC:
Identifier "Monitor0"
ModelName "Monitor 1024x768"
### Comment all HorizSync and VertSync values to use DDC:
HorizSync 31.5 - 61.0
VertRefresh 50.0 - 75.0
Option "dpms"
EndSection

Section "Device"
Identifier "Videocard0"
Driver "nv"
EndSection

Section "Screen"
Identifier "Screen0"
Device "Videocard0"
Monitor "Monitor0"
DefaultDepth 24
SubSection "Display"
Viewport 0 0
Depth 24
Modes "1280x800" "1152x864" "1152x768" "1024x768" "800x600" "640x480"
EndSubSection
EndSection

Friday, 22 January 2010

Eurostar Oopsie

It seems that forgetting to translate that one message happens to everyone, even Eurostar:

Thursday, 21 January 2010

Liferay + FancyBox + IE8 = problems

The last few days I've been trying to integrate PDF in a nice way into Liferay by showing them in a Lightbox alike way (more on the full integration in a future post). I first tried to use the Liferay.Popup, but that just gave me a headache. So finally I settled on using FancyBox 1.2.6.

Getting it to work on OSX/Safari wasn't a problem, Firefox also was a breeze, but IE8 was a different story. It was the first browser to cause a real problem, a Javascript error to be exact: Object doesn't support this property or method. The strange thing was that the examples on the FancyBox website would work correctly in IE8. So what to do now?

The error line and column pointed to some browser detection code (if you can call IE a browser) in the FancyBox Javascript file:
var ieQuirks = null, IE6 = $.browser.msie && $.browser.version.substr(0,1) == 6 && !window.XMLHttpRequest, oldIE = IE6 || ($.browser.msie && $.browser.version.substr(0,1) == 7);
This is default JQuery browser detection code, so I couldn't figure out why this would cause a problem? But some Googling turned up a comment of Nate Cavanaugh to a post about Liferay/IE/Browser detection problems. In his comment he mentions that Liferay has had its own Javascript browser detection code since ages. So I decided to change the FancyBox code to use the Liferay browser detection instead of the JQuery one:
var ieQuirks = null;
var IE6 = Liferay.Browser.isIe() && Liferay.Browser.getMajorVersion() == 6 && !window.XMLHttpRequest;
var oldIE = IE6 || (Liferay.Browser.isIe() && Liferay.Browser.getMajorVersion() == 7);

And what do you think: the error disappeared. I'm no Javascript expert and so I can't explain what exactly causes the Javascript error, maybe a strange Liferay/JQuery/browser interaction (it's not the first one we've encountered), but frankly I don't care since it works now and I don't have to time to investigate further. On to the next problem...

Sunday, 20 December 2009

Exception hell

All Java programmers have certainly encountered their fare share of excessively long, incomprehensible or just plain stupid stacktraces, but this one got me stumped when I saw it:

08:34:55,989 ERROR [jsp:165] java.lang.ClassCastException: com.icesoft.faces.context.ElementController cannot be cast to com.icesoft.faces.context.ElementController
at com.icesoft.faces.context.ElementController.from(ElementController.java:22)
at com.icesoft.faces.context.DOMResponseWriter.enhanceBody(DOMResponseWriter.java:294)
at com.icesoft.faces.context.DOMResponseWriter.enhanceAndFixDocument(DOMResponseWriter.java:239)
at com.icesoft.faces.context.DOMResponseWriter.endDocument(DOMResponseWriter.java:144)
at com.icesoft.faces.facelets.D2DFaceletViewHandler.renderResponse(D2DFaceletViewHandler.java:283)
at com.icesoft.faces.application.D2DViewHandler.renderView(D2DViewHandler.java:161)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:107)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:268)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:137)
at com.icesoft.faces.webapp.http.core.JsfLifecycleExecutor.apply(JsfLifecycleExecutor.java:18)
at com.icesoft.faces.webapp.http.core.PageServer$1.respond(PageServer.java:25)
at com.icesoft.faces.webapp.http.servlet.ServletRequestResponse.respondWith(ServletRequestResponse.java:161)
at com.icesoft.faces.webapp.http.servlet.ThreadBlockingAdaptingServlet$ThreadBlockingRequestResponse.respondWith(ThreadBlockingAdaptingServlet.java:36)
at com.icesoft.faces.webapp.http.core.PageServer.service(PageServer.java:30)
at com.icesoft.faces.webapp.http.core.MultiViewServer.service(MultiViewServer.java:56)
at com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer$Matcher.serviceOnMatch(PathDispatcherServer.java:50)
at com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer.service(PathDispatcherServer.java:19)
at com.icesoft.faces.webapp.http.servlet.ThreadBlockingAdaptingServlet.service(ThreadBlockingAdaptingServlet.java:19)
at com.icesoft.faces.webapp.http.servlet.EnvironmentAdaptingServlet.service(EnvironmentAdaptingServlet.java:29)
at com.icesoft.faces.webapp.http.servlet.MainSessionBoundServlet.service(MainSessionBoundServlet.java:106)
Unless my eyesight has really gone, this exception is saying it can't cast ElementController to ElementController? Same name, same package, as per usual it's just a big bag of fail again. Why can't I just get a normal comprehensible exception and stacktrace for once?

This one is bad, but there is one that is much worse, the dreaded NoClassDefFoundError, but that one deserves its own post somewhere after new year.

Monday, 7 December 2009

Liferay installation problem: Could not find the main class: . Program will exit.

A few weeks ago I was installing Liferay at a client and ran into a very weird installation problem. I was under the impression that I'd done everything correctly, but when I tried to start Liferay I got a baffling error message almost immediatly:

"Could not find the main class: . Program will exit."

As Eddie Izzard would say: Quod The Fuck! How can a simple Liferay install go this wrong? I followed some simple steps to install it:
  • download Liferay
  • transfer ZIP file to server
  • unzip the downloaded file
  • check installed Java version
  • check DB connection
  • configure DB connection in portal-ext.properties
  • start Liferay
By now I've done this simple procedure more than a few times and it never failed, ... until now. So I was kinda gobsmacked. I started digging around, but couldn't find an obvious cause. Also Googling didn't turn up any leads. So I was on my own and decided to start from the beginning and check every line in the Tomcat start script and quickly found that strange things were happening with the startup classpath, hence the empty main class name. A JAR file seemed to be missing: bootstrap.jar.

How could that be possible. I downloaded a fresh Liferay ZIP that unzipped without problems. Then I remembered a long pause during the download around the same time the datacenter people were fiddling with the firewall. A second fresh download later, this time without hickups, I was able to run a file size check against the old download and what do you think: the first download was 20Mb smaller.

How it was able to unzip with throwing obvious errors I still don't know, but after deleting the previous install, unzipping the new one, Liferay started without any problem.

Email containing cid

Did you ever receive an email containing a line that looks like this:

[cid:163091419@01122009-2514]

This probably means that something, in my case an image, is attached to the mail or embedded in it, but the email client, isn't able to process it correctly. There's an easy way to still visualize it.

Just open the email source and find the correct mail part, this should look something like this:
------=_Part_42520_1491296619.1259746973647
Content-Type: image/png; name="Picture 9.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Picture 9.png"
Once you've found this you'll know what the type and encoding of the missing attachment is. Now you'll just need to select the block of encoded text, drop it in a suitable decoder, save the result as a binary file and hey presto!

Wednesday, 25 November 2009

Can't touch this!

Last week I was working on some problem when I got the following output in the console:

touch: cannot touch

So apparently the script I was running was trying to touch a file and was having problems with it. This usually means that the file isn't where it is supposed to be, but just in case I still Googled it and starting from the 3rd result things got funny:


Monday, 2 November 2009

Liferay Password Policies with a regular expression sauce

For a Liferay project that I'm working on there are a bunch of security requirements. One of those is that a user password has to conform to a certain set of rules. Because Liferay is pretty customizable, changing the password policy shouldn't be a problem.

Looking around the Control Board already reveals some possibilities hidden in the Portal > Password Policies menu. Here you can tweak the default password policy or even create a new one. There are several things to customize on this page:
  • Changeable/Change required: can the user change his/her password and is a change required after the first login
  • Password Syntax Checking: (dis)allow passwords that are common dictionary words and set a minimum password length
  • Password History: disallow passwords according to a password history of a configurable length
  • Password Expiration: how long is a password valid before it needs to be changed
  • Lockout: how many times can someone try to log in correctly before his/her account gets locked and how does it get unlocked
While this is already a nice feature set, the password syntax checking isn't quite enough to satisfy our password requirements. Checking the source code and the default portal.properties file reveals that it is possible configure a custom passwords.toolkit. This can be one you create yourself and that extends from com.liferay.portal.security.pwd.BasicToolkit:
public abstract class BasicToolkit {

public abstract String generate();

public void validate(String password1, String password2, PasswordPolicy passwordPolicy)
throws PortalException, SystemException {

validate(0, password1, password2, passwordPolicy);
}

public abstract void validate(long userId, String password1, String password2, PasswordPolicy passwordPolicy)
throws PortalException, SystemException;

}

Another possibility is to use the regular expression toolkit that is available in Liferay. This toolkit can be configured as follows:

#
# Input a class name that extends
# com.liferay.portal.security.pwd.BasicToolkit. This class will be called to
# generate and validate passwords.
#
passwords.toolkit=com.liferay.portal.security.pwd.RegExpToolkit

#
# If you choose to use com.liferay.portal.security.pwd.RegExpToolkit as
# your password toolkit, set the regular expression pattern that will be
# used to generate and validate passwords.
#
# Note that \ is replaced with \\ to work in Java.
#
# The pattern ensures that passwords must have at least 4 valid
# characters consisting of digits or letters.
#
passwords.regexptoolkit.pattern=(?=.{4})(?:[a-zA-Z0-9]*)
One useful regular expression (customized a bit more) that checks if a password is at least 6 characters long, contains at least 1 digit, 1 lowercase/uppercase character, 1 symbol from a given list and no whitespace is:

^.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z])(?=.*[@#$%^&+=])(?!.*\s).*$

I would be using this regular expression if I could only find out how to extend it so it checks if a passwords contains for example at least 3 lowercase/uppercase characters. I tried several variants, but didn't succeed. So for the moment I'm using a custom written BasicToolkit that is using plain old Java code to do the necessary checks.

If a tree falls in the woods does it make a sound? Can you merge a SVN branch if your mother hasn't told you about it?

Change Liferay Chat Portlet Buddy List Strategy

Some time ago I had to check out the possibilities of the Liferay Chat portlet together with a colleague to see if it could be used in our current project or not. Installing it was easily done via the recently introduced Liferay Control Panel. After some quick tests it seemed that for the most part the portlet would be useable if we could find some way to configure who gets to chat to who.

Some clicking around in the control panel and checking out the portlet preferences didn't provide any clues as to how this could be done. So we started looking at the Chat portlet source code and discovered something promising: ChatUtil.getBuddies(long userId).

public class ChatUtil {

public static final int MAX_ENTRIES = 50;

public static final long MAX_POLL_LATENCY = Time.SECOND * 15;

public static final long ONLINE_DELTA = Time.MINUTE;

public static List<Object[]> getBuddies(long userId)
throws SystemException {

long modifiedDate = System.currentTimeMillis() - ONLINE_DELTA;

List<Object[]> buddies = null;

if (PortletPropsValues.BUDDY_LIST_STRATEGY.equals("all")) {
buddies = StatusLocalServiceUtil.getAllStatuses(
userId, modifiedDate, 0, SearchContainer.DEFAULT_DELTA);
}
else if (PortletPropsValues.BUDDY_LIST_STRATEGY.equals("communities")) {
buddies = StatusLocalServiceUtil.getGroupStatuses(
userId, modifiedDate, 0, SearchContainer.DEFAULT_DELTA);
}
else if (PortletPropsValues.BUDDY_LIST_STRATEGY.equals("friends")) {
buddies = StatusLocalServiceUtil.getSocialStatuses(
userId, SocialRelationConstants.TYPE_BI_FRIEND,
modifiedDate, 0, SearchContainer.DEFAULT_DELTA);
}
else if (PortletPropsValues.BUDDY_LIST_STRATEGY.equals(
"communities,friends")) {

List<Object[]> groupBuddies =
StatusLocalServiceUtil.getGroupStatuses(
userId, modifiedDate, 0, SearchContainer.DEFAULT_DELTA);
List<Object[]> socialBuddies =
StatusLocalServiceUtil.getSocialStatuses(
userId, SocialRelationConstants.TYPE_BI_FRIEND,
modifiedDate, 0, SearchContainer.DEFAULT_DELTA);

buddies = new ArrayList<Object[]>(
groupBuddies.size() + socialBuddies.size());

buddies.addAll(groupBuddies);

BuddyComparator buddyComparator = new BuddyComparator(true);

for (Object[] socialBuddy : socialBuddies) {
if (Collections.binarySearch(
groupBuddies, socialBuddy, buddyComparator) < 0) {

buddies.add(socialBuddy);
}
}

Collections.sort(buddies, buddyComparator);
}
else {
buddies = new ArrayList<Object[]>();
}

return buddies;
}

}
This code seems to suggest that there are 4 different ways in which the list of people you can chat to is constructed:
  • all
  • communities
  • friends
  • communities,friends (this looks like something that will have to be refactored someday...)
It seems from the code that all is the default strategy. As almost everything in Liferay can be configured by extending the default portal.properties file, by making and deploying a portal-ext.properties file containing a set of diffs against the original properties file. So we tried to change the strategy to communities by adding 'buddy.list.strategy=communities' to our portal-ext.properties file, using the key as suggested by the code in the PortletPropsValues class:
public class PortletPropsValues {

public static final String BUDDY_LIST_STRATEGY = GetterUtil.getString(
PortletProps.get("buddy.list.strategy"));

}

After restarting the portal and testing, it seemed as if nothing had changed. We still got the see the same people in our respective buddy list. We tried changing the organisations and communities our different users belonged to, but nothing seemed to have the desired effect. Where did we go wrong...? Some Googling pointed us to a forum post that reinforced our feeling that we were indeed looking in the right direction. But why didn't it work then?

While I had to start working on something else, my colleague continued to look into the problem and ultimately found the solution. While we did indeed find the correct configuration key/value pair, we had put it in the wrong file, portal-ext.properties, as the Chat portlet, by means of the portlet.properties file, is configured to override even this file:

include-and-override=portlet-ext.properties

buddy.list.strategy=all
#buddy.list.strategy=communities
#buddy.list.strategy=friends
#buddy.list.strategy=communities,friends
So setting the buddy list strategy in the portal-ext.properties file doesn't have any effect since it will be overridden by the value in portlet.properties because of the include-and-override setting. So when you change the value directly in the portlet everything starts to make a whole lot more sense.

And now I'm gonna have me some object-oriented toast.

Saturday, 24 October 2009

My new GPS: to Garmin or not to Garmin

My trusty old GPS device, a Magellan SporTrak Color, that I use for Geocaching, recently started going a bit haywire. After being turned on it would start to go randomly into on-off cycles, making it very difficult to use. I've had this device for about 4 to 5 years now and was pretty satisfied with it. It had all the necessary functions for geocaching such as an electronic compass and map support, but it was starting to show its age a bit, most of all in satellite acquisition speed and accuracy.

So I decided to look for a new device that had to satisfy certain requirements:
  • Electronic compass
  • Map support (commercial and open source if possible)
  • Mac compatible (Magellan has little to no support)
  • USB (my SporTrak only supports serial ports, a USB to serial cable helps, but transfer speed is still slow)
  • Newest generation GPS chip with WAAS/EGNOS support
  • Extendable memory (my SporTrak only had a fixed 32Mb internal memory)
  • Run on standard AA size batteries (I bloody hate battery packs)
  • Color screen (my SporTrak also had this, but it's just a nice to have)
So I started looking around and came across the newer generation Magellan and Garmin devices. After checking out some tests and reviews for the Magellan Trition series and the Garmin Colorado/Oregon/Dakota series, it seemed like these devices are promising, but still have some teething problems. So I decided to go for the tried and tested Garmin GPSMAP 60CSx as it seems to be the preferred device among geocachers according to the usage numbers and reviews on the geocaching site.
The GPSMAP 60CSx fits all the requirements I'd set, but has one small problem: a recommended selling price of 400 euro. So like I usually do when I don't want to pay full price: I started looking around online. After some searching I found a promising eBay shop: SATNAV24. Here I could buy the device I wanted, including an additional 2Gb MicroSD card and get it shipped to me for only 275 euro.

After ordering it and paying for it via PayPal on sunday evening, it was delivered at work on friday, You've got to love those online retailers. With those prices and service I'm wondering why anybody still buys their stuff in regular shops where they never have what you want, treat you as crap, overcharge you or just plainly annoy you (e.g. pushy sales people).

After receiving the new device and testing it out a bit, I've come to the following preliminary conclusions:
  • Startup time and satellite acquisition are really fast
  • Position accuracy is much better than my old SporTrak
  • Mac support in general is good, but MapSource maps still need to be converted on a Windows PC first before they can be used
  • Lots of advanced functionalities (maybe even just a bit too much)
  • Certain tasks, such as entering a waypoint are not as easy, clear and quick as on my old SporTrak
  • The electronic compass is not as simple to use as the one on my old SporTrak
So we'll have to see what the future brings. After some jiggling around with the battery connectors on my old SporTrak it seems to be working a bit better again. The GPSMAP 60CSx will become my new primary GPS device because I don't want to be geocaching somewhere far away from civilisation and have my GPS die on me, but I'll keep the SporTrak as a backup in case I drop the Garmin off a cliff.

Tuesday, 13 October 2009

Barbie pink network cable

What's wrong with this picture of the network cable that's currently plugged in to my Mac at a customers' offices?


It's pink! Not a soft pastel pink or even hot pink, bloody Barbie pink. While I do understand the value of color coding network/patch cables so you don't get lost in your own racks, Barbie pink ones are one step too far. And after seeing these cables I think all hope is lost.

Monday, 12 October 2009

I do code in my free time

A few days ago I read a blog post by Ted Dziuba: I Don't Code in my Free Time. This post has been reverberating around the blogosphere quite a bit and popped up on a lot of different programming related sites and even on Reddit and I have to say that I disagree.

I'm not a 20-something anymore since this year, but in my opinion age doesn't automatically mean you're right. It's not because you're older that you know better then somebody that's younger. And why couldn't a programmer have a valid opinion about hiring?

My short experience so far tells me that the current hiring procedures for programmers are sadly lacking a lot of oomph. This certainly is the case with a lot of the recruitment companies that wouldn't be able to tell you the difference between an Apache HTTP server and an Apache Tomcat server if their life depended on it. Mostly it's a lot of talk about anything but actual coding skill, but my opinion about how I would hire programmers will be the subject of a future blog post.

While I certainly wouldn't go as far as not hiring someone that doesn't code in his free time, I do think it can be a good additional indicator, especially if it involves a different programming language than is used at your company. It doesn't have to be much and it doesn't have to be coding per se.

Besides coding it could also be reading, going to conferences, posting on forums or maintaining a blog. Everyone of these activities does not only improve your skills, it also gives the interviewer some extra area's to ask questions about that wouldn't come up in an interview otherwise. It is also a way to distinguish yourself from other candidates, be someone unique and show that you care about your craft.

As you can probably tell by now: I do code in my own time. I made and maintain a couple of small websites, I try to blog a bit and I also code some stuff to support one of my other hobbies (I still have time after all the coding, no kids you know): geocaching. Not only do I code for my own, I also code in my own time for work-related stuff, usually when I get some kind of eureka moment at home about how to solve a problem. If I'd waited until I'm back at work the idea would have vanished.

Wednesday, 7 October 2009

Hosting a custom maven archetype in Artifactory

Today we needed to set up a an in-house Maven 2 repository. The product we chose for this task is Artifactory from JFrog. Setting up this product will maybe be a separate blog post sometime, but for now the focus of this post will be on getting Artifactory to serve a custom Maven archetype.

After creating your archetype you'll first need to install it to your new Maven repository. This can easily be done using the Artifactory web interface. Once this is done you'll need to create an archetype-catalog.xml file that describes your new archetype using these rules:




your.group.id
your.artifact.id
1.0
http://your.artifactory.server:8081/artifactory/libs-releases/local



Once this is done you'll need to get Artifactory to serve this file. As there is no direct GUI support for this in the web interface, we'll need to use a workaround for this:

curl -u admin:password -f -T /path/to/archetype-catalog.xml -X PUT "http://your.artifactory.server:8081/artifactory/libs-releases-local/archetype-catalog.xml"

Once this is done you'll be able to define a remote archetype catalog with this URL:

http://your.artifactory.server:8081/artifactory/libs-releases-local

You can also easily use it in combination with the m2eclipse plugin:



Atlassian rules

One of my favorite software companies, Atlassian, has just announced that they're reviving their Starter program and that this time it is here to stay. Under the terms of the starter program you can get licenses for up to six products at 10$ a piece. If that isn't a bargain I don't know what is. And they're not shortchanging the buyers, since you'll get:
  • a full featured product
  • 1 year of support and maintenance
  • you'll be able to renew the support each year for 10$
  • an easy to follow install guide, Here be Dragons, with the promise of getting a unique t-shirt when you complete the quest
  • some freebies to complement the products (although a voluntary 10$ donation to Room to Read is encouraged as the 10$ of the other product are also donated in full to this charity - way to go Atlassian!)
The six products to choose from are:
  • Jira: an excellent issue tracker, that I've been using on and off for the last 7 years
  • Confluence: a great wiki/content sharing product, which I also have been using for about 2 years now
  • GreenHopper: an interesting Jira plugin that supports the agile methodology that the company I work for has tested for some time and is now using in production
  • Bamboo: looks like a full-featured continuous integration server that we could use instead of Hudson
  • FishEye: I think our current company CVS server is in dire need for this product. Maybe I should propose this sometime to our CTO
  • Crowd: this is a SSO/IDM product that we're currently evaluating as a less complicated replacement for Sun IDM when not all the bells and whistles of Sun IDM are needed
So even though these licenses are only for 10 users/plans/committers (50 users in the case of Crowd) they are perfect for small teams or even if you're tinkering on your own at home. Confluence, Jira and Bamboo for 10$ a pop are sounding very interesting to use on my own Skunk works.