RADIUS authentication patch

Demus

Joined: 2007-06-13
Posts: 1
Posted: Wed, 2007-06-13 22:48

I've done a quick hack to allow authentication to be passed seamlessly to a RADIUS server. It requires the radius PHP module. http://pecl.php.net/package/radius

The goal was to deploy Gallery 2 as a means of sharing photos amongst employees of my company. Many of these employees find it very difficult to remember/keep track of multiple passwords, and as a result would never use the service. With this patch they can simply use their Active Directory usernames and passwords.

The brief overview:
The login routine first attempts to authenticate in the normal way. If that fails, authentication is attempted with the RADIUS server. If that succeeds then the saved password (in the Gallery database) is updated with the supplied one, and authentication continues on to the validation plugins. If RADIUS authentication is successful but there is no existing user account then one is created.

SECURITY WARNING!!!!!!!!!!
The username and password are transmitted in plain text from the browser to the server. This is normally not a major concern, because a compromised account normally only provides access to an image gallery. If you're using RADIUS authentication then a compromise could mean access to whatever is secured by your RADIUS server! For this reason this patch should only be used on SSL-forced servers.

The second security warning is that the MD5 hashes for these passwords are stored in the local SQL database. This is done to speed up requests, and to prevent many RADIUS requests. If the security of your SQL database is questionable, or you connect to it with an unencrypted socket then you should comment out the changePassword lines.

Future developments: A configurable module would elevate this patch from "hack" status. Currently RADIUS server configs are loaded from a php file. Also configuration options to decide the level of RADIUS authentication (full-time or when the local MD5s don't match) would be nice.

UserLevel.inc.path

--- UserLogin.inc.orig	2007-03-09 02:36:19.000000000 -0700
+++ UserLogin.inc	2007-06-13 12:29:57.000000000 -0600
@@ -88,6 +88,137 @@
 		GalleryUtilities::unsanitizeInputValues($form['password'], false);
 		$isCorrect = (isset($user) && $user->isCorrectPassword($form['password']));
 
+		 /* RADIUS authentication v0.0.1 - By Demus	*
+		  * Requires php_radius								*
+		  * Authenticates a username & password against a   *
+		  * radius server. If successful either updates the *
+		  * hashed password, or creates a new user account. */
+		  
+		 if (!$isCorrect) {
+				
+			// Load radius configs
+			define ("IN_RADIUS_INIT", 1);
+			require(dirname(__FILE__) . '/../../radius_config.php');
+			
+			// Create radius handle and add radius server
+			if (!($radh = radius_auth_open()) || !radius_add_server ($radh, $radius_hostname, $radius_port, $radius_secret, 15, 1))
+			{	
+				// Destroy sensitive information
+				unset ($radius_hostname, $radius_port, $radius_secret);
+									
+				// Create error information
+				$results['delegate']['view'] = 'core.UserAdmin';
+				$results['delegate']['subView'] = 'core.UserLogin';
+				$results['status'] = array();
+				$error[] = 'form[error][radius][unknown]';
+				$results['error'] = $error;
+
+				return array(null, $results);
+			}
+			
+			// Destroy sensitive information
+			unset ($radius_hostname, $radius_port, $radius_secret);
+			
+			// Create authentication request
+			if (!radius_create_request($radh,RADIUS_ACCESS_REQUEST))
+			{
+				$results['delegate']['view'] = 'core.UserAdmin';
+				$results['delegate']['subView'] = 'core.UserLogin';
+				$results['status'] = array();
+				$error[] = 'form[error][radius][unknown]';
+				$results['error'] = $error;
+
+				return array(null, $results);
+			}
+			
+			// Attach username and password
+			radius_put_attr($radh,RADIUS_USER_NAME,$form['username']);
+			radius_put_attr($radh,RADIUS_USER_PASSWORD,$form['password']);
+			
+			// Send request to the server
+			switch (radius_send_request($radh))
+			{
+				case RADIUS_ACCESS_ACCEPT:
+					$isCorrect = true;
+					break;
+				default:
+					$isCorrect = false;
+			}			
+			
+			// If successfull and user does not exist, create it
+			if ($isCorrect && !isset($user))
+			{
+				list ($ret, $user) = GalleryCoreApi::newFactoryInstance('GalleryEntity', 'GalleryUser');
+					
+				if ($ret || !isset($user) || $user->create($form['username'])) 
+				{
+					// Set our error status and fall back to the view
+					$results['delegate']['view'] = 'core.UserAdmin';
+					$results['delegate']['subView'] = 'core.UserLogin';
+					$results['status'] = array();
+					$error[] = 'form[error][radius][unknown]';
+					$results['error'] = $error;
+
+					return array(null, $results);
+				}
+				
+				// Assume username@domain - user can update later if need be
+				$user->setEmail($form['username'] . '@' . $radius_domain);
+				
+				// Check for first.last naming convention, otherwise just use username
+				if (strpos($form['username'], '.') === FALSE)
+					$user->setFullName (ucfirst($form['username']));
+				else
+					$user->setFullName( ucfirst(substr($form['username'], 0, strpos($form['username'], '.'))) 
+									. ' ' . 
+									ucfirst(substr($form['username'], strpos($form['username'], '.')+1)));
+									
+				// Set language to english
+				// Fix me: Should set to default language setting
+				$user->setLanguage('en_US');
+				
+				// WARNING: Possible security risk. Compromise of Gallery database
+				//			could lead to compromise of RADIUS security.
+				$user->changePassword($form['password']);
+
+				if ($user->save()) {
+					$results['delegate']['view'] = 'core.UserAdmin';
+					$results['delegate']['subView'] = 'core.UserLogin';
+					$results['status'] = array();
+					$error[] = 'form[error][radius][unknown]';
+					$results['error'] = $error;
+
+					return array(null, $results);
+				}
+			} else if ($isCorrect) // Update hashed password
+			{
+				// WARNING: Possible security risk. Compromise of Gallery database
+				//			could lead to compromise of RADIUS security.			
+				list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($user->getId());
+
+				if (!$ret)
+				{
+					$user->changePassword($form['password']);
+					$ret = $user->save();
+					GalleryCoreApi::releaseLocks($lockId);
+				} 					
+
+				if ($ret)
+				{
+					$results['delegate']['view'] = 'core.UserAdmin';
+					$results['delegate']['subView'] = 'core.UserLogin';
+					$results['status'] = array();
+					$error[] = 'form[error][radius][unknown]';
+					$results['error'] = $error;
+
+					return array(null, $results);
+				}
+
+			}
+			// Continue with login
+		}
+		/* End RADIUS authentication */		
+
 		/* Prepare for validation */
 		$options = array('pass' => $isCorrect);
 		list ($ret, $options['level']) =

radius_config.php (place in gallery root folder)

<?php
        if (!defined("IN_RADIUS_INIT"))
                die ("Hacking attempt!");

        $radius_hostname="radiusserver.yourhost.com";
        $radius_port=1812;
        $radius_secret="somesecret";
        $radius_domain="yourhost.com";
?>

UserLogin.tpl.patch (if you want the RADIUS error message to be displayed)

--- UserLogin.tpl.orig  2006-12-01 14:14:46.000000000 -0700
+++ UserLogin.tpl       2007-06-13 16:46:37.000000000 -0600
@@ -54,6 +54,11 @@
     {g->text text="Your login information is incorrect.  Please try again."}
   </div>
   {/if}
+  {if isset($form.error.radius.unknown)}
+  <div class="giError">
+    {g->text text="An error occurred with the RADIUS server. Please try again."}
+  </div>
+  {/if}
 </div>

 {* Include our ValidationPlugins *}