View Issue Details

IDProjectCategoryView StatusLast Update
0009967mantisbtldappublic2021-01-16 05:01
Reporterrgomes1997 Assigned Todregad  
PrioritynormalSeverityfeatureReproducibilityN/A
Status closedResolutionwon't fix 
Product Versiongit trunk 
Summary0009967: Added capability to create accounts on OpenLDAP
Description

Hi,

I've added basic support for creating userDN on OpenLDAP and potentially Micro$oft AD to.

---Overview ---

  1. When a user creation is requested, a LDAP dependent code tries to create it on LDAP database before creating it in internal Mantis database.
  2. If an existing userDN already exists on LDAP, continue silently and create a user in the internal Mantis database.

---Issues---

  1. Code for changing LDAP password is not tested.
Additional Information

See attached files:

  • config_inc.php
    Contains an example confuration

  • core/user_api.php
    Minor changes, intended to call LDAP dependent code during user creation and
    password change.

  • core/ldap_api.php

    • Bugfixes;
    • Added code intended to support user creation;
Tagspatch
Attached Files
mantisbt-20081208.patch (17,916 bytes)   
Only in mantisbt-20081208.new: config_inc.php
diff -urp mantisbt-20081208.old/core/ldap_api.php mantisbt-20081208.new/core/ldap_api.php
--- mantisbt-20081208.old/core/ldap_api.php	2008-12-09 14:40:45.000000000 +0000
+++ mantisbt-20081208.new/core/ldap_api.php	2008-12-12 19:14:39.000000000 +0000
@@ -73,12 +73,12 @@ function ldap_email_from_username( $p_us
 	$t_ldap_organization = config_get( 'ldap_organization' );
 	$t_ldap_root_dn = config_get( 'ldap_root_dn' );
 
-	$t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
-	$t_search_filter = "(&$t_ldap_organization($t_ldap_uid_field=$p_username))";
+	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
+	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$p_username))";
 	$t_search_attrs = array(
-		$t_ldap_uid_field,
+		$t_ldap_username_field,
 		'mail',
-		'dn',
+		'dn'
 	);
 	$t_ds = ldap_connect_bind();
 
@@ -97,12 +97,12 @@ function ldap_has_group( $p_user_id, $p_
 	$t_ldap_root_dn = config_get( 'ldap_root_dn' );
 
 	$t_username = user_get_field( $p_user_id, 'username' );
-	$t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
-	$t_search_filter = "(&$t_ldap_organization($t_ldap_uid_field=$t_username)(assignedgroup=$p_group))";
+	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
+	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$t_username)(assignedgroup=$p_group))";
 	$t_search_attrs = array(
-		$t_ldap_uid_field,
+		$t_ldap_username_field,
 		'dn',
-		'assignedgroup',
+		'assignedgroup'
 	);
 	$t_ds = ldap_connect_bind();
 
@@ -133,11 +133,11 @@ function ldap_authenticate( $p_user_id, 
 	$t_ldap_root_dn = config_get( 'ldap_root_dn' );
 
 	$t_username = user_get_field( $p_user_id, 'username' );
-	$t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
-	$t_search_filter = "(&$t_ldap_organization($t_ldap_uid_field=$t_username))";
+	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
+	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$t_username))";
 	$t_search_attrs = array(
-		$t_ldap_uid_field,
-		'dn',
+		$t_ldap_username_field,
+		'dn'
 	);
 	$t_ds = ldap_connect_bind();
 
@@ -169,6 +169,394 @@ function ldap_authenticate( $p_user_id, 
 	return $t_authenticated;
 }
 
+
+##### Richard Gomes: started to add code from here #####
+
+
+/**
+ * This method is responsible for finding an user given its username, the rootDN
+ * and parameters used to build a search filter. A bind using the given username
+ * and password is tried for every candidate match in order to properly identify
+ * which userDN entry we are interested on.
+ *
+ * A typical scenario would be when we would like to find an inetOrgPerson class
+ * which contains a certain cn attribute. Another scenario would be when we are
+ * interested on a posixAccount class which contatin a certain uid. Doing so, we
+ * can find an user by various search strategies
+ * 
+ * This approach is useful when a certain user has a typical unix id for
+ * FTP and SSH access for instance, but has also another identification for
+ * wiki systems and eventually other identifications.
+ *
+ * Below we can see an use case:
+ * -----------------------------------------------------------
+ * dn: uid=jsmith,ou=people,dc=example,dc=com
+ * objectClass: krb5Principal
+ * objectClass: krb5KDCEntry
+ * objectClass: simpleSecurityObject
+ * objectClass: inetOrgPerson
+ * objectClass: person
+ * objectClass: top
+ * uid: jsmith
+ * cn: JohnSmith
+ * cn: John Smith
+ * sn: Smith
+ * description: John Paul Smith
+ * givenName: John
+ * displayName: Smith, John
+ * mail: john.smith@example.com
+ * userPassword: {K5KEY}
+ * krb5PrincipalName: jsmith@EXAMPLE.COM
+ * krb5KeyVersionNumber: 0
+ * krb5KDCFlags: 126
+ * krb5MaxLife: 86400
+ * krb5MaxRenew: 604800
+ * krb5Key:
+ * -----------------------------------------------------------
+ *
+ * Observe that "cn=JohnSmith" is intended to be used by wiki systems.
+ *
+ * Notes:
+ * 1. This method <i>always<i> performs a search. It never tries to access
+ *    the user entry directly by its supposed dn.
+ * 2. Mantis is not responsible for creating all possible objectClass(es)
+ *    needed by other purposes like FTP, SSH, Kerberos, but it's desirable to
+ *    smoothly integrate Mantis with previously created user entries, providing
+ *    means of recognizing the same user by various means.
+ *
+ * Parameters
+ *    $p_username is user we would like to find. Example: 'jsmith'
+ *    $p_password is the password for binding such user. A bind is performed in
+ *                order to make sure the correct userDN was found.
+ *    $p_ldap_people_dn is the rootDN where userDNs are located into.
+ *                Example: 'ou=people,dc=example,dc=com'
+ *    $p_ldap_organization is used to build a search filter.
+ *                Example: '(objectClass=inetOrgPerson)'
+ *
+ * Returns:
+ *   a DN relative to the successfully authenticated user entry.
+ */
+function ldap_find_userdn( $p_username, $p_password, $p_ldap_people_dn = '', $p_ldap_organization = '' ) {
+//trigger_error( "Entering ldap_find_userdn", WARNING );
+    // assertions
+	if ( '' == $p_username ) return NULL;
+    if ( '' == $p_ldap_people_dn ) {
+        $p_ldap_people_dn     = config_get( 'ldap_people_dn', "ou=people,".config_get( 'ldap_base_dn' ) );
+    }
+    if ( '' == $p_ldap_organization ) {
+        $p_ldap_organization  = config_get( 'ldap_organization' , '' );
+    }
+
+    // find userDN, if any
+	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
+//trigger_error( "p_ldap_people_dn"."=".$p_ldap_people_dn, WARNING );
+//trigger_error( "p_ldap_organization"."=".$p_ldap_organization, WARNING );
+//trigger_error( "t_ldap_username_field"."=".$t_ldap_username_field, WARNING );
+    $t_user_dn = ldap_find_userdn_by_attribute( $p_username, $p_password, $p_ldap_people_dn, $p_ldap_organization, $t_ldap_username_field );
+//trigger_error( "t_user_dn"."=".$t_user_dn, WARNING );
+    return $t_user_dn;
+}
+
+
+/**
+ * This method is intended to be called by ldap_find_userdn.
+ * Some consistency checks are not done here! You were warned!
+ */
+function ldap_find_userdn_by_attribute( $p_username = '',
+                                        $p_password = '',
+                                        $p_ldap_people_dn = '',
+                                        $p_ldap_organization ='',
+                                        $p_ldap_attribute ='') {
+//trigger_error( "Entering ldap_find_userdn_by_attribute", WARNING );
+	// assertions
+    if ( '' == $p_ldap_attribute ) return NULL;
+
+    // connect using an admin bind username/password
+	$t_ldap_bind_dn       = config_get( 'ldap_bind_dn' );
+	$t_ldap_bind_passwd   = config_get( 'ldap_bind_passwd' );
+	$t_ds = ldap_connect_bind( $t_ldap_bind_dn, $t_ldap_bind_password );
+    
+    // build search filter and attributes we are interested on
+	if ( '' == $p_ldap_organization ) {
+		$t_search_filter = "($p_ldap_attribute=$p_username)";
+	} else {
+		$t_search_filter = "(&$p_ldap_organization($p_ldap_attribute=$p_username))";
+	}
+	$t_search_attrs = array($p_ldap_attribute, 'dn');
+    // perform search
+//trigger_error( "p_ldap_root_dn"."=".$p_ldap_root_dn, WARNING );
+//trigger_error( "t_search_filter"."=".$t_search_filter, WARNING );
+	$t_sr = ldap_search( $t_ds, $p_ldap_root_dn, $t_search_filter, $t_search_attrs );
+	$t_info = ldap_get_entries( $t_ds, $t_sr );
+
+    $t_result_dn = NULL;
+    
+	if ( $t_info ) {
+		for ( $i = 0; $i < $t_info['count']; $i++ ) {
+			$t_dn = $t_info[$i]['dn'];
+			# Attempt to connect again but using the obtained DN
+	        $t_test_ds = ldap_connect_bind( $t_dn, $p_password );
+            if ( $t_test_ds ) {
+                $t_result_dn = $t_dn;
+                ldap_unbind( $t_test_ds );
+                break;
+            }
+        }
+    }
+
+	ldap_free_result( $t_sr );
+    ldap_unbind( $t_ds );
+    
+	return $t_result_dn;
+}
+
+
+function ldap_default_kerberos_domain( $p_ldap_base_dn = '' ) {
+    if ( '' == $p_ldap_base_dn ) {
+        $p_ldap_base_dn = config_get( 'ldap_base_dn' );
+    }
+    return strtoupper( str_replace ( array('dc=',','), array('','.'), $p_ldap_base_dn ) );
+}
+
+
+/**
+ * Returns a password that is created via the configured hash settings.
+ *
+ * @param string $password
+ * @return string
+ */
+function ldap_get_password_hash( $password ) {
+    $t_passwd_method = strtolower( config_get( 'passwd_method', 'sha' ) );
+
+    switch ( $t_passwd_method ) {
+        case 'clear':
+            $pass = $password;
+            break;
+        case 'crypt':
+            $pass = '{CRYPT}' . crypt( $password );
+            break;
+        case 'md5':
+            $tmp = base64_encode( pack( 'H*', md5( $password ) ) );
+            $pass = "{MD5}".$tmp;
+            break;
+        default:
+            $tmp = base64_encode( pack( 'H*', sha1( $password ) ) );
+            $pass = "{SHA}".$tmp;
+            break;
+    }
+
+    return $pass;
+}
+
+
+/**
+ * This is a typical record being produced by this method:
+ * -----------------------------------------------------------
+ * dn: uid=jsmith,ou=people,dc=example,dc=com
+ * objectClass: krb5Principal
+ * objectClass: krb5KDCEntry
+ * objectClass: simpleSecurityObject
+ * objectClass: inetOrgPerson
+ * objectClass: person
+ * objectClass: top
+ * uid: jsmith
+ * cn: JohnSmith
+ * cn: John Smith
+ * sn: Smith
+ * description: John Paul Smith
+ * givenName: John
+ * displayName: Smith, John
+ * mail: john.smith@example.com
+ * userPassword: {K5KEY}
+ * krb5PrincipalName: jsmith@EXAMPLE.COM
+ * krb5KeyVersionNumber: 0
+ * krb5KDCFlags: 126
+ * krb5MaxLife: 86400
+ * krb5MaxRenew: 604800
+ * krb5Key:
+ * -----------------------------------------------------------
+ */
+function ldap_prepare_entry( $p_username, $p_realname, $p_email ) {
+
+    // Ensure username is lowecase
+    $p_username = strtolower( $p_username );
+    
+    // split realname and test results
+    if ( '' == $p_realname ) {
+        trigger_error( "Realname cannot be empty", ERROR );
+        return NULL;
+    }
+    $names = split(' ', $p_realname);
+    if ( sizeof( $names ) < 2 ) {
+        trigger_error( "Realname must have at least first and last names", ERROR );
+        return NULL;
+    }    
+
+    // obtain name and surname
+    $first  = $names[0];
+    $last   = $names[sizeof($names)-1];
+
+    // obtain Kerberos preferences
+    $t_krb_vendor = strtolower( config_get( 'krb_vendor', 'none' ) );
+    
+    // set up basic objectClass(es)
+    $classes = 0;
+    if ( 'heimdal' == $t_krb_vendor ) {
+        $entry['objectClass'][$classes++] = 'krb5Principal';
+        $entry['objectClass'][$classes++] = 'krb5KDCEntry';
+    }
+    $entry['objectClass'][$classes++] = 'simpleSecurityObject';
+    $entry['objectClass'][$classes++] = 'inetOrgPerson';
+    $entry['objectClass'][$classes++] = 'person';
+    $entry['objectClass'][$classes++] = 'top';
+
+    // attributes uid and cn are mandatory in objectClass=account
+   	$t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
+    $t_ldap_cn_field  = config_get( 'ldap_cn_field', 'cn' );
+    $entry[$t_ldap_uid_field]   = $p_username;
+    $entry[$t_ldap_cn_field][0] = $first;
+    $entry[$t_ldap_cn_field][1] = $first.$last;
+    $entry[$t_ldap_cn_field][2] = $first.' '.$last;
+
+    // attribute sn is mandatory in objectClass=inetOrgPerson
+    $entry['sn'] = $last;
+
+    // attribute mail is optional in objectClass=inetOrgPerson
+    // but highly useful
+    $entry['mail']        = $p_email;
+
+    // attributes givenName, displayName and description are optional in 
+    // objectClass=inetOrgPerson but highly useful
+    $entry['givenName']   = $first;
+    $entry['displayName'] = $last.", ".$first;
+    $middle = "";
+    for ( $i = 1; $i < sizeof($names)-1; $i++ ) {
+        $middle = $middle.' '.$names[$i];
+    }
+    $entry['description'] = $first.$middle.' '.$last;
+    
+    if ( 'heimdal' == $t_krb_vendor ) {
+        // attribute krb5PrincipalName is mandatory in objectClass=krb5Principal
+        $t_krb_domain = config_get( 'krb_domain', ldap_default_kerberos_domain( ) );
+        $entry['krb5PrincipalName'] = $p_username.'@'.$t_krb_domain;
+
+        // attribute krb5VersionNumber is mandatory in objectClass=krb5KDCEntry
+        $entry['krb5KeyVersionNumber'] = 0;
+        
+        // optional attributes in objectClass=krb5KDCEntry
+        $entry['krb5KDCFlags'] =    126;
+        $entry['krb5MaxLife']  =  86400;
+        $entry['krb5MaxRenew'] = 604800;
+        
+        // optional krb5Key is the initial password in objectClass=krb5KDCEntry
+        $entry['krb5Key'] = 'cleartext';
+        
+        // attribute userPassword is mandatory in objectClass=simpleSecurityObject
+        $entry['userPassword'] = '{K5KEY}';
+    } else {
+        // attribute userPassword is mandatory in objectClass=simpleSecurityObject
+        $entry['userPassword'] = 'cleartext';
+    }
+
+    return $entry;
+}
+
+
 # Create a new user account in the LDAP Directory.
+function ldap_create_user( $p_username, $p_password, $p_realname, $p_email ) {
+//trigger_error( "Entering ldap_create_user", WARNING );
+
+    // find userDN, if any
+    $t_user_dn = ldap_find_userdn( $p_username, $p_password );
+    $create = ( NULL == $t_user_dn );
+    if ( $create ) {
+	    $t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
+	    $t_ldap_people_dn     = config_get( 'ldap_people_dn' );
+        $t_user_dn = $t_ldap_username_field.'='.$p_username.','.$t_ldap_people_dn;
+    }
+
+    // prepare an entry for being added or modify an existing one
+    $entry = ldap_prepare_entry( $p_username, $p_realname, $p_email );
+    if ( NULL == $entry ) return false;
+
+    // if an existing userDN was found, simply consolidate its contents
+    if ( ! $create ) return ldap_modify_userdn( $t_user_dn, $entry );
+
+	// bind as privileged user
+	$t_ldap_writer_dn     = config_get( 'ldap_writer_dn', config_get( 'ldap_bind_dn' ) );
+	$t_ldap_writer_passwd = config_get( 'ldap_writer_passwd', config_get( 'ldap_bind_passwd' ) );
+	$t_ds = ldap_connect_bind( $t_ldap_writer_dn , $t_ldap_writer_passwd );
+
+    // try to add a new userDN
+    $t_added = ldap_add( $t_ds, $t_user_dn, $entry );
+    if ( !$t_added ) {
+        trigger_error( "Failed to add user $t_user_dn", ERROR );
+    }
+    
+    // now change password :: ldap_get_password_hash( $p_password );
+    
+    ldap_unbind($t_ds);
+    return $t_added;
+}
+
 # Update the user's account in the LDAP Directory
+
+function ldap_modify_user( $p_username, $p_password, $p_realname, $p_email ) {
+trigger_error( "ldap_modify_user(username, password, realname, email) is not implemented", WARNING );
+    return false;
+}
+
+function ldap_modify_userdn( $p_user_dn, $p_entry ) {
+trigger_error( "ldap_modify_userdn(p_user_dn, p_entry) is not implemented", WARNING );
+    return true;
+}
+
+
 # Change the user's password in the LDAP Directory
+
+/**
+ * This method binds to an existing userDN and requests a password change.
+ *
+ * See:
+ *    ldap_passwd_userdn
+ */
+function ldap_passwd( $p_username, $p_password, $p_new_password) {
+//trigger_error( "Entering ldap_passwd", WARNING );
+    $t_user_dn = ldap_find_userdn( $p_username, $p_password );
+    return ldap_passwd_userdn( $t_userdn, $p_new_password );
+}
+
+
+/**
+ * This method changes the password of a given userDN without binding to it.
+ * This strategy allows a priviledged account to change user's password without
+ * knowing its previous password.
+ *
+ * See:
+ *    ldap_passwd
+ */
+function ldap_passwd_userdn( $p_user_dn, $p_new_password ) {
+trigger_error( "ldap_passwd_userdn(p_user_dn, p_new_password) is not implemented", ERROR );
+    // not allowed yet
+    return FALSE;
+    
+    // THIS CODE WAS NEVER TESTED ! You were warned!! :(
+    
+    // assertions
+    if ( (NULL == $p_userdn) || ( '' == $p_user_dn ) ) return false;
+
+    // bind as privileged user
+	$t_ldap_writer_dn     = config_get( 'ldap_writer_dn', config_get( 'ldap_bind_dn' ) );
+	$t_ldap_writer_passwd = config_get( 'ldap_writer_passwd', config_get( 'ldap_bind_passwd' ) );
+	$t_ds = ldap_connect_bind( $t_ldap_writer_dn, $t_ldap_writer_passwd );
+
+    $entry['userPassword'] = $p_password;
+    
+    // try to add a new userDN
+    $t_modified = ldap_modify( $t_ds, $p_user_dn, $entry );
+    if ( !$t_modified ) {
+        trigger_error( "Failed to change password for $t_user_dn", ERROR );
+    }
+
+    return $t_modified;
+}
diff -urp mantisbt-20081208.old/core/user_api.php mantisbt-20081208.new/core/user_api.php
--- mantisbt-20081208.old/core/user_api.php	2008-12-09 14:40:45.000000000 +0000
+++ mantisbt-20081208.new/core/user_api.php	2008-12-11 12:43:36.000000000 +0000
@@ -446,6 +446,13 @@ function user_create( $p_username, $p_pa
 	user_ensure_realname_unique( $p_username, $p_realname );
 	email_ensure_valid( $p_email );
 
+    $t_login_method = config_get( 'login_method' );
+    if ( $t_login_method == LDAP) {
+            if ( !ldap_create_user( $p_username, $p_password, $p_realname, $p_email ) ) {
+                    return '';
+            }
+    }
+
 	$t_seed = $p_email . $p_username;
 	$t_cookie_string = auth_generate_unique_cookie_string( $t_seed );
 	$t_user_table = db_get_table( 'mantis_user_table' );
@@ -1253,6 +1260,13 @@ function user_set_password( $p_user_id, 
 	$c_password = auth_process_plain_password( $p_password );
 	$c_user_table = db_get_table( 'mantis_user_table' );
 
+    $t_login_method = config_get( 'login_method' );
+    if ( $t_login_method == LDAP) {
+            if ( !ldap_passwd( $p_username, $p_password ) ) {
+                    return false;
+            }
+    }
+
 	$query = "UPDATE $c_user_table
 				  SET password=" . db_param() . ",
 				  cookie_string=" . db_param() . "
mantisbt-20081208.patch (17,916 bytes)   
config_inc.php (4,678 bytes)   
<?php

//
// FILE: config_inc.php
//

    //
    // Regarding LDAP authentication
    //
    // Mantis does not fully support LDAP authentication.
    //
    // For the time being, LDAP accounts must be created elsewhere whilst
    // Mantis administrator is responsible for creating these accounts by
    // hand in Mantis. This is because Mantis keeps user information in its
    // internal SQL database. Only authentication is done againt LDAP database.
    //
    // The account creation in Mantis consists of picking up a convenient
    // username for a previously created account in LDAP. The current
    // implementation allows you to choose which LDAP attribute must be used.
    // For instance: The following userDN could be selected by its "uid=jsmith"
    // or "cn=JohnSmith" or "cn=John Smith", etc.
    // -----------------------------------------------------------
    //  dn: uid=jsmith,ou=people,dc=example,dc=com
    //  objectClass: simpleSecurityObject
    //  objectClass: inetOrgPerson
    //  objectClass: person
    //  objectClass: top
    //  uid: jsmith
    //  cn: JohnSmith
    //  cn: John Smith
    //  sn: Smith
    //  description: John Paul Smith
    //  givenName: John
    //  displayName: Smith, John
    //  mail: john.smith@example.com
    //  userPassword: secret
    //  -----------------------------------------------------------
    //
    // Once the administrator choose which field must be used, it must be
    // referenced by g_ldap_username_field, like this:
    //
    // $g_ldap_uid_field        = 'uid'; // M$ AD uses 'sAMAccountName'
    // $g_ldap_cn_field         = 'cn';  
    // $g_ldap_username_field   = &$g_ldap_cn_field;
    //
    // Other aspects to consider is that only the Mantis administrator is
    // allowed to create accounts. For this reason, signups are not allowed
    // and the password reset feature must be disallowed because it's not
    // fully implemented.
    // 
    // $g_send_reset_password   = OFF;
    // $g_allow_signup          = OFF;
    //
    
    $g_send_reset_password   = OFF; // This will display password input while creating new account for admin login.
    $g_allow_signup          = OFF; // Sign up feature is disabled

    $g_login_method          = LDAP;
    $g_passwd_method         = 'clear'; // one of: 'clear', 'crypt', 'md5', 'sha'. default: 'sha' 
    $g_ldap_protocol_version = 3;
    $g_ldap_server           = 'ldap://localhost';
    $g_ldap_port             = '389';
    $g_ldap_base_dn          = 'dc=jquantlib,dc=org';
    
    // bind for read-only opeations
    $g_ldap_bind_dn          = 'cn=admin,dc=jquantlib,dc=org';
    $g_ldap_bind_passwd      = 'secret';
    
    // bind for privileged operations
    $g_ldap_writer_dn        = 'cn=admin,dc=jquantlib,dc=org'; // default: $g_ldap_bind_dn
    $g_ldap_writer_passwd    = 'secret';                       // default: $g_ldap_bind_passwd

    // DEPRECATED:
    // The name "rootDN" is a generic concept which can be applied to different operations.
    // We should have several "rootDNs" for several different operations instead.
    $g_ldap_root_dn          = "ou=People,dc=jquantlib,dc=org"; // DEPRECATED: for backward compatibility only

    // peopleDN and groupDN are better replacements for rootDN
    $g_ldap_people_dn        = "ou=People,dc=jquantlib,dc=org"; // default: "ou=people,".$ldap_base_dn
    $g_ldap_group_dn         = "ou=Group,dc=jquantlib,dc=org";  // default: "ou=group,".$ldap_base_dn

    $g_ldap_uid_field        = 'uid';         // default: 'uid'
    $g_ldap_cn_field         = 'cn';          // default: 'cn'
    
    // attribute to be used for userDN lookups
    $g_ldap_username_field   = &$g_ldap_uid_field;

    // We should have a more specific name here, for instance: $g_people_filter
    $g_ldap_organization     = '(objectClass=inetOrgPerson)'; // default: ''

    $g_use_ldap_email        = ON;


    //
    // The following setting are Kerberos related and indirectly related to LDAP
    // 

    // MIT Kerberos and Heimdal Kerberos are able to store username/password information in LDAP backends.
    // By specifying the Kerberos implementation, additional objectClass(es) need to be added in the userDN
    // At the moment, only Heimdal Kerberos is supported
    $g_krb_vendor            = 'heimdal'; // values: 'mit', 'heimdal', 'none'. Default: 'none'
    $g_krb_domain            = 'JQUANTLIB.ORG'; // default: strtoupper(str_replace(array('dc=',','), array('','.'), $p_ldap_base_dn))


    // ***** Add your other configurations here *****

?>
config_inc.php (4,678 bytes)   
ldap_api.php (19,141 bytes)   
<?php
# Mantis - a php based bugtracking system

# Mantis is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Mantis is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.

/**
 * LDAP API
 * @package CoreAPI
 * @subpackage LDAPAPI
 * @copyright Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
 * @copyright Copyright (C) 2002 - 2009  Mantis Team   - mantisbt-dev@lists.sourceforge.net
 * @link http://www.mantisbt.org
 */

# Connect and bind to the LDAP directory
function ldap_connect_bind( $p_binddn = '', $p_password = '' ) {
	$t_ldap_server = config_get( 'ldap_server' );

	if( !extension_loaded( 'ldap' ) ) {
		trigger_error( ERROR_LDAP_EXTENSION_NOT_LOADED, ERROR );
	}

	$t_ds = @ldap_connect( $t_ldap_server );
	if( $t_ds > 0 ) {
		$t_protocol_version = config_get( 'ldap_protocol_version' );

		if( $t_protocol_version > 0 ) {
			ldap_set_option( $t_ds, LDAP_OPT_PROTOCOL_VERSION, $t_protocol_version );
		}

		# If no Bind DN and Password is set, attempt to login as the configured
		#  Bind DN.
		if( is_blank( $p_binddn ) && is_blank( $p_password ) ) {
			$p_binddn = config_get( 'ldap_bind_dn', '' );
			$p_password = config_get( 'ldap_bind_passwd', '' );
		}

		if( !is_blank( $p_binddn ) && !is_blank( $p_password ) ) {
			$t_br = @ldap_bind( $t_ds, $p_binddn, $p_password );
		} else {
			# Either the Bind DN or the Password are empty, so attempt an anonymous bind.
			$t_br = @ldap_bind( $t_ds );
		}
		if( !$t_br ) {
			trigger_error( ERROR_LDAP_AUTH_FAILED, ERROR );
		}
	} else {
		trigger_error( ERROR_LDAP_SERVER_CONNECT_FAILED, ERROR );
	}

	return $t_ds;
}

# Return an email address from LDAP, given a userid
function ldap_email( $p_user_id ) {
	$t_username = user_get_field( $p_user_id, 'username' );
	return ldap_email_from_username( $t_username );
}

# Return an email address from LDAP, given a username
function ldap_email_from_username( $p_username ) {
	$t_ldap_organization = config_get( 'ldap_organization' );
	$t_ldap_root_dn = config_get( 'ldap_root_dn' );

	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$p_username))";
	$t_search_attrs = array(
		$t_ldap_username_field,
		'mail',
		'dn'
	);
	$t_ds = ldap_connect_bind();

	$t_sr = ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );

	$t_info = ldap_get_entries( $t_ds, $t_sr );
	ldap_free_result( $t_sr );
	ldap_unbind( $t_ds );

	return $t_info[0]['mail'][0];
}

# Return true if the $uid has an assigngroup=$p_group tag, false otherwise
function ldap_has_group( $p_user_id, $p_group ) {
	$t_ldap_organization = config_get( 'ldap_organization' );
	$t_ldap_root_dn = config_get( 'ldap_root_dn' );

	$t_username = user_get_field( $p_user_id, 'username' );
	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$t_username)(assignedgroup=$p_group))";
	$t_search_attrs = array(
		$t_ldap_username_field,
		'dn',
		'assignedgroup'
	);
	$t_ds = ldap_connect_bind();

	$t_sr = ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
	$t_entries = ldap_count_entries( $t_ds, $t_sr );
	ldap_free_result( $t_sr );
	ldap_unbind( $t_ds );

	if( $t_entries > 0 ) {
		return true;
	} else {
		return false;
	}
}

# Attempt to authenticate the user against the LDAP directory
#  return true on successful authentication, false otherwise
function ldap_authenticate( $p_user_id, $p_password ) {

	# if password is empty and ldap allows anonymous login, then
	# the user will be able to login, hence, we need to check
	# for this special case.
	if( is_blank( $p_password ) ) {
		return false;
	}

	$t_ldap_organization = config_get( 'ldap_organization' );
	$t_ldap_root_dn = config_get( 'ldap_root_dn' );

	$t_username = user_get_field( $p_user_id, 'username' );
	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
	$t_search_filter = "(&$t_ldap_organization($t_ldap_username_field=$t_username))";
	$t_search_attrs = array(
		$t_ldap_username_field,
		'dn'
	);
	$t_ds = ldap_connect_bind();

	# Search for the user id
	$t_sr = ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
	$t_info = ldap_get_entries( $t_ds, $t_sr );

	$t_authenticated = false;

	if( $t_info ) {

		# Try to authenticate to each until we get a match
		for( $i = 0;$i < $t_info['count'];$i++ ) {
			$t_dn = $t_info[$i]['dn'];

			# Attempt to bind with the DN and password
			if( @ldap_bind( $t_ds, $t_dn, $p_password ) ) {
				$t_authenticated = true;
				break;

				# Don't need to go any further
			}
		}
	}

	ldap_free_result( $t_sr );
	ldap_unbind( $t_ds );

	return $t_authenticated;
}


##### Richard Gomes: started to add code from here #####


/**
 * This method is responsible for finding an user given its username, the rootDN
 * and parameters used to build a search filter. A bind using the given username
 * and password is tried for every candidate match in order to properly identify
 * which userDN entry we are interested on.
 *
 * A typical scenario would be when we would like to find an inetOrgPerson class
 * which contains a certain cn attribute. Another scenario would be when we are
 * interested on a posixAccount class which contatin a certain uid. Doing so, we
 * can find an user by various search strategies
 * 
 * This approach is useful when a certain user has a typical unix id for
 * FTP and SSH access for instance, but has also another identification for
 * wiki systems and eventually other identifications.
 *
 * Below we can see an use case:
 * -----------------------------------------------------------
 * dn: uid=jsmith,ou=people,dc=example,dc=com
 * objectClass: krb5Principal
 * objectClass: krb5KDCEntry
 * objectClass: simpleSecurityObject
 * objectClass: inetOrgPerson
 * objectClass: person
 * objectClass: top
 * uid: jsmith
 * cn: JohnSmith
 * cn: John Smith
 * sn: Smith
 * description: John Paul Smith
 * givenName: John
 * displayName: Smith, John
 * mail: john.smith@example.com
 * userPassword: {K5KEY}
 * krb5PrincipalName: jsmith@EXAMPLE.COM
 * krb5KeyVersionNumber: 0
 * krb5KDCFlags: 126
 * krb5MaxLife: 86400
 * krb5MaxRenew: 604800
 * krb5Key:
 * -----------------------------------------------------------
 *
 * Observe that "cn=JohnSmith" is intended to be used by wiki systems.
 *
 * Notes:
 * 1. This method <i>always<i> performs a search. It never tries to access
 *    the user entry directly by its supposed dn.
 * 2. Mantis is not responsible for creating all possible objectClass(es)
 *    needed by other purposes like FTP, SSH, Kerberos, but it's desirable to
 *    smoothly integrate Mantis with previously created user entries, providing
 *    means of recognizing the same user by various means.
 *
 * Parameters
 *    $p_username is user we would like to find. Example: 'jsmith'
 *    $p_password is the password for binding such user. A bind is performed in
 *                order to make sure the correct userDN was found.
 *    $p_ldap_people_dn is the rootDN where userDNs are located into.
 *                Example: 'ou=people,dc=example,dc=com'
 *    $p_ldap_organization is used to build a search filter.
 *                Example: '(objectClass=inetOrgPerson)'
 *
 * Returns:
 *   a DN relative to the successfully authenticated user entry.
 */
function ldap_find_userdn( $p_username, $p_password, $p_ldap_people_dn = '', $p_ldap_organization = '' ) {
//trigger_error( "Entering ldap_find_userdn", WARNING );
    // assertions
	if ( '' == $p_username ) return NULL;
    if ( '' == $p_ldap_people_dn ) {
        $p_ldap_people_dn     = config_get( 'ldap_people_dn', "ou=people,".config_get( 'ldap_base_dn' ) );
    }
    if ( '' == $p_ldap_organization ) {
        $p_ldap_organization  = config_get( 'ldap_organization' , '' );
    }

    // find userDN, if any
	$t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
//trigger_error( "p_ldap_people_dn"."=".$p_ldap_people_dn, WARNING );
//trigger_error( "p_ldap_organization"."=".$p_ldap_organization, WARNING );
//trigger_error( "t_ldap_username_field"."=".$t_ldap_username_field, WARNING );
    $t_user_dn = ldap_find_userdn_by_attribute( $p_username, $p_password, $p_ldap_people_dn, $p_ldap_organization, $t_ldap_username_field );
//trigger_error( "t_user_dn"."=".$t_user_dn, WARNING );
    return $t_user_dn;
}


/**
 * This method is intended to be called by ldap_find_userdn.
 * Some consistency checks are not done here! You were warned!
 */
function ldap_find_userdn_by_attribute( $p_username = '',
                                        $p_password = '',
                                        $p_ldap_people_dn = '',
                                        $p_ldap_organization ='',
                                        $p_ldap_attribute ='') {
//trigger_error( "Entering ldap_find_userdn_by_attribute", WARNING );
	// assertions
    if ( '' == $p_ldap_attribute ) return NULL;

    // connect using an admin bind username/password
	$t_ldap_bind_dn       = config_get( 'ldap_bind_dn' );
	$t_ldap_bind_passwd   = config_get( 'ldap_bind_passwd' );
	$t_ds = ldap_connect_bind( $t_ldap_bind_dn, $t_ldap_bind_password );
    
    // build search filter and attributes we are interested on
	if ( '' == $p_ldap_organization ) {
		$t_search_filter = "($p_ldap_attribute=$p_username)";
	} else {
		$t_search_filter = "(&$p_ldap_organization($p_ldap_attribute=$p_username))";
	}
	$t_search_attrs = array($p_ldap_attribute, 'dn');
    // perform search
//trigger_error( "p_ldap_root_dn"."=".$p_ldap_root_dn, WARNING );
//trigger_error( "t_search_filter"."=".$t_search_filter, WARNING );
	$t_sr = ldap_search( $t_ds, $p_ldap_root_dn, $t_search_filter, $t_search_attrs );
	$t_info = ldap_get_entries( $t_ds, $t_sr );

    $t_result_dn = NULL;
    
	if ( $t_info ) {
		for ( $i = 0; $i < $t_info['count']; $i++ ) {
			$t_dn = $t_info[$i]['dn'];
			# Attempt to connect again but using the obtained DN
	        $t_test_ds = ldap_connect_bind( $t_dn, $p_password );
            if ( $t_test_ds ) {
                $t_result_dn = $t_dn;
                ldap_unbind( $t_test_ds );
                break;
            }
        }
    }

	ldap_free_result( $t_sr );
    ldap_unbind( $t_ds );
    
	return $t_result_dn;
}


function ldap_default_kerberos_domain( $p_ldap_base_dn = '' ) {
    if ( '' == $p_ldap_base_dn ) {
        $p_ldap_base_dn = config_get( 'ldap_base_dn' );
    }
    return strtoupper( str_replace ( array('dc=',','), array('','.'), $p_ldap_base_dn ) );
}


/**
 * Returns a password that is created via the configured hash settings.
 *
 * @param string $password
 * @return string
 */
function ldap_get_password_hash( $password ) {
    $t_passwd_method = strtolower( config_get( 'passwd_method', 'sha' ) );

    switch ( $t_passwd_method ) {
        case 'clear':
            $pass = $password;
            break;
        case 'crypt':
            $pass = '{CRYPT}' . crypt( $password );
            break;
        case 'md5':
            $tmp = base64_encode( pack( 'H*', md5( $password ) ) );
            $pass = "{MD5}".$tmp;
            break;
        default:
            $tmp = base64_encode( pack( 'H*', sha1( $password ) ) );
            $pass = "{SHA}".$tmp;
            break;
    }

    return $pass;
}


/**
 * This is a typical record being produced by this method:
 * -----------------------------------------------------------
 * dn: uid=jsmith,ou=people,dc=example,dc=com
 * objectClass: krb5Principal
 * objectClass: krb5KDCEntry
 * objectClass: simpleSecurityObject
 * objectClass: inetOrgPerson
 * objectClass: person
 * objectClass: top
 * uid: jsmith
 * cn: JohnSmith
 * cn: John Smith
 * sn: Smith
 * description: John Paul Smith
 * givenName: John
 * displayName: Smith, John
 * mail: john.smith@example.com
 * userPassword: {K5KEY}
 * krb5PrincipalName: jsmith@EXAMPLE.COM
 * krb5KeyVersionNumber: 0
 * krb5KDCFlags: 126
 * krb5MaxLife: 86400
 * krb5MaxRenew: 604800
 * krb5Key:
 * -----------------------------------------------------------
 */
function ldap_prepare_entry( $p_username, $p_realname, $p_email ) {

    // Ensure username is lowecase
    $p_username = strtolower( $p_username );
    
    // split realname and test results
    if ( '' == $p_realname ) {
        trigger_error( "Realname cannot be empty", ERROR );
        return NULL;
    }
    $names = split(' ', $p_realname);
    if ( sizeof( $names ) < 2 ) {
        trigger_error( "Realname must have at least first and last names", ERROR );
        return NULL;
    }    

    // obtain name and surname
    $first  = $names[0];
    $last   = $names[sizeof($names)-1];

    // obtain Kerberos preferences
    $t_krb_vendor = strtolower( config_get( 'krb_vendor', 'none' ) );
    
    // set up basic objectClass(es)
    $classes = 0;
    if ( 'heimdal' == $t_krb_vendor ) {
        $entry['objectClass'][$classes++] = 'krb5Principal';
        $entry['objectClass'][$classes++] = 'krb5KDCEntry';
    }
    $entry['objectClass'][$classes++] = 'simpleSecurityObject';
    $entry['objectClass'][$classes++] = 'inetOrgPerson';
    $entry['objectClass'][$classes++] = 'person';
    $entry['objectClass'][$classes++] = 'top';

    // attributes uid and cn are mandatory in objectClass=account
   	$t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
    $t_ldap_cn_field  = config_get( 'ldap_cn_field', 'cn' );
    $entry[$t_ldap_uid_field]   = $p_username;
    $entry[$t_ldap_cn_field][0] = $first;
    $entry[$t_ldap_cn_field][1] = $first.$last;
    $entry[$t_ldap_cn_field][2] = $first.' '.$last;

    // attribute sn is mandatory in objectClass=inetOrgPerson
    $entry['sn'] = $last;

    // attribute mail is optional in objectClass=inetOrgPerson
    // but highly useful
    $entry['mail']        = $p_email;

    // attributes givenName, displayName and description are optional in 
    // objectClass=inetOrgPerson but highly useful
    $entry['givenName']   = $first;
    $entry['displayName'] = $last.", ".$first;
    $middle = "";
    for ( $i = 1; $i < sizeof($names)-1; $i++ ) {
        $middle = $middle.' '.$names[$i];
    }
    $entry['description'] = $first.$middle.' '.$last;
    
    if ( 'heimdal' == $t_krb_vendor ) {
        // attribute krb5PrincipalName is mandatory in objectClass=krb5Principal
        $t_krb_domain = config_get( 'krb_domain', ldap_default_kerberos_domain( ) );
        $entry['krb5PrincipalName'] = $p_username.'@'.$t_krb_domain;

        // attribute krb5VersionNumber is mandatory in objectClass=krb5KDCEntry
        $entry['krb5KeyVersionNumber'] = 0;
        
        // optional attributes in objectClass=krb5KDCEntry
        $entry['krb5KDCFlags'] =    126;
        $entry['krb5MaxLife']  =  86400;
        $entry['krb5MaxRenew'] = 604800;
        
        // optional krb5Key is the initial password in objectClass=krb5KDCEntry
        $entry['krb5Key'] = 'cleartext';
        
        // attribute userPassword is mandatory in objectClass=simpleSecurityObject
        $entry['userPassword'] = '{K5KEY}';
    } else {
        // attribute userPassword is mandatory in objectClass=simpleSecurityObject
        $entry['userPassword'] = 'cleartext';
    }

    return $entry;
}


# Create a new user account in the LDAP Directory.
function ldap_create_user( $p_username, $p_password, $p_realname, $p_email ) {
//trigger_error( "Entering ldap_create_user", WARNING );

    // find userDN, if any
    $t_user_dn = ldap_find_userdn( $p_username, $p_password );
    $create = ( NULL == $t_user_dn );
    if ( $create ) {
	    $t_ldap_username_field = config_get( 'ldap_username_field', 'uid' );
	    $t_ldap_people_dn     = config_get( 'ldap_people_dn' );
        $t_user_dn = $t_ldap_username_field.'='.$p_username.','.$t_ldap_people_dn;
    }

    // prepare an entry for being added or modify an existing one
    $entry = ldap_prepare_entry( $p_username, $p_realname, $p_email );
    if ( NULL == $entry ) return false;

    // if an existing userDN was found, simply consolidate its contents
    if ( ! $create ) return ldap_modify_userdn( $t_user_dn, $entry );

	// bind as privileged user
	$t_ldap_writer_dn     = config_get( 'ldap_writer_dn', config_get( 'ldap_bind_dn' ) );
	$t_ldap_writer_passwd = config_get( 'ldap_writer_passwd', config_get( 'ldap_bind_passwd' ) );
	$t_ds = ldap_connect_bind( $t_ldap_writer_dn , $t_ldap_writer_passwd );

    // try to add a new userDN
    $t_added = ldap_add( $t_ds, $t_user_dn, $entry );
    if ( !$t_added ) {
        trigger_error( "Failed to add user $t_user_dn", ERROR );
    }
    
    // now change password :: ldap_get_password_hash( $p_password );
    
    ldap_unbind($t_ds);
    return $t_added;
}

# Update the user's account in the LDAP Directory

function ldap_modify_user( $p_username, $p_password, $p_realname, $p_email ) {
trigger_error( "ldap_modify_user(username, password, realname, email) is not implemented", WARNING );
    return false;
}

function ldap_modify_userdn( $p_user_dn, $p_entry ) {
trigger_error( "ldap_modify_userdn(p_user_dn, p_entry) is not implemented", WARNING );
    return true;
}


# Change the user's password in the LDAP Directory

/**
 * This method binds to an existing userDN and requests a password change.
 *
 * See:
 *    ldap_passwd_userdn
 */
function ldap_passwd( $p_username, $p_password, $p_new_password) {
//trigger_error( "Entering ldap_passwd", WARNING );
    $t_user_dn = ldap_find_userdn( $p_username, $p_password );
    return ldap_passwd_userdn( $t_userdn, $p_new_password );
}


/**
 * This method changes the password of a given userDN without binding to it.
 * This strategy allows a priviledged account to change user's password without
 * knowing its previous password.
 *
 * See:
 *    ldap_passwd
 */
function ldap_passwd_userdn( $p_user_dn, $p_new_password ) {
trigger_error( "ldap_passwd_userdn(p_user_dn, p_new_password) is not implemented", ERROR );
    // not allowed yet
    return FALSE;
    
    // THIS CODE WAS NEVER TESTED ! You were warned!! :(
    
    // assertions
    if ( (NULL == $p_userdn) || ( '' == $p_user_dn ) ) return false;

    // bind as privileged user
	$t_ldap_writer_dn     = config_get( 'ldap_writer_dn', config_get( 'ldap_bind_dn' ) );
	$t_ldap_writer_passwd = config_get( 'ldap_writer_passwd', config_get( 'ldap_bind_passwd' ) );
	$t_ds = ldap_connect_bind( $t_ldap_writer_dn, $t_ldap_writer_passwd );

    $entry['userPassword'] = $p_password;
    
    // try to add a new userDN
    $t_modified = ldap_modify( $t_ds, $p_user_dn, $entry );
    if ( !$t_modified ) {
        trigger_error( "Failed to change password for $t_user_dn", ERROR );
    }

    return $t_modified;
}
ldap_api.php (19,141 bytes)   
user_api.php (40,762 bytes)   
<?php
# Mantis - a php based bugtracking system
# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
# Copyright (C) 2002 - 2009  Mantis Team   - mantisbt-dev@lists.sourceforge.net
# Mantis is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Mantis is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
#
# --------------------------------------------------------
# $Id$
# --------------------------------------------------------

/**
 * @package CoreAPI
 * @subpackage UserAPI
 */

$t_core_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;

require_once( $t_core_dir . 'email_api.php' );
require_once( $t_core_dir . 'ldap_api.php' );

# ===================================
# Caching
# ===================================
# ########################################
# SECURITY NOTE: cache globals are initialized here to prevent them
#   being spoofed if register_globals is turned on

$g_cache_user = array();

# --------------------
# Cache a user row if necessary and return the cached copy
#  If the second parameter is true (default), trigger an error
#  if the user can't be found.  If the second parameter is
#  false, return false if the user can't be found.
function user_cache_row( $p_user_id, $p_trigger_errors = true ) {
	global $g_cache_user;

	if( isset( $g_cache_user[$p_user_id] ) ) {
		return $g_cache_user[$p_user_id];
	}

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT *
				  FROM $t_user_table
				  WHERE id=" . db_param();
	$result = db_query_bound( $query, Array( $p_user_id ) );

	if( 0 == db_num_rows( $result ) ) {
		$g_cache_user[$p_user_id] = false;

		if( $p_trigger_errors ) {
			error_parameters( (integer)$p_user_id );
			trigger_error( ERROR_USER_BY_ID_NOT_FOUND, ERROR );
		}

		return false;
	}

	$row = db_fetch_array( $result );

	$g_cache_user[$p_user_id] = $row;

	return $row;
}

function user_cache_array_rows( $p_user_id_array ) {
	global $g_cache_user;
	$c_user_id_array = array();

	foreach( $p_user_id_array as $t_user_id ) {
		if( !isset( $g_cache_user[(int) $t_user_id] ) ) {
			$c_user_id_array[] = (int) $t_user_id;
		}
	}

	if( empty( $c_user_id_array ) ) {
		return;
	}


	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT *
				  FROM $t_user_table
				  WHERE id IN (" . implode( ',', $c_user_id_array ) . ')';
	$result = db_query_bound( $query );

	while( $row = db_fetch_array( $result ) ) {
		$g_cache_user[(int) $row['id']] = $row;
	}
	return;
}

# --------------------
# Cache an object as a bug.
function user_cache_database_result( $p_user_database_result ) {
	global $g_cache_user;

	if( isset( $g_cache_user[$p_user_database_result['id']] ) ) {
		return $g_cache_user[$p_user_database_result['id']];
	}

	$g_cache_user[$p_user_database_result['id']] = $p_user_database_result;
}

# --------------------
# Clear the user cache (or just the given id if specified)
function user_clear_cache( $p_user_id = null ) {
	global $g_cache_user;

	if( null === $p_user_id ) {
		$g_cache_user = array();
	} else {
		unset( $g_cache_user[$p_user_id] );
	}

	return true;
}

function user_update_cache( $p_user_id, $p_field, $p_value ) {
	global $g_cache_user;

	if( isset( $g_cache_user[$p_user_id] ) && isset( $g_cache_user[$p_user_id][$p_field] ) ) {
		$g_cache_user[$p_user_id][$p_field] = $p_value;
	} else {
		user_clear_cache( $p_user_id );
	}
}

function user_search_cache( $p_field, $p_value ) {
	global $g_cache_user;
	if( isset( $g_cache_user ) ) {
		foreach( $g_cache_user as $t_user ) {
			if( $t_user[$p_field] == $p_value ) {
				return $t_user;
			}
		}
	}
	return false;
}

# ===================================
# Boolean queries and ensures
# ===================================
# --------------------
# check to see if user exists by id
# return true if it does, false otherwise
#
# Use user_cache_row() to benefit from caching if called multiple times
#  and because if the user does exist the data may well be wanted
function user_exists( $p_user_id ) {
	$row = user_cache_row( $p_user_id, false );

	if( false === $row ) {
		return false;
	} else {
		return true;
	}
}

# --------------------
# check to see if project exists by id
# if it doesn't exist then error
#  otherwise let execution continue undisturbed
function user_ensure_exists( $p_user_id ) {
	$c_user_id = (integer)$p_user_id;

	if ( !user_exists( $c_user_id ) ) {
		error_parameters( $c_user_id );
		trigger_error( ERROR_USER_BY_ID_NOT_FOUND, ERROR );
	}
}

# --------------------
# return true if the username is unique, false if there is already a user
#  with that username
function user_is_name_unique( $p_username ) {
	$c_username = db_prepare_string( $p_username );

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT username
				FROM $t_user_table
				WHERE username=" . db_param();
	$result = db_query_bound( $query, Array( $c_username ), 1 );

	if( db_num_rows( $result ) > 0 ) {
		return false;
	} else {
		return true;
	}
}

# --------------------
# Check if the username is unique and trigger an ERROR if it isn't
function user_ensure_name_unique( $p_username ) {
	if( !user_is_name_unique( $p_username ) ) {
		trigger_error( ERROR_USER_NAME_NOT_UNIQUE, ERROR );
	}
}

# --------------------
# Check if the realname is a valid username (does not account for uniqueness)
# Return 0 if it is invalid, The number of matches + 1
function user_is_realname_unique( $p_username, $p_realname ) {
	if( is_blank( $p_realname ) ) {

		# don't bother checking if realname is blank
		return 1;
	}

	$c_realname = db_prepare_string( $p_realname );

	# allow realname to match username
	$t_count = 0;
	if( $p_realname <> $p_username ) {

		# check realname does not match an existing username
		if( user_get_id_by_name( $p_realname ) ) {
			return 0;
		}

		# check to see if the realname is unique
		$t_user_table = db_get_table( 'mantis_user_table' );
		$query = "SELECT id
				FROM $t_user_table
				WHERE realname=" . db_param();
		$result = db_query_bound( $query, Array( $c_realname ) );
		$t_count = db_num_rows( $result );

		if( $t_count > 0 ) {
			# set flags for non-unique realnames
			if( config_get( 'differentiate_duplicates' ) ) {
				for( $i = 0;$i < $t_count;$i++ ) {
					$t_user_id = db_result( $result, $i );
					user_set_field( $t_user_id, 'duplicate_realname', ON );
				}
			}
		}
	}
	return $t_count + 1;
}

# --------------------
# Check if the realname is a unique
# Trigger an error if the username is not valid
function user_ensure_realname_unique( $p_username, $p_realname ) {
	if( 1 > user_is_realname_unique( $p_username, $p_realname ) ) {
		trigger_error( ERROR_USER_REAL_MATCH_USER, ERROR );
	}
}

# --------------------
# Check if the realname is a valid (does not account for uniqueness)
# true: valid, false: not valid
function user_is_realname_valid( $p_realname ) {
	return( !string_contains_scripting_chars( $p_realname ) );
}

# --------------------
# Check if the realname is a valid (does not account for uniqueness), if not, trigger an error
function user_ensure_realname_valid( $p_realname ) {
	if( !user_is_realname_valid( $p_realname ) ) {
		trigger_error( ERROR_USER_REAL_NAME_INVALID, ERROR );
	}
}

# --------------------
# Check if the username is a valid username (does not account for uniqueness)
#  realname can match
# Return true if it is, false otherwise
function user_is_name_valid( $p_username ) {

	# The DB field is hard-coded. USERLEN should not be modified.
	if( strlen( $p_username ) > USERLEN ) {
		return false;
	}

	# username must consist of at least one character
	if( is_blank( $p_username ) ) {
		return false;
	}

	# Only allow a basic set of characters
	if( 0 == preg_match( config_get( 'user_login_valid_regex' ), $p_username ) ) {
		return false;
	}

	# We have a valid username
	return true;
}

# --------------------
# Check if the username is a valid username (does not account for uniqueness)
# Trigger an error if the username is not valid
function user_ensure_name_valid( $p_username ) {
	if( !user_is_name_valid( $p_username ) ) {
		trigger_error( ERROR_USER_NAME_INVALID, ERROR );
	}
}

# --------------------
# return whether user is monitoring bug for the user id and bug id
function user_is_monitoring_bug( $p_user_id, $p_bug_id ) {
	$c_user_id = db_prepare_int( $p_user_id );
	$c_bug_id = db_prepare_int( $p_bug_id );

	$t_bug_monitor_table = db_get_table( 'mantis_bug_monitor_table' );

	$query = "SELECT COUNT(*)
				  FROM $t_bug_monitor_table
				  WHERE user_id=" . db_param() . " AND bug_id=" . db_param();

	$result = db_query_bound( $query, Array( $c_user_id, $c_bug_id ) );

	if( 0 == db_result( $result ) ) {
		return false;
	} else {
		return true;
	}
}

# --------------------
# return true if the user has access of ADMINISTRATOR or higher, false otherwise
function user_is_administrator( $p_user_id ) {
	$t_access_level = user_get_field( $p_user_id, 'access_level' );

	if( $t_access_level >= ADMINISTRATOR ) {
		return true;
	} else {
		return false;
	}
}

# --------------------
# return true is the user account is protected, false otherwise
function user_is_protected( $p_user_id ) {
	if( ON == user_get_field( $p_user_id, 'protected' ) ) {
		return true;
	} else {
		return false;
	}
}

# --------------------
# Trigger an ERROR if the user account is protected
function user_ensure_unprotected( $p_user_id ) {
	if( user_is_protected( $p_user_id ) ) {
		trigger_error( ERROR_PROTECTED_ACCOUNT, ERROR );
	}
}

# --------------------
# return true is the user account is enabled, false otherwise
function user_is_enabled( $p_user_id ) {
	if( ON == user_get_field( $p_user_id, 'enabled' ) ) {
		return true;
	} else {
		return false;
	}
}

# --------------------
# count the number of users at or greater than a specific level
function user_count_level( $p_level = ANYBODY ) {
	$t_level = db_prepare_int( $p_level );
	$t_user_table = db_get_table( 'mantis_user_table' );
	$query = "SELECT COUNT(id) FROM $t_user_table WHERE access_level>=" . db_param();
	$result = db_query_bound( $query, Array( $t_level ) );

	# Get the list of connected users
	$t_users = db_result( $result );

	return $t_users;
}

# --------------------
# Return an array of user ids that are logged in.
# A user is considered logged in if the last visit timestamp is within the
# specified session duration.
# If the session duration is 0, then no users will be returned.
function user_get_logged_in_user_ids( $p_session_duration_in_minutes ) {
	$t_session_duration_in_minutes = (integer) $p_session_duration_in_minutes;

	# if session duration is 0, then there is no logged in users.
	if( $t_session_duration_in_minutes == 0 ) {
		return array();
	}

	# Generate timestamp
	# @@@ The following code may not be portable accross DBMS.
	$t_last_timestamp_threshold = mktime( date( "H" ), date( "i" ) - 1 * $t_session_duration_in_minutes, date( "s" ), date( "m" ), date( "d" ), date( "Y" ) );
	$c_last_timestamp_threshold = date( "Y-m-d H:i:s", $t_last_timestamp_threshold );

	$t_user_table = db_get_table( 'mantis_user_table' );

	# Execute query
	$query = "SELECT id FROM $t_user_table WHERE last_visit > '$c_last_timestamp_threshold'";
	$result = db_query( $query, 1 );

	# Get the list of connected users
	$t_users_connected = array();
	while( $row = db_fetch_array( $result ) ) {
		$t_users_connected[] = $row['id'];
	}

	return $t_users_connected;
}

# ===================================
# Creation / Deletion / Updating
# ===================================
# --------------------
# 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 = '' ) {
	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 );
	$c_enabled = db_prepare_bool( $p_enabled );

	user_ensure_name_valid( $p_username );
	user_ensure_name_unique( $p_username );
	user_ensure_realname_valid( $p_realname );
	user_ensure_realname_unique( $p_username, $p_realname );
	email_ensure_valid( $p_email );

    $t_login_method = config_get( 'login_method' );
    if ( $t_login_method == LDAP) {
            if ( !ldap_create_user( $p_username, $p_password, $p_realname, $p_email ) ) {
                    return '';
            }
    }

	$t_seed = $p_email . $p_username;
	$t_cookie_string = auth_generate_unique_cookie_string( $t_seed );
	$t_user_table = db_get_table( '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
				    ( " . db_param() . ", " . db_param() . ", " . db_param() . ", " . db_param() . ", " . db_param()  . ",
				     " . db_param() . "," . db_param() . "," . db_param() . "," . db_param() . ", " . db_param() . ")";
	db_query_bound( $query, Array( $c_username, $c_email, $c_password, db_now(), db_now(), $c_enabled, $c_access_level, 0, $t_cookie_string, $c_realname ) );

	# Create preferences for the user
	$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
	# preferences.  Now set the real value of protected.
	if( $c_protected ) {
		user_set_field( $t_user_id, 'protected', 1 );
	}

	# Send notification email
	if( !is_blank( $p_email ) ) {
		$t_confirm_hash = auth_generate_confirm_hash( $t_user_id );
		email_signup( $t_user_id, $p_password, $t_confirm_hash );
	}

	return $t_cookie_string;
}

# --------------------
# Signup a user.
# If the use_ldap_email config option is on then tries to find email using
# ldap. $p_email may be empty, but the user wont get any emails.
# returns false if error, the generated cookie string if ok
function user_signup( $p_username, $p_email = null ) {
	if( null === $p_email ) {
		$p_email = '';

		# @@@ I think the ldap_email stuff is a bit borked
		#  Where is it being set?  When is it being used?
		#  Shouldn't we override an email that is passed in here?
		#  If the user doesn't exist in ldap, is the account created?
		#  If so, there password won't get set anywhere...  (etc)
		#  RJF: I was going to check for the existence of an LDAP email.
		#  however, since we can't create an LDAP account at the moment,
		#  and we don't know the user password in advance, we may not be able
		#  to retrieve it anyway.
		#  I'll re-enable this once a plan has been properly formulated for LDAP
		#  account management and creation.
		/*			$t_email = '';
					if ( ON == config_get( 'use_ldap_email' ) ) {
						$t_email = ldap_email_from_username( $p_username );
					}

					if ( !is_blank( $t_email ) ) {
						$p_email = $t_email;
					}
		*/
	}

	$p_email = trim( $p_email );

	$t_seed = $p_email . $p_username;

	# Create random password
	$t_password = auth_generate_random_password( $t_seed );

	return user_create( $p_username, $t_password, $p_email );
}

# --------------------
# delete project-specific user access levels.
# returns true when successfully deleted
function user_delete_project_specific_access_levels( $p_user_id ) {
	$c_user_id = db_prepare_int( $p_user_id );

	user_ensure_unprotected( $p_user_id );

	$t_project_user_list_table = db_get_table( 'mantis_project_user_list_table' );

	$query = "DELETE FROM $t_project_user_list_table
				  WHERE user_id=" . db_param();
	db_query_bound( $query, Array( $c_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# delete profiles for the specified user
# returns true when successfully deleted
function user_delete_profiles( $p_user_id ) {
	$c_user_id = db_prepare_int( $p_user_id );

	user_ensure_unprotected( $p_user_id );

	$t_user_profile_table = db_get_table( 'mantis_user_profile_table' );

	# Remove associated profiles
	$query = "DELETE FROM $t_user_profile_table
				  WHERE user_id=" . db_param();
	db_query_bound( $query, Array( $c_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# delete a user account (account, profiles, preferences, project-specific access levels)
# returns true when the account was successfully deleted
function user_delete( $p_user_id ) {
	$c_user_id = db_prepare_int( $p_user_id );
	$t_user_table = db_get_table( 'mantis_user_table' );

	user_ensure_unprotected( $p_user_id );

	# Remove associated profiles
	user_delete_profiles( $p_user_id );

	# Remove associated preferences
	user_pref_delete_all( $p_user_id );

	# Remove project specific access levels
	user_delete_project_specific_access_levels( $p_user_id );

	# unset non-unique realname flags if necessary
	if( config_get( 'differentiate_duplicates' ) ) {
		$c_realname = db_prepare_string( user_get_field( $p_user_id, 'realname' ) );
		$query = "SELECT id
					FROM $t_user_table
					WHERE realname=" . db_param();
		$result = db_query_bound( $query, Array( $c_realname ) );
		$t_count = db_num_rows( $result );

		if( $t_count == 2 ) {

			# unset flags if there are now only 2 unique names
			for( $i = 0;$i < $t_count;$i++ ) {
				$t_user_id = db_result( $result, $i );
				user_set_field( $t_user_id, 'duplicate_realname', OFF );
			}
		}
	}

	user_clear_cache( $p_user_id );

	# Remove account
	$query = "DELETE FROM $t_user_table
				  WHERE id=" . db_param();
	db_query_bound( $query, Array( $c_user_id ) );

	return true;
}

# ===================================
# Data Access
# ===================================
# --------------------
# get a user id from a username
#  return false if the username does not exist
function user_get_id_by_name( $p_username ) {
	global $g_cache_user;
	if( $t_user = user_search_cache( 'username', $p_username ) ) {
		return $t_user['id'];
	}

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT *
				  FROM $t_user_table
				  WHERE username=" . db_param();
	$result = db_query_bound( $query, Array( $p_username ) );

	if( 0 == db_num_rows( $result ) ) {
		return false;
	} else {
		$row = db_fetch_array( $result );
		user_cache_database_result( $row );
		return $row['id'];
	}
}

# Get a user id from an email address
function user_get_id_by_email( $p_email ) {
	global $g_cache_user;
	if( $t_user = user_search_cache( 'email', $p_email ) ) {
		return $t_user['id'];
	}

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT *
				  FROM $t_user_table
				  WHERE email=" . db_param();
	$result = db_query_bound( $query, Array( $p_email ) );

	if( 0 == db_num_rows( $result ) ) {
		return false;
	} else {
		$row = db_fetch_array( $result );
		user_cache_database_result( $row );
		return $row['id'];
	}
}

# Get a user id from their real name
function user_get_id_by_realname( $p_realname ) {
	global $g_cache_user;
	if( $t_user = user_search_cache( 'realname', $p_realname ) ) {
		return $t_user['id'];
	}

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "SELECT *
				  FROM $t_user_table
				  WHERE realname=" . db_param();
	$result = db_query_bound( $query, Array( $p_realname ) );

	if( 0 == db_num_rows( $result ) ) {
		return false;
	} else {
		$row = db_fetch_array( $result );
		user_cache_database_result( $row );
		return $row['id'];
	}
}

# --------------------
# return all data associated with a particular user name
#  return false if the username does not exist
function user_get_row_by_name( $p_username ) {
	$t_user_id = user_get_id_by_name( $p_username );

	if( false === $t_user_id ) {
		return false;
	}

	$row = user_get_row( $t_user_id );

	return $row;
}

# --------------------
# return a user row
function user_get_row( $p_user_id ) {
	return user_cache_row( $p_user_id );
}

# --------------------
# return the specified user field for the user id
function user_get_field( $p_user_id, $p_field_name ) {
	if( NO_USER == $p_user_id ) {
		trigger_error( 'user_get_field() for NO_USER', WARNING );
		return "@null@";
	}

	$row = user_get_row( $p_user_id );

	if( isset( $row[$p_field_name] ) ) {
		return $row[$p_field_name];
	} else {
		error_parameters( $p_field_name );
		trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
		return '';
	}
}

# --------------------
# lookup the user's email in LDAP or the db as appropriate
function user_get_email( $p_user_id ) {
	$t_email = '';
	if( ON == config_get( 'use_ldap_email' ) ) {
		$t_email = ldap_email( $p_user_id );
	}
	if( is_blank( $t_email ) ) {
		$t_email = user_get_field( $p_user_id, 'email' );
	}
	return $t_email;
}

# --------------------
# lookup the user's realname
function user_get_realname( $p_user_id ) {
	$t_realname = user_get_field( $p_user_id, 'realname' );

	return $t_realname;
}

# --------------------
# return the username or a string "user<id>" if the user does not exist
# if show_realname is set, replace the name with a realname (if set)
function user_get_name( $p_user_id ) {
	$row = user_cache_row( $p_user_id, false );

	if( false == $row ) {
		return lang_get( 'prefix_for_deleted_users' ) . (int) $p_user_id;
	} else {
		if( ON == config_get( 'show_realname' ) ) {
			if( is_blank( $row['realname'] ) ) {
				return $row['username'];
			} else {
				if( isset( $row['duplicate_realname'] ) && ( ON == $row['duplicate_realname'] ) ) {
					return $row['realname'] . ' (' . $row['username'] . ')';
				} else {
					return $row['realname'];
				}
			}
		} else {
			return $row['username'];
		}
	}
}

/**
* Return the user avatar image URL
* in this first implementation, only gravatar.com avatars are supported
* @return array|bool an array( URL, width, height ) or false when the given user has no avatar
*/
function user_get_avatar( $p_user_id, $p_size = 80 ) {
	$t_email = strtolower( user_get_email( $p_user_id ) );
	if( is_blank( $t_email ) ) {
		$t_result = false;
	} else {
		$t_default_image = config_get( 'default_avatar' );
		$t_size = $p_size;

		$t_use_ssl = false;
		if( isset( $_SERVER['HTTPS'] ) && ( strtolower( $_SERVER['HTTPS'] ) != 'off' ) ) {
			$t_use_ssl = true;
		}

		if( !$t_use_ssl ) {
			$t_gravatar_domain = 'http://www.gravatar.com/';
		} else {
			$t_gravatar_domain = 'https://secure.gravatar.com/';
		}

		$t_avatar_url = $t_gravatar_domain . 'avatar.php?gravatar_id=' . md5( $t_email ) . '&amp;default=' . urlencode( $t_default_image ) . '&amp;size=' . $t_size . '&amp;rating=G';
		$t_result = array(
			$t_avatar_url,
			$t_size,
			$t_size,
		);
	}

	return $t_result;
}

# --------------------
# return the user's access level
#  account for private project and the project user lists
function user_get_access_level( $p_user_id, $p_project_id = ALL_PROJECTS ) {
	$t_access_level = user_get_field( $p_user_id, 'access_level' );

	if( $t_access_level >= ADMINISTRATOR ) {
		return $t_access_level;
	}

	$t_project_access_level = project_get_local_user_access_level( $p_project_id, $p_user_id );

	if( false === $t_project_access_level ) {
		return $t_access_level;
	} else {
		return $t_project_access_level;
	}
}

$g_user_accessible_projects_cache = null;

# --------------------
# retun an array of project IDs to which the user has access
function user_get_accessible_projects( $p_user_id, $p_show_disabled = false ) {
	global $g_user_accessible_projects_cache;

	if( null !== $g_user_accessible_projects_cache && auth_get_current_user_id() == $p_user_id && false == $p_show_disabled ) {
		return $g_user_accessible_projects_cache;
	}

	if( access_has_global_level( config_get( 'private_project_threshold' ), $p_user_id ) ) {
		$t_projects = project_hierarchy_get_subprojects( ALL_PROJECTS, $p_show_disabled );
	} else {
		$t_project_table = db_get_table( 'mantis_project_table' );
		$t_project_user_list_table = db_get_table( 'mantis_project_user_list_table' );
		$t_project_hierarchy_table = db_get_table( 'mantis_project_hierarchy_table' );

		$t_public = VS_PUBLIC;
		$t_private = VS_PRIVATE;

		$result = null;
		if( $p_show_disabled ) {
			$query = "SELECT p.id, p.name, ph.parent_id
								  FROM $t_project_table p
								  LEFT JOIN $t_project_user_list_table u
								    ON p.id=u.project_id AND u.user_id=" . db_param() . "
								  LEFT JOIN $t_project_hierarchy_table ph
								    ON ph.child_id = p.id
								  WHERE
									( p.view_state=" . db_param() . "
									    OR (p.view_state=" . db_param() . "
										    AND
									        u.user_id=" . db_param() . " )
									)
					  ORDER BY p.name";
			$result = db_query_bound( $query, Array( $p_user_id, $t_public, $t_private, $p_user_id ) );
		} else {
			$query = "SELECT p.id, p.name, ph.parent_id
							  FROM $t_project_table p
							  LEFT JOIN $t_project_user_list_table u
							    ON p.id=u.project_id AND u.user_id=" . db_param() . "
							  LEFT JOIN $t_project_hierarchy_table ph
							    ON ph.child_id = p.id
							  WHERE p.enabled = " . db_param() . " AND
								( p.view_state=" . db_param() . "
								    OR (p.view_state=" . db_param() . "
									    AND
								        u.user_id=" . db_param() . " )
								)
				  ORDER BY p.name";
			$result = db_query_bound( $query, Array( $p_user_id, true, $t_public, $t_private, $p_user_id ) );
		}

		$row_count = db_num_rows( $result );

		$t_projects = array();

		for( $i = 0;$i < $row_count;$i++ ) {
			$row = db_fetch_array( $result );

			$t_projects[$row['id']] = ( $row['parent_id'] === NULL ) ? 0 : $row['parent_id'];
		}

		# prune out children where the parents are already listed. Make the list
		#  first, then prune to avoid pruning a parent before the child is found.
		$t_prune = array();
		foreach( $t_projects as $t_id => $t_parent ) {
			if(( $t_parent !== 0 ) && isset( $t_projects[$t_parent] ) ) {
				$t_prune[] = $t_id;
			}
		}
		foreach( $t_prune as $t_id ) {
			unset( $t_projects[$t_id] );
		}
		$t_projects = array_keys( $t_projects );
	}

	if( auth_get_current_user_id() == $p_user_id ) {
		$g_user_accessible_projects_cache = $t_projects;
	}

	return $t_projects;
}

$g_user_accessible_subprojects_cache = null;

# --------------------
# retun an array of subproject IDs of a certain project to which the user has access
function user_get_accessible_subprojects( $p_user_id, $p_project_id, $p_show_disabled = false ) {
	global $g_user_accessible_subprojects_cache;

	if( null !== $g_user_accessible_subprojects_cache && auth_get_current_user_id() == $p_user_id && false == $p_show_disabled ) {
		if( isset( $g_user_accessible_subprojects_cache[$p_project_id] ) ) {
			return $g_user_accessible_subprojects_cache[$p_project_id];
		} else {
			return Array();
		}
	}

	$t_project_table = db_get_table( 'mantis_project_table' );
	$t_project_user_list_table = db_get_table( 'mantis_project_user_list_table' );
	$t_project_hierarchy_table = db_get_table( 'mantis_project_hierarchy_table' );

	$t_public = VS_PUBLIC;
	$t_private = VS_PRIVATE;

	if( access_has_global_level( config_get( 'private_project_threshold' ), $p_user_id ) ) {
		$t_enabled_clause = $p_show_disabled ? '' : 'p.enabled = ' . db_param() . ' AND';
		$query = "SELECT DISTINCT p.id, p.name, ph.parent_id
					  FROM $t_project_table p
					  LEFT JOIN $t_project_hierarchy_table ph
					    ON ph.child_id = p.id
					  WHERE $t_enabled_clause
					  	 ph.parent_id IS NOT NULL
					  ORDER BY p.name";
		$result = db_query_bound( $query, ( $p_show_disabled ? null : Array( true ) ) );
	} else {
		$p = 0;
		$query = "SELECT DISTINCT p.id, p.name, ph.parent_id
					  FROM $t_project_table p
					  LEFT JOIN $t_project_user_list_table u
					    ON p.id = u.project_id AND u.user_id=" . db_param( $p++ ) . "
					  LEFT JOIN $t_project_hierarchy_table ph
					    ON ph.child_id = p.id
					  WHERE " . ( $p_show_disabled ? '' : ( 'p.enabled = ' . db_param( $p++ ) . ' AND ' ) ) . '
					  	ph.parent_id IS NOT NULL AND
						( p.view_state=' . db_param( $p++ ) . '
						    OR (p.view_state=' . db_param( $p++ ) . '
							    AND
						        u.user_id=' . db_param( $p++ ) . ' )
						)
					  ORDER BY p.name';
		$result = db_query_bound( $query, ( $p_show_disabled ? Array( $p_user_id, $t_public, $t_private, $p_user_id ) : Array( $p_user_id, 1, $t_public, $t_private, $p_user_id ) ) );
	}

	$row_count = db_num_rows( $result );

	$t_projects = array();

	for( $i = 0;$i < $row_count;$i++ ) {
		$row = db_fetch_array( $result );

		if( !isset( $t_projects[$row['parent_id']] ) ) {
			$t_projects[$row['parent_id']] = array();
		}

		array_push( $t_projects[$row['parent_id']], $row['id'] );
	}

	if( auth_get_current_user_id() == $p_user_id ) {
		$g_user_accessible_subprojects_cache = $t_projects;
	}

	if( !isset( $t_projects[$p_project_id] ) ) {
		$t_projects[$p_project_id] = array();
	}

	return $t_projects[$p_project_id];
}

# --------------------
function user_get_all_accessible_subprojects( $p_user_id, $p_project_id ) {

	# @@@ (thraxisp) Should all top level projects be a sub-project of ALL_PROJECTS implicitly?
	#   affects how news and some summaries are generated
	$t_todo = user_get_accessible_subprojects( $p_user_id, $p_project_id );
	$t_subprojects = Array();

	while( $t_todo ) {
		$t_elem = array_shift( $t_todo );
		if( !in_array( $t_elem, $t_subprojects ) ) {
			array_push( $t_subprojects, $t_elem );
			$t_todo = array_merge( $t_todo, user_get_accessible_subprojects( $p_user_id, $t_elem ) );
		}
	}

	return $t_subprojects;
}

# --------------------
# return the number of open assigned bugs to a user in a project
function user_get_assigned_open_bug_count( $p_user_id, $p_project_id = ALL_PROJECTS ) {
	$t_bug_table = db_get_table( 'mantis_bug_table' );

	$t_where_prj = helper_project_specific_where( $p_project_id, $p_user_id ) . " AND";

	$t_resolved = config_get( 'bug_resolved_status_threshold' );

	$query = "SELECT COUNT(*)
				  FROM $t_bug_table
				  WHERE $t_where_prj
				  		status<'$t_resolved' AND
				  		handler_id=" . db_param();
	$result = db_query_bound( $query, Array( $p_user_id ) );

	return db_result( $result );
}

# --------------------
# return the number of open reported bugs by a user in a project
function user_get_reported_open_bug_count( $p_user_id, $p_project_id = ALL_PROJECTS ) {
	$t_bug_table = db_get_table( 'mantis_bug_table' );

	$t_where_prj = helper_project_specific_where( $p_project_id, $p_user_id ) . " AND";

	$t_resolved = config_get( 'bug_resolved_status_threshold' );

	$query = "SELECT COUNT(*)
				  FROM $t_bug_table
				  WHERE $t_where_prj
						  status<'$t_resolved' AND
						  reporter_id=" . db_param();
	$result = db_query_bound( $query, Array( $p_user_id ) );

	return db_result( $result );
}

# --------------------
# return a profile row
function user_get_profile_row( $p_user_id, $p_profile_id ) {
	$c_user_id = db_prepare_int( $p_user_id );
	$c_profile_id = db_prepare_int( $p_profile_id );

	$t_user_profile_table = db_get_table( 'mantis_user_profile_table' );

	$query = "SELECT *
				  FROM $t_user_profile_table
				  WHERE id=" . db_param() . " AND
				  		user_id=" . db_param();
	$result = db_query_bound( $query, Array( $c_profile_id, $c_user_id ) );

	if( 0 == db_num_rows( $result ) ) {
		trigger_error( ERROR_USER_PROFILE_NOT_FOUND, ERROR );
	}

	$row = db_fetch_array( $result );

	return $row;
}

# --------------------
# Get failed login attempts
function user_is_login_request_allowed( $p_user_id ) {
	$t_max_failed_login_count = config_get( 'max_failed_login_count' );
	$t_failed_login_count = user_get_field( $p_user_id, 'failed_login_count' );
	return( $t_failed_login_count < $t_max_failed_login_count || OFF == $t_max_failed_login_count );
}

# --------------------
# Get 'lost password' in progress attempts
function user_is_lost_password_request_allowed( $p_user_id ) {
	if( OFF == config_get( 'lost_password_feature' ) ) {
		return false;
	}
	$t_max_lost_password_in_progress_count = config_get( 'max_lost_password_in_progress_count' );
	$t_lost_password_in_progress_count = user_get_field( $p_user_id, 'lost_password_request_count' );
	return( $t_lost_password_in_progress_count < $t_max_lost_password_in_progress_count || OFF == $t_max_lost_password_in_progress_count );
}

# --------------------
# return the bug filter parameters for the specified user
function user_get_bug_filter( $p_user_id, $p_project_id = null ) {
	if( null === $p_project_id ) {
		$t_project_id = helper_get_current_project();
	} else {
		$t_project_id = $p_project_id;
	}

	$t_view_all_cookie_id = filter_db_get_project_current( $t_project_id, $p_user_id );
	$t_view_all_cookie = filter_db_get_filter( $t_view_all_cookie_id, $p_user_id );
	$t_cookie_detail = explode( '#', $t_view_all_cookie, 2 );

	if( !isset( $t_cookie_detail[1] ) ) {
		return false;
	}

	$t_filter = unserialize( $t_cookie_detail[1] );

	$t_filter = filter_ensure_valid_filter( $t_filter );

	return $t_filter;
}

# ===================================
# Data Modification
# ===================================
# --------------------
# Update the last_visited field to be now
function user_update_last_visit( $p_user_id ) {
	$c_user_id = db_prepare_int( $p_user_id );
	$c_value = db_now();

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				  SET last_visit= " . db_param() . "
				  WHERE id=" . db_param();

	db_query_bound( $query, Array( $c_value, $c_user_id ) );

	user_update_cache( $p_user_id, 'last_visit', $c_value );

	# db_query errors on failure so:
	return true;
}

# --------------------
# Increment the number of times the user has logegd in
# This function is only called from the login.php script
function user_increment_login_count( $p_user_id ) {
	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				SET login_count=login_count+1
				WHERE id=" . db_param();

	db_query_bound( $query, Array( $p_user_id ) );

	user_clear_cache( $p_user_id );

	# db_query errors on failure so:
	return true;
}

# --------------------
# Reset to zero the failed login attempts
function user_reset_failed_login_count_to_zero( $p_user_id ) {
	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				SET failed_login_count=0
				WHERE id=" . db_param();
	db_query_bound( $query, Array( $p_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# Increment the failed login count by 1
function user_increment_failed_login_count( $p_user_id ) {
	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				SET failed_login_count=failed_login_count+1
				WHERE id=" . db_param();
	db_query_bound( $query, Array( $p_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# Reset to zero the 'lost password' in progress attempts
function user_reset_lost_password_in_progress_count_to_zero( $p_user_id ) {
	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				SET lost_password_request_count=0
				WHERE id=" . db_param();
	db_query_bound( $query, Array( $p_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# Increment the failed login count by 1
function user_increment_lost_password_in_progress_count( $p_user_id ) {
	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				SET lost_password_request_count=lost_password_request_count+1
				WHERE id=" . db_param();
	db_query_bound( $query, Array( $p_user_id ) );

	user_clear_cache( $p_user_id );

	return true;
}

# --------------------
# Set a user field
function user_set_field( $p_user_id, $p_field_name, $p_field_value ) {
	$c_user_id = db_prepare_int( $p_user_id );
	$c_field_name = db_prepare_string( $p_field_name );
	$c_field_value = db_prepare_string( $p_field_value );

	if( $p_field_name != "protected" ) {
		user_ensure_unprotected( $p_user_id );
	}

	$t_user_table = db_get_table( 'mantis_user_table' );

	$query = "UPDATE $t_user_table
				  SET $c_field_name=" . db_param() . "
				  WHERE id=" . db_param();

	db_query_bound( $query, Array( $c_field_value, $c_user_id ) );

	user_clear_cache( $p_user_id );

	# db_query errors on failure so:
	return true;
}

# --------------------
# Set the user's default project
function user_set_default_project( $p_user_id, $p_project_id ) {
	return user_pref_set_pref( $p_user_id, 'default_project', (int) $p_project_id );
}

# --------------------
# 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 ) {
	if( !$p_allow_protected ) {
		user_ensure_unprotected( $p_user_id );
	}

	$t_email = user_get_field( $p_user_id, 'email' );
	$t_username = user_get_field( $p_user_id, 'username' );

	# When the password is changed, invalidate the cookie to expire sessions that
	# may be active on all browsers.
	$t_seed = $t_email . $t_username;
	$c_cookie_string = auth_generate_unique_cookie_string( $t_seed );

	$c_user_id = db_prepare_int( $p_user_id );
	$c_password = auth_process_plain_password( $p_password );
	$c_user_table = db_get_table( 'mantis_user_table' );

    $t_login_method = config_get( 'login_method' );
    if ( $t_login_method == LDAP) {
            if ( !ldap_passwd( $p_username, $p_password ) ) {
                    return false;
            }
    }

	$query = "UPDATE $c_user_table
				  SET password=" . db_param() . ",
				  cookie_string=" . db_param() . "
				  WHERE id=" . db_param();
	db_query_bound( $query, Array( $c_password, $c_cookie_string, $c_user_id ) );

	# db_query errors on failure so:
	return true;
}

# --------------------
# Set the user's email to the given string after checking that it is a valid email
function user_set_email( $p_user_id, $p_email ) {
	email_ensure_valid( $p_email );

	return user_set_field( $p_user_id, 'email', $p_email );
}

# --------------------
# Set the user's realname to the given string after checking validity
function user_set_realname( $p_user_id, $p_realname ) {

	# @@@ TODO:	ensure_realname_valid( $p_realname );

	return user_set_field( $p_user_id, 'realname', $p_realname );
}

# --------------------
# Set the user's username to the given string after checking that it is valid
function user_set_name( $p_user_id, $p_username ) {
	user_ensure_name_valid( $p_username );
	user_ensure_name_unique( $p_username );

	return user_set_field( $p_user_id, 'username', $p_username );
}

# --------------------
# Reset the user's password
#  Take into account the 'send_reset_password' setting
#   - if it is ON, generate a random password and send an email
#      (unless the second parameter is false)
#   - if it is OFF, set the password to blank
#  Return false if the user is protected, true if the password was
#   successfully reset
function user_reset_password( $p_user_id, $p_send_email = true ) {
	$t_protected = user_get_field( $p_user_id, 'protected' );

	# Go with random password and email it to the user
	if( ON == $t_protected ) {
		return false;
	}

	# @@@ do we want to force blank password instead of random if
	#      email notifications are turned off?
	#     How would we indicate that we had done this with a return value?
	#     Should we just have two functions? (user_reset_password_random()
	#     and user_reset_password() )?
	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 );

		# Send notification email
		if( $p_send_email ) {
			$t_confirm_hash = auth_generate_confirm_hash( $p_user_id );
			email_send_confirm_hash_url( $p_user_id, $t_confirm_hash );
		}
	} else {

		# use blank password, no emailing
		$t_password = auth_process_plain_password( '' );
		user_set_field( $p_user_id, 'password', $t_password );

		# reset the failed login count because in this mode there is no emailing
		user_reset_failed_login_count_to_zero( $p_user_id );
	}

	return true;
}
user_api.php (40,762 bytes)   

Activities

rgomes1997

rgomes1997

2008-12-12 14:59

reporter   ~0020364

Files attached:

mantisbt-20081208.patch
config_inc.php
ldap_api.inc
user_api.inc

grangeway

grangeway

2008-12-12 19:06

reporter   ~0020365

rgomes, i'll take a look at this tomorrow morning.

rgomes1997

rgomes1997

2011-01-31 18:55

reporter   ~0028135

When I rolled out to Mantis 1.2.4 I've decided to centralize user maintenance on a separate web application. Now Mantis only consumes data from LDAP and performs authentication. No need anymore to create users in the backing store.

See module 'selfregister', part of simpleSAMLphp
http://rnd.feide.no/2010/03/25/new_simplesamlphp_module_selfregister/

Thanks

rogueresearch

rogueresearch

2021-01-06 13:28

reporter   ~0064942

What is the status of this? I suspect it predates the LDAP/AD support that's already in Mantis at this point?

dregad

dregad

2021-01-06 17:51

developer   ~0064946

I don't think this belongs in MantisBT core. If such functionality is needed, it should be implemented as an authentication plugin.