Posts Tagged ‘ LDAP

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…

Authenticating against UIUC Active Directory (AD) using Python

So in a follow up to my earlier post, here is the equivalent code to authenticate against UIUC Active Directory using Python:

ldap_auth.py:

import ldap

def authenticate(netid, password):
    server = "ldaps://ad.uiuc.edu:636"
    who = "CN="+netid+",OU=Campus Accounts,DC=ad,DC=uiuc,DC=edu"
    try:
        conn = ldap.initialize(server)
        ldap.set_option( ldap.OPT_X_TLS_DEMAND, True )
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_NEVER)
        result = conn.simple_bind_s(who,password)
    except ldap.LDAPError, e:
        return False
    conn.unbind()
    return True

print authenticate("cemeyer2", "password")

That’s it. As mentioned in my earlier post, I’m pretty sure that any application that authenticates this way would be looked down upon by CITES since the application in question would potentially be able to sniff user login information, so please only use this code in a personal testing environment, not for a deployed application unless approved by people much higher up than me.