2014-11-24 16:19 EST

View Issue Details Jump to Notes ] Wiki ]
IDProjectCategoryView StatusLast Update
0004235mantisbtauthenticationpublic2014-11-07 15:27
Reportervboctor 
Assigned To 
PrioritynormalSeverityfeatureReproducibilityalways
StatusacknowledgedResolutionopen 
Product Version 
Target VersionFixed in Version 
Summary0004235: Support Generic Authentication through Plug-ins
DescriptionDefine an API which can be implemented to allow Mantis to use a certain kind of authentication.
TagsNo tags attached.
Attached Files
  • patch file icon pear_auth.patch (32,592 bytes) 2007-07-18 05:15 - 
    --- admin/install.php	Mon Feb 19 23:05:04 2007
    +++ admin/install.php	Wed Jul 18 00:55:32 2007
    @@ -8,8 +8,8 @@
     	# --------------------------------------------------------
     	# $Id: install.php,v 1.31 2007/02/20 06:05:03 vboctor Exp $
     	# --------------------------------------------------------
    -?>
    -<?php
    +	$g_login_allowed = false;
    +
     	error_reporting( E_ALL );
     
     	//@@@ put this somewhere
    @@ -99,9 +99,12 @@
     		<td class="title">
     		<?php
     			switch ( $t_install_state ) {
    -				case 6:
    -					echo "Post Installation Checks";
    -					break;
    +				case 7:
    +					echo "Add administrator user";
    +					break;
    +				case 6:
    +					echo "Post Installation Checks";
    +					break;
     				case 5:
     					echo "Install Configuration File";
     					break;
    @@ -687,8 +690,6 @@
     	</td>
     </tr>
     
    -<!-- Checking MD5 -->
    -<?php print_test( 'Checking for MD5 Crypt() support', 1 === CRYPT_MD5, false, 'password security may be lower than expected' ) ?>
     
     <!-- Checking register_globals are off -->
     <?php print_test( 'Checking for register_globals are off for mantis', ! ini_get_bool( 'register_globals' ), false, 'change php.ini to disable register_globals setting' ) ?>
    @@ -773,14 +774,63 @@
     		}
     	?>
     </tr>
    -</table>
     <?php
     	if ( false == $g_failed ) {
     		$t_install_state++;
     	}
     }  # end install_state == 6
     
    -if ( 7 == $t_install_state ) {
    +if ( 7 == $t_install_state ) {
    +?>
    +<tr>
    +	<td bgcolor="#ffffff">
    +	Initializing 'administrator' user
    +	</td>
    +<?php
    + /*
    +     "(username, realname, email, password, date_created, last_visit, enabled, protected, access_level, login_count, lost_password_request_count, failed_login_count, cookie_string) VALUES 
    +        ('administrator', '', 'root@localhost', '63a9f0ea7bb98050796b649e85481845', " . db_now() . ", " . db_now() . ", 1, 0, 90, 3, 0, 0, '" . 
    +             md5( mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() ) ) . md5( time() ) . "')" ) );
    + */
    +    $pearauth = auth_api_initialize_auth_object();
    +    
    +	$query = "DELETE FROM mantis_user_table
    +			  WHERE username='administrator';";
    +	db_query( $query );
    +	
    +	$t_result =	user_create( 
    +		'administrator',    #user name 
    +		'', 				#passwprd
    +		'',                 #email
    +		90,    			    # access level
    +		false,              # protected
    +		true,               # enabled
    +		'');   				#real name
    +	
    +	if ( $t_result != false ) {
    +		print_test_result( GOOD );
    +	} else {
    +		print_test_result( BAD, true, 'could not create user through the user API' );
    +	}
    +	if($t_result != false)
    +	{
    +		$g_failed = false;
    +	}
    +	else
    +	{
    +		$g_failed = true;
    +	}
    +?>
    +</tr>
    +</table>
    +<?php
    +	if ( false == $g_failed ) {
    +		$t_install_state++;
    +	}
    +}  # end install_state == 7
    +
    +
    +if ( 8 == $t_install_state ) {
     # cleanup and launch upgrade
     ?>
     <p>Install was successful.</p>
    --- admin/schema.php	Thu Mar 15 22:13:46 2007
    +++ admin/schema.php	Wed Jul 18 00:57:09 2007
    @@ -309,10 +309,13 @@
     $upgrade[] = Array('CreateIndexSQL',Array('idx_user_username',config_get('mantis_user_table'),'username',Array('UNIQUE')));
     $upgrade[] = Array('CreateIndexSQL',Array('idx_enable',config_get('mantis_user_table'),'enabled'));
     $upgrade[] = Array('CreateIndexSQL',Array('idx_access',config_get('mantis_user_table'),'access_level'));
    -$upgrade[] = Array('InsertData', Array( config_get('mantis_user_table'), 
    +/* I had to remove this so that PEAR::Auth could hash the password */
    +/* 
    + $upgrade[] = Array('InsertData', Array( config_get('mantis_user_table'), 
         "(username, realname, email, password, date_created, last_visit, enabled, protected, access_level, login_count, lost_password_request_count, failed_login_count, cookie_string) VALUES 
             ('administrator', '', 'root@localhost', '63a9f0ea7bb98050796b649e85481845', " . db_now() . ", " . db_now() . ", 1, 0, 90, 3, 0, 0, '" . 
                  md5( mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() ) ) . md5( time() ) . "')" ) );
    +*/
     $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "old_value C(255) NOTNULL" ) );
     $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "new_value C(255) NOTNULL" ) );
     
    --- core/adodb/datadict/datadict-postgres.inc.php	Sat Apr 22 04:35:06 2006
    +++ core/adodb/datadict/datadict-postgres.inc.php	Wed Jul 18 01:06:22 2007
    @@ -103,7 +103,20 @@
     		case 'D': return 'DATE';
     		case 'T': return 'TIMESTAMP';
     		
    -		case 'L': return 'BOOLEAN';
    +		case 'L': 
    +			/*
    +				#
    +				# postgres 8.3 expected quotes
    +				# around the val.  the easiest way
    +				# to work was to change to a 
    +				# numeric value. I had a very 
    +				# hard time trying to figure out
    +				# how to supply quotes arround the 
    +				# value, so I went the other way
    +				# 
    +				return 'BOOLEAN'; 
    +			*/
    +			return 'SMALLINT';
     		case 'I': return 'INTEGER';
     		case 'I1': return 'SMALLINT';
     		case 'I2': return 'INT2';
    --- core/authentication_api.php	Sun Apr 23 05:33:00 2006
    +++ core/authentication_api.php	Wed Jul 18 02:00:02 2007
    @@ -8,14 +8,67 @@
     	# --------------------------------------------------------
     	# $Id: authentication_api.php,v 1.55 2006/04/23 12:32:59 vboctor Exp $
     	# --------------------------------------------------------
    -
    +    require_once "Auth/Auth.php";
    +    require_once "string_api.php";
    +    require_once "print_api.php";
     	### Authentication API ###
    -
    -	$g_script_login_cookie = null;
    -	$g_cache_anonymous_user_cookie_string = null;
    -	$g_cache_current_user_cookie_string = null; 
    -	$g_cache_cookie_valid = null;
    -
    +	
    +	#
    +	# this function is called when a->start(); decides that a login is needed
    +	#
    +	function pearauth_login_redirector( $username, $status, $auth) {
    +		/* keep us from looping infinatly */
    +		global $on_login_page;
    +		if(isset($on_login_page))
    +		{
    +			if($on_login_page == true)
    +			{
    +				return;
    +			}
    +		}
    +		
    +		if ( !php_version_at_least( '4.1.0' ) ) {
    +			global $_SERVER;
    +		}
    +		
    +		$p_return_page = $auth->return_page;		
    +		if(is_blank($p_return_page)){
    +			if (!isset($_SERVER['REQUEST_URI'])) {
    +					$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
    +				}
    +				$p_return_page = $_SERVER['REQUEST_URI'];
    +		}
    +		$p_return_page = string_url( $p_return_page );
    +		print_header_redirect( 'login_page.php?return=' . $p_return_page );
    +	}
    +
    +    function auth_api_initialize_auth_object()
    +    {
    +    global $g_login_allowed;
    +    global $g_db_type;
    +    global $g_db_username;
    +    global $g_db_password;
    +    global $g_hostname;
    +    global $g_database_name;
    +    
    +        
    +    $pearauthDriver  = "DB";
    +    $pearAuthOptions = array(
    +  		'dsn'            => "$g_db_type://$g_db_username:$g_db_password@$g_hostname/$g_database_name",
    +  		'table'          => "mantis_user_table",
    +        'usernamecol'    => "username",
    +        'passwordcol'    => "password",
    +        'sessionName'    => "mantis" 
    +  		);
    +    
    +    if(!isset($g_login_allowed)) $g_login_allowed=true;
    +    $pearauth = new Auth($pearauthDriver, $pearAuthOptions, "pearauth_login_redirector",$g_login_allowed);
    +    $pearauth->return_page ='';
    +	return $pearauth;
    +    }
    +    
    +    $pearauth = auth_api_initialize_auth_object();
    +    $pearauth->start();
     	#===================================
     	# Boolean queries and ensures
     	#===================================
    @@ -38,26 +91,21 @@
     			if ( OFF == current_user_get_field( 'enabled' ) ) {
     				print_header_redirect( 'logout_page.php' );
     			}
    -		} else { # not logged in
    -			if ( is_blank( $p_return_page ) ) {
    -				if (!isset($_SERVER['REQUEST_URI'])) {
    -					$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
    -				}
    -				$p_return_page = $_SERVER['REQUEST_URI'];
    -			}
    -			$p_return_page = string_url( $p_return_page );
    -			print_header_redirect( 'login_page.php?return=' . $p_return_page );
    -		}
    +		} else { 
    +			# not logged in
    +			# old code had a redirect here.
    +			# hmmmm.... is this legal?
    +			$pearauth->return_page = $p_return_page;
    +			$pearauth->start();
    +		}
     	}
     
     	# --------------------
     	# Return true if there is a currently logged in and authenticated user,
     	#  false otherwise
    -	function auth_is_user_authenticated() {
    - 		global $g_cache_cookie_valid;
    - 		if($g_cache_cookie_valid)
    - 		  return true;		
    -		return ( auth_is_cookie_valid( auth_get_current_user_cookie() ) );
    +	function auth_is_user_authenticated() {
    +		global $pearauth;
    +		return $pearauth->checkAuth();
     	}
     
     
    @@ -72,123 +120,38 @@
     	#   true is returned.  If $p_perm_login is true, the long-term
     	#   cookie is created.
     	function auth_attempt_login( $p_username, $p_password, $p_perm_login=false ) {
    -		$t_user_id = user_get_id_by_name( $p_username );
    -
    -		$t_login_method = config_get( 'login_method' );
    -
    -		if ( false === $t_user_id ) {
    -			if ( BASIC_AUTH == $t_login_method ) {
    -				# attempt to create the user if using BASIC_AUTH
    -				$t_cookie_string = user_create( $p_username, $p_password );
    -
    -				if ( false === $t_cookie_string ) {
    -					# it didn't work
    -					return false;
    -				}
    -
    -				# ok, we created the user, get the row again
    -				$t_user_id = user_get_id_by_name( $p_username );
    -
    -				if ( false === $t_user_id ) {
    -					# uh oh, something must be really wrong
    -
    -					# @@@ trigger an error here?
    -
    -					return false;
    -				}
    -			} else {
    -				return false;
    -			}
    -		}
    -
    -		# check for disabled account
    -		if ( !user_is_enabled( $t_user_id ) ) {
    -			return false;
    -		}
    -
    -		# max. failed login attempts achieved...
    -		if( !user_is_login_request_allowed( $t_user_id ) ) {
    -			return false;
    -		}
    -
    -		$t_anon_account = config_get( 'anonymous_account' );
    -		$t_anon_allowed = config_get( 'allow_anonymous_login' );
    -
    -		# check for anonymous login
    -		if ( !( ( ON == $t_anon_allowed ) && ( $t_anon_account == $p_username)  ) ) {
    -			# anonymous login didn't work, so check the password
    -
    -			if ( !auth_does_password_match( $t_user_id, $p_password ) ) {
    -				user_increment_failed_login_count( $t_user_id );
    -				return false;
    -			}
    -		}
    -
    -		# ok, we're good to login now
    -
    -		# increment login count
    -		user_increment_login_count( $t_user_id );
    -
    -		user_reset_failed_login_count_to_zero( $t_user_id );
    -		user_reset_lost_password_in_progress_count_to_zero( $t_user_id );
    -
    -		# set the cookies
    -		auth_set_cookies( $t_user_id, $p_perm_login );
    -
    -		return true;
    +		#
    +		#   same thing.   just return if we are not logged in
    +		#
    +		global $pearauth;
    +		return $pearauth->checkAuth();
     	}
     
     	# --------------------
     	# Allows scripts to login using a login name or ( login name + password )
    -	function auth_attempt_script_login( $p_username, $p_password = null ) {
    -		global $g_script_login_cookie, $g_cache_cookie_valid, $g_cache_current_user_id, $g_cache_current_user_cookie_string;
    -
    -		$t_user_id = user_get_id_by_name( $p_username );
    -
    -		$t_user = user_get_row( $t_user_id );
    -
    -		# check for disabled account
    -		if ( OFF == $t_user['enabled'] ) {
    -			return false;
    -		}
    -
    -		# validate password if supplied
    -		if ( null !== $p_password ) {
    -			if ( !auth_does_password_match( $t_user_id, $p_password ) ) {
    -				return false;
    -			}
    -		}
    -
    -		# ok, we're good to login now
    -
    -		# With cases like RSS feeds and MantisConnect there is a login per operation, hence, there is no
    -		# real significance of incrementing login count.
    -		# increment login count
    -		# user_increment_login_count( $t_user_id );
    -
    -		# set the cookies
    -		$g_script_login_cookie = $t_user['cookie_string'];
    -		$g_cache_current_user_cookie_string = $g_script_login_cookie;
    -
    -		# cache user id for future reference
    -		$g_cache_current_user_id = $t_user_id;
    -		$g_cache_cookie_valid = true;
    -
    -		return true;
    +	function auth_attempt_script_login( $p_username, $p_password = null ) {
    +		#
    +		# we are unable to authenticate through this method anymore
    +		#  username and password are picked by the auth module from the 
    +		#  get/set scripts.  Anythin using this should be depreciated
    +		#
    +		global $pearauth;
    +		return $pearauth->checkAuth();
     	}
     
     	# --------------------
     	# Logout the current user and remove any remaining cookies from their browser
     	# Returns true on success, false otherwise
    -	function auth_logout() {
    -        global $g_cache_current_user_id;
    -        
    -        # clear cached userid
    -        $g_cache_current_user_id = null;
    -        
    -        # clear cookies, if they were set  
    -        if (auth_clear_cookies()) {
    -            helper_clear_pref_cookies();
    +	function auth_logout() {
    +		global $pearauth;
    +		if ($pearauth->checkAuth())
    +		{
    +			$pearauth->logout();
    +	        helper_clear_pref_cookies();
    +	    }
    +        else
    +        {
    +			$pearauth->logout();        	
             }
     		return true;
     	}
    @@ -200,71 +163,55 @@
     	# --------------------
     	# Return true if the password for the user id given matches the given
     	#  password (taking into account the global login method)
    -	function auth_does_password_match( $p_user_id, $p_test_password ) {
    -		$t_configured_login_method = config_get( 'login_method' );
    -
    -		if ( LDAP == $t_configured_login_method ) {
    -			return ldap_authenticate( $p_user_id, $p_test_password );
    -		}
    -
    -		$t_password			= user_get_field( $p_user_id, 'password' );
    -		$t_login_methods	= Array(MD5, CRYPT, PLAIN);
    -		foreach ( $t_login_methods as $t_login_method ) {
    -
    -			# pass the stored password in as the salt
    -			if ( auth_process_plain_password( $p_test_password, $t_password, $t_login_method ) == $t_password ) {
    -				# Check for migration to another login method and test whether the password was encrypted
    -				# with our previously insecure implemention of the CRYPT method
    -				if ( ( $t_login_method != $t_configured_login_method ) ||
    -					( ( CRYPT == $t_configured_login_method ) && substr( $t_password, 0, 2 ) == substr( $p_test_password, 0, 2 ) ) ) {
    -					user_set_password( $p_user_id, $p_test_password, true );
    -				}
    +	function auth_does_password_match( $p_user_id, $p_test_password ) {
    +		#
    +		#  this one is used all over the place  :(.....
    +		#   gonna have to try to remove it from the other functions,
    +		#   I am not sure how to do this through pear::AUTH
    +		#   maybe we could trick it out? (This would be a nice thing
    +		#   to add to PEAR::Auth)
    +		#
    +		
    +		$_POST["test_username"] = $p_user_id;
    +		$_POST["test_password"] = $p_test_password;
    +		
     
    -				return true;
    -			}
    -		}
    +	    $tmp_pearauthDriver    = $pearauthDriver;
    +	    $tmp_pearauthOptions   = $pearAuthOptions;
    +		$tmp_pearauthOptions["postUsername"] = "test_username";
    +		$tmp_pearauthOptions["postPassword"] = "test_password";
    + 		$tmp_pearauthOptions["sessionName" ] = "_auth_test";
     
    -		return false;
    -	}
    +		
    + 
    +		function noaction($a,$b,$c){}
    +		
    +		$tempauth = new Auth($tmp_pearauthDriver,$tmp_pearauthOptions,"noaction",false);
     
    -	# --------------------
    -	# Encrypt and return the plain password given, as appropriate for the current
    -	#  global login method.
    -	#
    -	# When generating a new password, no salt should be passed in.
    -	# When encrypting a password to compare to a stored password, the stored
    -	#  password should be passed in as salt.  If the auth method is CRYPT then
    -	#  crypt() will extract the appropriate portion of the stored password as its salt
    -	function auth_process_plain_password( $p_password, $p_salt=null, $p_method=null ) {
    -		$t_login_method = config_get( 'login_method' );
    -		if ( $p_method !== null ) {
    -			$t_login_method = $p_method;
    -		}
    +		/* give our fake data to the function */
    +		$retval =  $tempauth->checkAuth();
     
    -		switch ( $t_login_method ) {
    -			case CRYPT:
    -				# a null salt is the same as no salt, which causes a salt to be generated
    -				# otherwise, use the salt given
    -				$t_processed_password = crypt( $p_password, $p_salt );
    -				break;
    -			case MD5:
    -				$t_processed_password = md5( $p_password );
    -				break;
    -			case BASIC_AUTH:
    -			case PLAIN:
    -			default:
    -				$t_processed_password = $p_password;
    -				break;
    -		}
    +		/* nix our temps. */
    +		unset($tempauth);
    +		unset($_POST["test_username"]);
    +		unset($_POST["test_password"]);
    +		unset($_SESSION["_auth_test"]);
    +		
     
    -		# cut this off to 32 cahracters which the largest possible string in the database
    -		return substr( $t_processed_password, 0, 32 );
    +		return $retval;
     	}
     
     	# --------------------
     	# Generate a random 12 character password
     	# p_email is unused
    -	function auth_generate_random_password( $p_email ) {
    +    # used for lost passwords, when we can set the passwords though the system.
    +	#
    +	function auth_generate_random_password( $p_email ) {
    +		#
    +		# this on should stay.  it is used for creation and resetting of passwords
    +		#  (on platforms that support that)
    +		#
    +	
     		$t_val = mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() );
     		$t_val = md5( $t_val );
     
    @@ -290,7 +237,10 @@
     	# --------------------
     	# Set login cookies for the user
     	#  If $p_perm_login is true, a long-term cookie is created
    -	function auth_set_cookies( $p_user_id, $p_perm_login=false ) {
    +	function auth_set_cookies( $p_user_id, $p_perm_login=false ) {
    +		#
    +		#  used in verify.php
    +		#
     		$t_cookie_string = user_get_field( $p_user_id, 'cookie_string' );
     
     		$t_cookie_name = config_get( 'string_cookie' );
    @@ -304,204 +254,33 @@
     		}
     	}
     
    -	# --------------------
    -	# Clear login cookies, return true if they were cleared
    -	function auth_clear_cookies() {
    -		global $g_script_login_cookie;
    -
    -        $t_cookies_cleared = false;
    -        
    -        # clear cookie, if not logged in from script
    -        if ($g_script_login_cookie == null) {
    -		    $t_cookie_name =  config_get( 'string_cookie' );
    -		    $t_cookie_path = config_get( 'cookie_path' );
    -
    -		    gpc_clear_cookie( $t_cookie_name, $t_cookie_path );
    -            $t_cookies_cleared = true;
    -        } else {
    -            $g_script_login_cookie = null;
    -        }
    -        return $t_cookies_cleared;
    -	}
    -
    -	# --------------------
    -	# Generate a string to use as the identifier for the login cookie
    -	# It is not guaranteed to be unique and should be checked
    -	# The string returned should be 64 characters in length
    -	function auth_generate_cookie_string() {
    -		$t_val = mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() );
    -		$t_val = md5( $t_val ) . md5( time() );
    -
    -		return substr( $t_val, 0, 64 );
    -	}
    -
    -	# --------------------
    -	# Generate a UNIQUE string to use as the identifier for the login cookie
    -	# The string returned should be 64 characters in length
    -	function auth_generate_unique_cookie_string() {
    -		do {
    -			$t_cookie_string = auth_generate_cookie_string();
    -		} while ( !auth_is_cookie_string_unique( $t_cookie_string ) );
    -
    -		return $t_cookie_string;
    -	}
    -
    -	# --------------------
    -	# Return true if the cookie login identifier is unique, false otherwise
    -	function auth_is_cookie_string_unique( $p_cookie_string ) {
    -		$t_user_table = config_get( 'mantis_user_table' );
    -
    -		$c_cookie_string = db_prepare_string( $p_cookie_string );
    -
    -		$query = "SELECT COUNT(*)
    -				  FROM $t_user_table
    -				  WHERE cookie_string='$c_cookie_string'";
    -		$result = db_query( $query );
    -		$t_count = db_result( $result );
     
    -		if ( $t_count > 0 ) {
    -			return false;
    -		} else {
    -			return true;
    -		}
    -	}
    -
    -	# --------------------
    -	# Return the current user login cookie string,
    -	# note that the cookie cached by a script login superceeds the cookie provided by
    -	#  the browser. This shouldn't normally matter, except that the password verification uses
    -	#  this routine to bypass the normal authentication, and can get confused when a normal user
    -	#  logs in, then runs the verify script. the act of fetching config variables may get the wrong
    -	#  userid.
    -	# if no user is logged in and anonymous login is enabled, returns cookie for anonymous user
    -	# otherwise returns '' (an empty string)
    -	function auth_get_current_user_cookie() {
    -		global $g_script_login_cookie, $g_cache_anonymous_user_cookie_string, $g_cache_current_user_cookie_string;
    - 
    -		if( isset( $g_cache_current_user_cookie_string ) ) {
    -			return $g_cache_current_user_cookie_string;
    -		}
    -
    -		# if logging in via a script, return that cookie
    -		if ( $g_script_login_cookie !== null ) {
    -			return $g_script_login_cookie;
    -		}
    -			
    -		# fetch user cookie 
    -		$t_cookie_name = config_get( 'string_cookie' );
    -		$t_cookie = gpc_get_cookie( $t_cookie_name, '' );
    -
    -		# if cookie not found, and anonymous login enabled, use cookie of anonymous account.
    -		if ( is_blank( $t_cookie ) ) {
    -			if ( ON == config_get( 'allow_anonymous_login' ) ) {
    -				if ( $g_cache_anonymous_user_cookie_string === null ) {
    -                    if ( function_exists( 'db_is_connected' ) && db_is_connected() ) { 
    -                        # get anonymous information if database is available
    -                        $query = sprintf('SELECT id, cookie_string FROM %s WHERE username = \'%s\'',
    -								config_get( 'mantis_user_table' ), config_get( 'anonymous_account' ) );
    -                        $result = db_query( $query );
    -                        
    -                        if ( 1 == db_num_rows( $result ) ) {
    -                            $row = db_fetch_array( $result );
    -                            $t_cookie = $row['cookie_string'];
    -
    -                            $g_cache_anonymous_user_cookie_string = $t_cookie;
    -                            $g_cache_current_user_id = $row['id'];
    -                        }
    -                    }
    -                } else {
    -					$t_cookie = $g_cache_anonymous_user_cookie_string;
    -				}
    -			}
    -		}
    -
    -		$g_cache_current_user_cookie_string = $t_cookie;
    -		return $t_cookie;
    -	}
    -
    -
    -	#===================================
    -	# Data Access
    -	#===================================
    -
    -	#########################################
    -	# is cookie valid?
    -
    -	function auth_is_cookie_valid( $p_cookie_string ) {
    -		global $g_cache_current_user_id, $g_cache_cookie_valid;	
    -		
    -	    # fail if DB isn't accessible
    -	    if ( !db_is_connected() ) {
    -			return false;
    -		}
    -
    -	    # fail if cookie is blank
    -	    if ( '' === $p_cookie_string ) {
    -			return false;
    -		}
    -
    -        # succeeed if user has already been authenticated
    -		if ( null !== $g_cache_current_user_id ) {
    -			return true;
    -		}
    -		
    -		# look up cookie in the database to see if it is valid
    -		$t_user_table = config_get( 'mantis_user_table' );
    -
    -		$c_cookie_string = db_prepare_string( $p_cookie_string );
    -
    -		$query = "SELECT id
    -				  FROM $t_user_table
    -				  WHERE cookie_string='$c_cookie_string'";
    -		$result = db_query( $query );
    -
    -		# return true if a matching cookie was found
    - 		$g_cache_cookie_valid = false;
    - 		if( 1 == db_num_rows( $result ) ) {
    - 			$g_cache_cookie_valid = true;
    - 			return ( true );
    - 		}
    -}
    +	function auth_set_password($p_user_id, $p_password)
    +	{
    +		global $pearauth;
    +		if(	true!== $pearauth->changePassword(user_get_name($p_user_id),$p_password))
    +		{
    +			return false;
    +		}
    +		return true;
    +	}
    +	
     	
     	#########################################
     	# SECURITY NOTE: cache globals are initialized here to prevent them
     	#   being spoofed if register_globals is turned on
     	#
     	$g_cache_current_user_id = null;
    -
     	function auth_get_current_user_id() {
    -		global $g_cache_current_user_id;
    -
    -		if ( null !== $g_cache_current_user_id ) {
    -			return $g_cache_current_user_id;
    -		}
    -
    -		$t_user_table = config_get( 'mantis_user_table' );
    -
    -		$t_cookie_string = auth_get_current_user_cookie();
    -
    -		# @@@ error with an error saying they aren't logged in?
    -		#     Or redirect to the login page maybe?
    -
    -		$c_cookie_string = db_prepare_string( $t_cookie_string );
    -
    -		$query = "SELECT id
    -				  FROM $t_user_table
    -				  WHERE cookie_string='$c_cookie_string'";
    -		$result = db_query( $query );
    -
    -		# The cookie was invalid. Clear the cookie (to allow people to log in again)
    -		# and give them an Access Denied message.
    -		if ( db_num_rows( $result ) < 1 ) {
    -			auth_clear_cookies();
    -		    access_denied(); # never returns
    -			return false;
    +		global $pearauth;
    +		global $g_cache_current_user_id;
    +		
    +		if ( null == $g_cache_current_user_id ) {
    +			$g_cache_current_user_id = user_get_id_by_name($pearauth->getUsername());
     		}
     
    -		$t_user_id = (int)db_result( $result );
    -		$g_cache_current_user_id = $t_user_id;
    -
    -		return $t_user_id;
    +        #perhaps we should store all of this on a sucessful login into the auth obj?
    +		return $g_cache_current_user_id;
     	}
     
     	#===================================
    --- core/user_api.php	Mon May 07 13:03:06 2007
    +++ core/user_api.php	Wed Jul 18 01:48:49 2007
    @@ -342,15 +342,15 @@
     	# Create a user.
     	# returns false if error, the generated cookie string if ok
     	function user_create( $p_username, $p_password, $p_email='', $p_access_level=null, $p_protected=false, $p_enabled=true, $p_realname='' ) {
    +		global $pearauth;
    +		
     		if ( null === $p_access_level ) {
     			$p_access_level = config_get( 'default_new_account_access_level');
     		}
     
    -		$t_password = auth_process_plain_password( $p_password );
    -
    +		
     		$c_username		= db_prepare_string( $p_username );
     		$c_realname		= db_prepare_string( $p_realname );
    -		$c_password		= db_prepare_string( $t_password );
     		$c_email		= db_prepare_string( $p_email );
     		$c_access_level	= db_prepare_int( $p_access_level );
     		$c_protected	= db_prepare_bool( $p_protected );
    @@ -362,20 +362,46 @@
     		user_ensure_realname_unique( $p_username, $p_realname );
     		email_ensure_valid( $p_email );
     
    -		$t_seed				= $p_email . $p_username;
    +		$t_seed				= $p_email . $p_username;
    +		
    +		#
    +		# this is for perma-login.... we could use the $pearAuth->setAuth(string $username)
    +		#  to force a login if the cookie is good.
    +		# 
    +
    +		/*
     		$t_cookie_string	= auth_generate_unique_cookie_string( $t_seed );
    +		*/
    +
    +		/* bypass perma-cookie for now*/
    +		$t_cookie_string	= "";
    +		
     		$t_user_table 		= config_get( 'mantis_user_table' );
    -
    -		$query = "INSERT INTO $t_user_table
    -				    ( username, email, password, date_created, last_visit,
    -				     enabled, access_level, login_count, cookie_string, realname )
    -				  VALUES
    -				    ( '$c_username', '$c_email', '$c_password', " . db_now() . "," . db_now() . ",
    -				     $c_enabled, $c_access_level, 0, '$t_cookie_string', '$c_realname')";
    -		db_query( $query );
    -
    -		# Create preferences for the user
    -		$t_user_id = db_insert_id( $t_user_table );
    +
    +		#this will add the user to the auth method. ...   
    +		# some auth methods may already have the 
    +		# users added, and we would just need to create the rest of the information.
    +		$pearauth->addUser($p_username,$p_password);
    +		
    +		# update the database with the default configuration
    +		$query = "UPDATE $t_user_table SET 
    +				     email= '$c_email', date_created = " . db_now() . ", last_visit = " . db_now() . ",
    +				     enabled = $c_enabled, access_level = $c_access_level, login_count = 0 , cookie_string = '$t_cookie_string',
    +				 realname = '$c_realname'
    +				  WHERE username = '$c_username';";
    +		
    +		db_query( $query );
    +
    +		# Create preferences for the user
    +		if(db_is_pgsql())
    +		{
    +			$t_user_id = user_get_id_by_name( $p_username );
    +		}
    +		else
    +		{
    +			$t_user_id = db_insert_id( $t_user_table );
    +		}
    +		
     		user_pref_set_default( $t_user_id );
     
     		# Users are added with protected set to FALSE in order to be able to update
    @@ -389,8 +415,9 @@
     			$t_confirm_hash = auth_generate_confirm_hash( $t_user_id );
     			email_signup( $t_user_id, $p_password, $t_confirm_hash );
     		}
    -
    -		return $t_cookie_string;
    +
    +		if($t_cookie_string) return $t_cookie_string;
    +		return true;
     	}
     
     	# --------------------
    @@ -1069,21 +1096,11 @@
     	# --------------------
     	# Set the user's password to the given string, encoded as appropriate
     	function user_set_password( $p_user_id, $p_password, $p_allow_protected=false ) {
    -		$c_user_id = db_prepare_int( $p_user_id );
    -
     		if ( !$p_allow_protected ) {
     			user_ensure_unprotected( $p_user_id );
     		}
    -
    -		$t_password		= auth_process_plain_password( $p_password );
    -		$t_user_table	= config_get( 'mantis_user_table' );
    -		$query = "UPDATE $t_user_table
    -				  SET password='$t_password'
    -				  WHERE id='$c_user_id'";
    -		db_query( $query );
    -
    -		#db_query() errors on failure so:
    -		return true;
    +
    +		return auth_set_password($p_user_id, $p_password);
     	}
     
     	# --------------------
    @@ -1136,10 +1153,9 @@
     		if ( ( ON == config_get( 'send_reset_password' ) ) && ( ON == config_get( 'enable_email_notification' ) ) ) {
     			# Create random password
     			$t_email		= user_get_field( $p_user_id, 'email' );
    -			$t_password		= auth_generate_random_password( $t_email );
    -			$t_password2	= auth_process_plain_password( $t_password );
    -
    -			user_set_field( $p_user_id, 'password', $t_password2 );
    +			$t_password		= auth_generate_random_password( $t_email );
    +			
    +			if(!auth_set_password($p_user_id,$t_password)) return false;
     
     			# Send notification email
     			if ( $p_send_email ) {
    @@ -1148,8 +1164,8 @@
     			}
     		} else {
     			# use blank password, no emailing
    -			$t_password = auth_process_plain_password( '' );
    -			user_set_field( $p_user_id, 'password', $t_password );
    +			if(!auth_set_password($p_user_id,$t_password)) return false;
    +
     			# reset the failed login count because in this mode there is no emailing
     			user_reset_failed_login_count_to_zero( $p_user_id );
     		}
    --- login.php	Sat Mar 03 07:54:16 2007
    +++ login.php	Wed Jul 18 01:02:57 2007
    @@ -15,41 +15,17 @@
     
     	require_once( 'core.php' );
     
    -	$f_username		= gpc_get_string( 'username', '' );
    -	$f_password		= gpc_get_string( 'password', '' );
    -	$f_perm_login	= gpc_get_bool( 'perm_login' );
    -	$f_return		= gpc_get_string( 'return', config_get( 'default_home_page' ) );
    -	$f_from			= gpc_get_string( 'from', '' );
    +	#
    +	# not sure how to support HTTP_AUTH. I didn't see a storage continer for it
    +	# in pear::AUTH.   Maybe someone will write it?
    +    #
     
    -	if ( BASIC_AUTH == config_get( 'login_method' ) ) {
    -		$f_username = $_SERVER['REMOTE_USER'];
    -		$f_password = $_SERVER['PHP_AUTH_PW'];
    - 	}
    -
    -	if ( HTTP_AUTH == config_get( 'login_method' ) ) {
    -		if ( !auth_http_is_logout_pending() )
    -		{
    -			if ( isset( $_SERVER['PHP_AUTH_USER'] ) )
    -				$f_username = $_SERVER['PHP_AUTH_USER'];
    -			if ( isset( $_SERVER['PHP_AUTH_PW'] ) )
    -				$f_password = $_SERVER['PHP_AUTH_PW'];
    -		} else {
    -			auth_http_set_logout_pending( false );
    -			auth_http_prompt();
    -			return;
    -		}
    -	}
    -
    -	if ( auth_attempt_login( $f_username, $f_password, $f_perm_login ) ) {
    +	if ( $pearauth->checkAuth() ) {
     		$t_redirect_url = 'login_cookie_test.php?return=' . urlencode( $f_return );
    -	} else {
    +	} 
    +	else 
    +	{
     		$t_redirect_url = 'login_page.php?return=' . urlencode( $f_return ) . '&error=1';
    -
    -		if ( HTTP_AUTH == config_get( 'login_method' ) ) {
    -			auth_http_prompt();
    -			exit;
    -		}
     	}
    -
     	print_header_redirect( $t_redirect_url );
     ?>
    --- login_page.php	Sun Jul 23 18:31:28 2006
    +++ login_page.php	Wed Jul 18 00:22:11 2007
    @@ -11,6 +11,7 @@
     
     	# Login page POSTs results to login.php
     	# Check to see if the user is already logged in
    +	$g_login_allowed = false;
     
     	require_once( 'core.php' );
     
    @@ -23,7 +24,7 @@
     	$f_return		= gpc_get_string( 'return', '' );
     
     	# Check for HTTP_AUTH. HTTP_AUTH is handled in login.php
    -
    +/*
     	if ( HTTP_AUTH == config_get( 'login_method' ) ) {
     		$t_uri = "login.php";
     
    @@ -38,7 +39,7 @@
     		print_header_redirect( $t_uri );
     		exit;
     	}
    -
    +*/
     	html_page_top1();
     	html_page_top2a();
     
    @@ -183,7 +184,7 @@
     					( $t_upgrade_count != ( $t_upgrades_reqd + 10 ) ) ) { # there are 10 optional data escaping fixes that may be present
     				echo '<div class="warning" align="center">';
     				echo '<p><font color="red"><strong>WARNING:</strong> The database structure may be out of date. Please upgrade <a href="admin/upgrade.php">here</a> before logging in.</font></p>';
    -				echo '</div>';
    +		 		echo '</div>';
     			}
     		}
     
    
    patch file icon pear_auth.patch (32,592 bytes) 2007-07-18 05:15 + 

- Relationships
related to 0003043closedgrangeway support for NTLM authentication 
related to 0000287closedprescience authenticate against LDAP 
related to 0004234closedvboctor CAS authentication 
related to 0004010acknowledged Add NIS authentication as an alternative 
related to 0003303acknowledged Use X.509 certificate for authentication 
related to 0003887acknowledged radius authentcation support [patch] 
related to 0003847closedgrangeway CMS Integration (Namrly - Xoops) 
related to 0007478new MS Active Directory Authentication 
related to 0007432closedvboctor LDAP integration with Active Directory 
related to 0006771closedvboctor LDAP - Allow auth not to require dedicated LDAP account 
related to 0008012new Consider supporting Linux-PAM for authentication 
parent of 0003068closedgrangeway render_full_name in core/print.php 
has duplicate 0003394closedgrangeway rely on external source for authentication 
has duplicate 0007989closedjreese Multiple or Composite authentication mode for mantisbt 
has duplicate 0006718closedjreese Multiple authentification 
has duplicate 0007791closedatrol Allow custom login method 
related to 0004292closedthraxisp Sign-up sends a password for LDAP 
related to 0008402closedatrol Single Sign on for CMS such as Drupal, Joomla, Mambo etc 
related to 0011219acknowledged Provide OAuth inter-application authentication "tokens" mechanism 
related to 0012627new Plugin to enable login based upon Active Directory 
related to 0007371new Single signon for PHP-Fusion 
+ Relationships

-  Notes
User avatar

~0006604

thraxisp (manager)

We may want to look at the PEAR Authentication module for this. It covers all of the current authentication methods we use and is supposed to be extensible. I wrote a NIS method for it very quickly.
User avatar

~0006619

vboctor (administrator)

I agree that we should probably support PEAR authentication rather than inventing our own API. I remember I looked at it before and I liked it. It basically provides one interface that can be used to access more than one authentication technique, the nice thing is that they can be mixed, rather than having to use just one technique. So probably we can add Mantis authentication as one of the techniques.

Note that the above is from memory so it may not be very accurate.
User avatar

~0006796

rlegros (reporter)

I did have a look at the PEAR Auth package. It does seem much interresting. But there are two points to check.


1. Which version of PHP does it support ? Older versions make Mantis more portable.


2. There is a problem with CAS authentication (and maybe others). The architecture of PEAR Auth and of nearly all authentication systems is as follows. You take a user name and a password, you validate these against some system (file, DB and so on) and if it is OK you may log in. With CAS you delegate the login process to another application (so you are not aware of the user name and of the password), that application returns you a ticket, you ask for the ticket validation against the same application and in return you get the login id.


If Pear Auth is choosen, I think it should be needed to encapsulate it in a meta class that should also encapsulate CAS. This would make it possible for both methods to coexist cleanly in one MantisAuth class.

User avatar

~0006798

jlatour (reporter)

Perhaps that encapsulating class should also take in the account the possibility that PEAR's Authentication module is not installed/available and fall back to our own authentication in that case? Depends on how likely it is that the Authentication module is not installed (and how difficult it is to install).
User avatar

~0015090

edriede (reporter)

The attached files implement PEAR_Auth using the DB container (hardcoded).
It should be fairly easy to extend this to any of the underlying containers. the concerns with CAS should be able to be resolved with the custom container. PEAR_Auth also supports a Multi-Continer, which chould be used for per-user authentication. I tested this against Postgres 8.3, and the patches are based off of Mantis 1.1.0a3. I used the latest pear auth (1.5x), and my PHP version is 5.2.3. I attempted to use the existing authentication_api functions where possible, to keep the outside impact to a minimum.

I needed to patch the postgres ADO in order to get the app to work. I suppose that should be posted under a separate issue.


# Auth_Container_Array
# Auth_Container_DB
# Auth_Container_DBLite
# Auth_Container_File
# Auth_Container_IMAP
# Auth_Container_KADM5
# Auth_Container_LDAP
# Auth_Container_MDB
# Auth_Container_MDB2
# Auth_Container_Multiple
# Auth_Container_PEAR
# Auth_Container_POP3
# Auth_Container_RADIUS
# Auth_Container_SAP
# Auth_Container_SMBPasswd
# Auth_Container_SOAP
# Auth_Container_SOAP5
# Auth_Container_vpopmail
User avatar

~0016824

NT (reporter)

Could someone create a wiki page to let me start specifying a requirement list for this issue.
User avatar

~0016830

vboctor (administrator)

NT, Wiki page created! Go for it.
User avatar

~0016833

vboctor (administrator)

NT, I had a quick look at the requirements, and I believe it might be useful to have a "Scenarios" or "High Level Requirements" section after the "Introduction" and before we get into the detailed requirements. Following is some sample content that I had in mind:

1. Support for authentication via Mantis plugins by supporting protocols that validate a user name / password, as well as others that delegate the authentication process to another application (e.g. Open ID / CAS).

2. Support for single sign-on (e.g. Windows Login, CMS integration, etc). The single sign on should work for both Login and Logout scenarios.

3. Support for hybrid authentication. For example, employees are authenticated against LDAP where customers are authenticated using Mantis standard infrastructure.

4. Avoid sign-up when a user is authenticated but doesn't yet have a record in the DB. In this case, Mantis should pull the required data from the authentication plugin. For example, pull user information from LDAP when a user logs in for the first time.

5. Based on the plugin user for authentication, a user may not be able to edit some of the An authentication plugin should be able to mark some of the user profile information as read only. For example, user name or email may be marked as read only.

6. Once a user is signed up using a protocol, this protocol should be stamped on the user record and in the future the user should only be able to login via this protocol. For example, if a user is authenticated against Active Directory, then removed from Active Directory, then he/she should not be able to login against their user record in Mantis (use in termination of employment scenarios). -- This may cause a problem when a user logs in via Windows auth at work, but would like to login from home where he/she is not authenticated in Windows.
User avatar

~0017539

cdr-80 (reporter)

Last edited: 2008-04-02 08:56

Hello vboctor, NT,

I took the liberty of placing some comments en ideas in the document of NT
(http://www.mantisbt.org/wiki/doku.php/mantisbt:issue:4235 [^]) Fore some reason, whatever edit button I used, I could never ever edit another section than the first one..

In reference to the requirements of vboctor:
@1: Implementing simpleSAMLphp will do this for you and add a lot of other protocols as well.
@2: see my remarks in the wiki page
@4: make a clear difference between authentication data and authorisation data, otherwise login may break if the delegated idp changes one of the user credentials. In general Mantis should only care about authorisation, not about authentication, other then providing a "local" module if no other form of authentication is available.
@5: see @4
@6: see my remarks in the wiki document. Futhermore, when using windows authentication, don't make a windows problem a mantis problem: a clean solution is for the home user to use a vpn to log on to the (remote) windows network.

regards,

Niels

User avatar

~0019485

jwhitcraft (reporter)

What is the status of this? I would really love to use mantis but we already have an intranet user system and I don have to have to have another login for people to be able to submit bugs.
User avatar

~0022836

thungp (reporter)

I am also interested in this feature as well. Could anyone who was previously working on this or who has done anything with this, provide what they know as far a status update? Or did it pretty much stop at a list of requirements?
User avatar

~0023621

looki (reporter)

Hi,
I'm interested on that feature too.
Any updates?
Has anyone experiences with the NIS integration?
Thanks.
User avatar

~0025500

andy778 (reporter)

Have there been any plan when this is going to be implemented? 1.2.x or 1.3.x ?
User avatar

~0027314

Snaky (reporter)

Hi, please consider using http://simplesamlphp.org/ [^] for this - it will be the best solution for putting all the best php apps together!
User avatar

~0027772

rgomes1997 (reporter)

Last edited: 2011-01-02 23:07

View 2 revisions

+1 in regards to http://simplesamlphp.org [^]

SimpleSAMLphp is far from trivial to configure but, once configured, it can remove from other webapps a lot of logic related to authentication and user provisioning.

For more information about user provisioning, look for module "selfregister" (a module for simplesamlphp).
https://rnd.feide.no/2010/03/25/new_simplesamlphp_module_selfregister/ [^]

User avatar

~0028273

dpenezic (reporter)

Hi, i did code modification and simpleSAMLphp work with mantibt 1.3.0 how ever it will be more nicely implement if mantisbt have auth-plugin environment
User avatar

~0028288

rgomes1997 (reporter)

@dpenezic: Could you please provide more information? Thanks a lot :)
User avatar

~0028290

dpenezic (reporter)

@rgomes1997: You may found patch, part of configuration, and simplesamlphp_api.php file on fallow link http://developer.aaiedu.hr/download/ssphp_auth_addon_v.0.1.tar.gz [^]
User avatar

~0031742

matti (reporter)

Any progress on this feature?

PEAR Auth based patch looks quite complete and simple. Even the configuration for PEAR Auth is quite simple.

So as a default use multiple with variable number of Auth containers defined in config.

http://pear.php.net/manual/en/package.authentication.auth.storage.multiple.php [^]
+  Notes

- Issue History
Date Modified Username Field Change
2004-07-31 21:04 vboctor New Issue
2004-07-31 21:04 vboctor Relationship added related to 0003043
2004-07-31 21:05 vboctor Relationship added related to 0000287
2004-07-31 21:05 vboctor Relationship added related to 0003394
2004-07-31 21:06 vboctor Relationship added related to 0004234
2004-07-31 21:06 vboctor Relationship added related to 0004010
2004-07-31 21:07 vboctor Relationship added related to 0003303
2004-07-31 21:09 vboctor Category bugtracker => feature
2004-07-31 21:12 vboctor Relationship added related to 0003887
2004-07-31 21:15 vboctor Relationship added related to 0003847
2004-08-01 18:33 grangeway Status new => acknowledged
2004-08-03 11:47 thraxisp Note Added: 0006604
2004-08-04 06:31 vboctor Note Added: 0006619
2004-08-07 08:49 jlatour Status acknowledged => confirmed
2004-08-08 03:51 rlegros Note Added: 0006796
2004-08-08 04:13 jlatour Note Added: 0006798
2004-09-26 20:42 thraxisp Relationship added related to 0004292
2005-05-19 08:23 thraxisp Relationship added parent of 0003068
2007-07-06 10:44 vboctor Category feature => authentication
2007-07-18 05:15 edriede File Added: pear_auth.patch
2007-07-18 05:30 edriede Note Added: 0015090
2007-09-26 03:58 vboctor Relationship added related to 0008402
2008-01-26 08:47 NT Note Added: 0016824
2008-01-26 15:49 vboctor Note Added: 0016830
2008-01-27 03:58 vboctor Note Added: 0016833
2008-01-27 22:35 vboctor Relationship added related to 0007478
2008-01-27 22:35 vboctor Relationship added related to 0007432
2008-01-27 22:36 vboctor Relationship added related to 0006771
2008-01-27 22:36 vboctor Relationship added related to 0008012
2008-02-08 16:02 jreese Status confirmed => assigned
2008-02-08 16:02 jreese Assigned To => jreese
2008-03-20 08:33 jreese Relationship added has duplicate 0007989
2008-03-20 08:33 jreese Relationship added has duplicate 0006718
2008-04-02 08:53 cdr-80 Note Added: 0017539
2008-04-02 08:55 cdr-80 Note Edited: 0017539
2008-04-02 08:56 cdr-80 Note Edited: 0017539
2008-07-13 11:07 grangeway Relationship replaced has duplicate 0003394
2008-09-25 13:48 jreese Assigned To jreese => grangeway
2008-10-01 16:57 jwhitcraft Note Added: 0019485
2009-08-31 11:17 thungp Note Added: 0022836
2009-11-09 05:26 looki Note Added: 0023621
2010-02-07 07:40 dhx Relationship added related to 0011219
2010-05-14 06:47 andy778 Note Added: 0025500
2010-11-08 17:28 Snaky Note Added: 0027314
2011-01-02 23:06 rgomes1997 Note Added: 0027772
2011-01-02 23:07 rgomes1997 Note Edited: 0027772 View Revisions
2011-02-22 04:23 dpenezic Note Added: 0028273
2011-02-23 16:04 rgomes1997 Note Added: 0028288
2011-02-24 01:21 dpenezic Note Added: 0028290
2011-12-14 03:19 rombert Severity minor => feature
2012-05-02 02:56 matti Note Added: 0031742
2012-07-02 03:10 atrol Relationship added has duplicate 0007791
2012-07-02 03:11 atrol Relationship added related to 0012627
2012-07-02 03:11 atrol Relationship added related to 0007371
2014-11-07 15:27 atrol Assigned To grangeway =>
2014-11-07 15:27 atrol Status assigned => acknowledged
+ Issue History