Archive for April, 2011

Apache-based authentication for Seaside

I recently posted about a completely Smalltalk-based solution for LDAP authentication for Seaside applications. After I got that working for my application, I realized that once I submitted my application to our security group here on campus for review, it would never pass inspection due to the fact that user passwords are accessible to my code. I can imagine many scenarios where programmer access to user credentials is unacceptable.

Fortunately, our university provides its own kerberos-based authentication solution for applications called Bluestem. It provides the user first with a screen to enter their username, then a second screen to enter their university kerberos password. Upon successful authentication, the user is given access to the proper resources on the server. Bluestem is written in Perl and there exists an Apache module to integrate it with web applications. I previously set up the server I plan to deploy my Seaside application on for Bluestem authentication for other web applications that I host on it, but I did not know of a way that I could use this type of authentication with Seaside since I was planning on using mod_proxy to proxy requests from my external-facing Apache server to my headless Pharo VM.

While I was researching how to do this, I came across an Apache module that someone has posted on Github at https://github.com/aimxhaisse/mod-proxy-add-user which takes the authenticated user name set by Apache and forwards it on to servers being proxied. I was able to compile it (manually, the Makefile appeared to be broken) and get it up and running on my CentOS server within a few minutes. I configured my Apache configuration file with:

<Proxy *>
    Order deny,allow
    Allow from all
    ProxyAddUser On
    ProxyAddUserKey "HTTP_X_FORWARDED_USER"
</Proxy>

And low and behold, I was able to access the authenticated user name in my proxied Seaside application with:

username := self class decodeUserIdFrom: (self requestContext request headerAt: 'http_x_forwarded_user' ifAbsent: [username := 'cemeyer2']).

Let me explain a little bit. In most cases, just self requestContext request headerAt: ‘http_x_forwarded_user’ will give you the authenticated user name, but our university’s kerberos solution sets this variable to ‘username@uiuc.edu/kerberos’, so the decodeUserIdFrom message strips off everything but the user name. The above line also is useful for when accessing the application when not being proxied behind university authentication (such as during development), as it will default to return my user name if the header is not found.

Integrating LDAP Authentication with Seaside

For one of my projects at school, I am building an application using the Seaside web development framework. The project will eventually become a piece of courseware for a class I teach, and as such has requirements for authentication. I wanted to use our university’s authentication system to accomplish this. It took a fair amount of digging through the Seaside source code, and following the dead ends that the documentation provided (for example, WAAuthMain doesn’t even exist).

I decided to use the smalltalk LDAPLayer package to allow me to do what I needed to do. First order of business was to set up a way to do encrypted connections to the LDAP server. The LDAPLayer package does not support SSL connections out of the box (as far as i could tell), so rather than try to modify the source to allow it, I decided to run a local stunnel instance on my machine to provide a SSL proxy to the directory:


sudo stunnel -c -d 389 -r ad.uiuc.edu:636

I then can connect to localhost on the regular LDAP port, 389, and it will be securely tunneled to our campus’ Active Directory cluster.

The next step was to write an authenticator that would do the actual authentication. Mine turned out to look something like:

LDAPAuthenticator>>#verifyPassword: aPassword forUser: aUsername
	| dn req tempConn |
	aPassword ifNil: [^false].
	aPassword ifNil: [^false].
	dn := self getDNForUsername: aUsername .
	tempConn := LDAPConnection
		to: self class hostname
		port: self class port.
	req := tempConn
		bindAs: dn
		credentials: aPassword.
	[req wait] on: LDAPException do: [ :exception | tempConn disconnect. ^false ].
	tempConn disconnect.
	^true

I also implemented LDAPAuthenticator>>#getDNForUsername which converts a username into a fully qualified distinguished name for binding to the directory with. In addition, LDAPAuthenticator class>>#hostname and LDAPAuthenticator class>>#port return localhost and 139 respectively. It is important to note that the message implemented has to exactly match #verifyPassword:forUsername: and the message also needs to respond with false if either are nil (it was a bug that threw me off for a bit). Then its simply a matter of tying it all together in the initialize method of the application’s root component:

initialize
	"self initialize"
	| app authFilter |
	app := (WAAdmin register: self asApplicationAt: 'mypath')
		addLibrary: PTDeploymentLibrary;
		addLibrary: SUDeploymentLibrary;
		addLibrary: CTLibrary;
		yourself.
	app preferenceAt: #sessionClass put: MyCustomSession.
	authFilter := WAAuthenticationFilter new.
	authFilter realm: 'Active Directory. Use your username and Active Directory Password'.
	authFilter authenticator: LDAPAuthenticator new.
	app addFilter: authFilter .

Once that is complete, authentication works like a charm. Of course I could modify the authenticator to only allow members of certain groups, etc, but for my needs, letting anyone with valid credentials is quite alright. I also wanted to tie a user object representing the logged in user to my custom session, so I did the following to get the username of the authenticated user in my overridden #initialrequest in my root component:

username := self requestContext request user.

That took care of the whole mess for me…

Notes on using comet with Seaside

I’ve been using the Seaside web development framework for a project at school for the past few weeks and overall have found it to be quite enjoyable. Its amazing how quickly I was able to get a fairly complex web application up and running, especially considering all of the advanced JavaScript that I wanted to include in the project. I read the Seaside book to learn how to integrate the “pushiness” of comet into my project and hit a bit of a rocky road when I tried to take the simple examples in the book as well as in the Comet-Examples package that comes with Seaside and try to adopt them for use in my application.

Issue #1: Page never appears to finish loading

I was able to get basic comet up and running rather quickly, but I noticed when I loaded the page, the browser would continue to appear as if the page was constantly loading. This was because of the way that the Seaside book instructs you to add the comet script to your page. The book example code looks like:

html script: (html comet
     pusher: self class pusher;
     connect)

Assuming:

MyComponent class>>#pusher
     ^pusher ifNil: [ pusher := CTPusher new ]

What this actually does is place a block of JavaScript code in the generated HTML that the browser immediately executes in the foreground when it encounters it. Due to the nature of the comet implementation that Seaside provides (streaming), the browser immediately opens up an asynchronous connection to the Seaside server and streams from it, stopping the browser from doing anything else. This is why it appears to constantly be loading. The proper way to add comet to a Seaside application is as follows:

html document
    addLoadScript: (html comet
    pusher: self class pusher;
    connect)

This causes the browser to start up the comet streaming connection when the page has finished loading, and in the background. Thus the page will appear to the end user as if it is completely loaded, even though the streaming comet connection is open.

Issue #2: Using comet with a custom session

I still haven’t solved this error yet, but hopefully I will soon, so while I wait to hear back from the Seaside mailing list, I’ll describe the kludge I put in to make it work right.

For my application, I needed a custom subclass of WASession to hold on to information about the logged in user. Unfortunately, when I used this session class with comet, things broke. On the first request of a new session, I would constantly get stacktraces like:

Comet Error Stacktrace With Custom Session

It appears as if the parent of my custom session was never being set to the WAApplication instance that it should have been. It only happened on the first request of the session, and all subsequent comet interactions worked as expected. I thought I could fix this problem by overriding initialize in my custom session, but that did not work:

initialize
    ^super initialize

So, I posted my question to the Seaside mailing list and am still awaiting a response. In the meantime, I have added a “patch” to CTHandler>>notifySession to change it from:

notifySession
    self session application cache
        notifyRetrieved: self session
        key: self session key

to:

notifySession
    [self session application cache
        notifyRetrieved: self session
        key: self session key]
    on: MessageNotUnderstood do: [:error | ]

This basically attempts to run the old notifySession code, but when the MessageNotUnderstood error is raised, it simply ignores it and continues on with execution. I know this is not the best practice, but it does make the application work as expected.

Issue #3: Duplicated JavaScript code sent to clients

I didn’t notice this bug until I used FireBug and took a look at the JS that Seaside was generating from my Smalltalk code and streaming to the clients. What I found out, much to my surprise, was that every single JavaScript action that I had coded once in SmallTalk was being sent over the wire twice, both identical. This took a bit to trace down, but again it had to do with inadequate examples from the Seaside book. The Seaside book makes use of jQuery for all of its work with comet (as do the comet examples that come with Seaside itself), and I had chosen to use Prototype/Scriptaculous for my application, so this is where the issue might have arisen.

Say you wanted to push some JavaScript that would set the contents of the div element with id=’peanut’ to be ‘butter’. The Seaside book and examples led me to believe that using Scriptaculous/Prototype, the code should look like:

pushToClient
    self class pusher
        javascript: [ :script |
            script <<  (script scriptaculous element
                id:'peanut';
                update: 'butter').
        ].

But this actually causes the div to be updated twice in rapid succession, both to the same thing. It was completely unnoticeable on the client side, but it was extra overhead being executed on the client browser as well as extra unnecessary data being sent over the wire.

Thanks to the fantastic debugger built into Pharo, I was able to trace down the problem and rewrite the above code as follows:

pushToClient
    self class pusher
        javascript: [ :script |
            script scriptaculous element
                id:'peanut';
                update: 'butter'.
        ].

The script variable is an instance of a JSScript object, and the #<< message means to append the argument onto its list of JavaScript statements. What I found was that when using the JSScript>>scriptaculous message, it was already appending the generated JavaScript statements onto the JSScript, so the #<< message was simply redundant. Eliminating all usages of #<< in my comet code fixed the error and everything works as expected now.

Issue #4: Conditionally pushing to clients

In my application, I have regular users and administrators. They are defined as such based on what type of object is attached to the custom session I wrote for the project. There are certain elements on my pages that only appear for regular users and certain elements that only appear for administrators, as well as a large number of elements that appear in both. I wanted to use a single comet pusher to be able to push to both regular users as well as administrators, to reduce connection overhead in the final deployed version of the application. Having a separate CTPusher for regular users and a separate one for administrators would have worked, but would have been painful. I wanted something a little cleaner. So I did some hacking on the comet source that comes with seaside and this is what I came up with (added):

javascript: aBlock forHandlers: handlerBlock
	"Evaluate aBlock with an JSScript instance and pushes the resulting script-string aString to
	handlers which when evaluated as the argument to handlerBlock causes it to return true."

	| script |
	script := (JSScript context: self renderContext)
		rendererClass: self rendererClass;
		yourself.
	aBlock value: script.
	self push: (String streamContents: [ :stream |
		stream
			nextPutAll: '<script type="text/javascript">parent.Comet.eval(';
			javascript: script asJavascript;
			nextPutAll: ')</script>' ])
		forHandlers: handlerBlock

And:

push: aString forHandlers: aBlock
	"Push aString to handlers which when passed as the argument to aBlock causes
	it to return true."
	| pushHandlers |
	pushHandlers := handlers select: aBlock.
	self mutex critical: [ pushHandlers do: [ :each | each push: aString ] ]

I do realize that this does not do exactly what the plain CTHandler>>#push message does. Namely, it does not prune down the handlers list to ensure that only the connected ones continue to receive updates from the server, but in my application I do mostly regular pushes with only a few requiring the handlers block, so I feel like I am pretty much safe for now. So now, using the code I added, if I want to update the div on the page with id ‘peanut’ to contain the text ‘butter’ for regular users, but to ‘jelly’ for administrators, its quite simple:

pushToClient
    self class pusher
        javascript: [ :script |
            script scriptaculous element
                id:'peanut';
                update: 'butter'.
        ] forHandlers: [ :handler | handler session user isRegularUser ].
    self class pusher
        javascript: [ :script |
            script scriptaculous element
                id:'peanut';
                update: 'jelly'.
        ] forHandlers: [ :handler | handler session user isAdministrator ].

So those are my notes on using comet with Seaside thus far. Overall its been a lot of fun watching something really complex come together in such a short period of time. Next, when I get time, I’ll post about how I was able to get custom basic authentication working using LDAP in Seaside.