array( 'arguments' => array('form' => NULL), 'file' => 'ldapdata.theme.inc' ), 'ldapdata_ldap_attribute' => array( 'arguments' => array('value' => NULL, 'type' => NULL), 'file' => 'ldapdata.theme.inc' ), ); } /** * Implements hook_menu(). */ function ldapdata_menu() { return array( 'admin/settings/ldap/ldapdata' => array( 'title' => 'Data', 'description' => 'Configure LDAP data to Drupal profiles synchronization settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('ldapdata_admin_settings'), 'access arguments' => array('administer ldap modules'), 'file' => 'ldapdata.admin.inc', ), 'admin/settings/ldap/ldapdata/edit' => array( 'title' => 'Data', 'page callback' => 'drupal_get_form', 'page arguments' => array('ldapdata_admin_edit', 4, 5), 'type' => MENU_CALLBACK, 'access arguments' => array('administer ldap modules'), 'file' => 'ldapdata.admin.inc', ), 'admin/settings/ldap/ldapdata/edit/%/test' => array( 'title' => 'Test LDAP Server', 'page callback' => '_ldapdata_ajax_test', 'page arguments' => array(5), 'type' => MENU_CALLBACK, 'access arguments' => array('administer ldap modules'), 'file' => 'ldapdata.admin.inc', ), 'admin/settings/ldap/ldapdata/reset' => array( 'title' => 'Data', 'page callback' => 'drupal_get_form', 'page arguments' => array('ldapdata_admin_edit', 4, 5), 'type' => MENU_CALLBACK, 'access arguments' => array('administer ldap modules'), 'file' => 'ldapdata.admin.inc', ), ); } /** * Implements hook_user(). */ function ldapdata_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'categories': return _ldapdata_user_categories(); case 'form': return _ldapdata_user_form($account, $category); case 'load': _ldapdata_user_load($account); break; case 'login': _ldapdata_user_login($account); break; case 'submit': _ldapdata_user_submit($edit, $account, $category); break; case 'view': return _ldapdata_user_view($account); } } ////////////////////////////////////////////////////////////////////////////// // hook_user() functions /** * Implements hook_user() categories operation. */ function _ldapdata_user_categories() { return array( array( 'name' => LDAPDATA_USER_DATA, 'title' => t(LDAPDATA_USER_TAB), 'weight' => 50, 'access callback' => 'ldapdata_category_access', 'access arguments' => array(1) ) ); } /** * Checks if LDAP data category should be printed. */ function ldapdata_category_access($account) { if (!isset($account->ldap_authentified)) return FALSE; return (_ldapdata_ldap_info($account, 'mapping_type') == LDAPDATA_MAP_ATTRIBUTES && count(_ldapdata_ldap_info($account, 'ldapdata_rwattrs')) > 0) ? TRUE : FALSE; } /** * Implements hook_user() categories operation. * Only used for editable LDAP attributes with no Drupal equivalents. */ function _ldapdata_user_form(&$user, $category) { global $_ldapdata_ldap; // Force LDAP sync. _ldapdata_user_load($user, TRUE); $attributes = _ldapdata_ldap_info($user, 'ldapdata_rwattrs'); if (!isset($user->ldap_dn) || $category != LDAPDATA_USER_DATA || _ldapdata_ldap_info($user, 'mapping_type') != LDAPDATA_MAP_ATTRIBUTES || !$attributes) return; $bind_info = _ldapdata_edition($user); if (!$_ldapdata_ldap->connect($bind_info['dn'], $bind_info['pass'])) { watchdog('ldapdata', "User form: user %name's data could not be read in the LDAP directory", array('%name' => $user->name), WATCHDOG_WARNING); return; } $entry = $_ldapdata_ldap->retrieveAttributes($user->ldap_dn); $form['ldap_attributes'] = array( '#title' => t(LDAPDATA_PROFILE), '#type' => 'fieldset', ); foreach (_ldapdata_ldap_info($user, 'ldapdata_attrs') as $attr_name => $attr_info) { if (in_array($attr_name, $attributes)) { array_shift($attr_info); $value = isset($entry[drupal_strtolower($attr_name)]) ? $entry[drupal_strtolower($attr_name)][0] : ''; $form['ldap_attributes']['ldap_'. $attr_name] = _ldapdata_attribute_form($value, $attr_info); } } $_ldapdata_ldap->disconnect(); return $form; } /** * Implements hook_user() load operation. */ function _ldapdata_user_load(&$account, $sync = FALSE) { global $user, $_ldapdata_ldap; // Setup the global $_ldapdata_ldap object. if (!_ldapdata_init($account)) return; if (!$sync && (LDAPDATA_SYNC < 1 || LDAPDATA_SYNC < 2 && $user->uid != $account->uid)) return; static $accounts_synced = array(); if (isset($accounts_synced[$account->uid])) { return; } // See http://drupal.org/node/91786 about user_node(). // User can be edited by the user or by other authorized users. $authmap = user_get_authmaps($account->name); if (!isset($authmap['ldapauth']) || (_ldapdata_ldap_info($account, 'mapping_type') == LDAPDATA_MAP_NOTHING)) { return; } $accounts_synced[$account->uid] = TRUE; $bind_info = _ldapdata_edition($account); if (!$_ldapdata_ldap->connect($bind_info['dn'], $bind_info['pass'])) { watchdog('ldapdata', "User load: user %name's data could not be read in the LDAP directory", array('%name' => $account->name), WATCHDOG_WARNING); return; } if ($entry = $_ldapdata_ldap->retrieveAttributes($account->ldap_dn)) { $ldap_drupal_reverse_mappings = _ldapdata_reverse_mappings($account->ldap_config); // Retrieve profile fields list. $profile_fields = _ldapdata_retrieve_profile_fields(); $drupal_fields = array(); foreach (_ldapdata_reverse_mappings($account->ldap_config) as $drupal_field => $ldap_attr) { $value = isset($entry[strtolower($ldap_attr)]) ? $entry[strtolower($ldap_attr)][0] : ''; // Is it a profile field? if (is_numeric($drupal_field)) { if ($profile_field = isset($profile_fields[$drupal_field]) ? $profile_fields[$drupal_field] : NULL) { if ($row = db_fetch_array(db_query("SELECT value FROM {profile_values} WHERE fid = '%d' AND uid = '%d'", $drupal_field, $account->uid))) { if ($row['value'] != $value) db_query("UPDATE {profile_values} SET value = '%s' WHERE fid = '%d' AND uid = '%d'", $value, $drupal_field, $account->uid); } else { db_query("INSERT INTO {profile_values} (value, fid, uid) VALUES ('%s', '%d', '%d')", $value, $drupal_field, $account->uid); } $account->$drupal_field = $value; } } // Then it might be a Drupal field. else if (isset($account->$drupal_field) && !in_array($drupal_field, array('pass'))) { $drupal_fields = array_merge($drupal_fields, array($drupal_field => $value)); } } if (!empty($drupal_fields)) $account = user_save($account, $drupal_fields); } $_ldapdata_ldap->disconnect(); } /** * Implements hook_user() login operation. */ function _ldapdata_user_login(&$user) { global $_ldapdata_ldap; // Force LDAP sync. if (LDAPDATA_SYNC == 0) _ldapdata_user_load($user, TRUE); } /** * Implements hook_user() submit operation. */ function _ldapdata_user_submit(&$edit, &$user, $category) { global $_ldapdata_ldap; // Setup the global $_ldapdata_ldap object. if (!_ldapdata_init($user)) return; $authmap = user_get_authmaps($user->name); if (!isset($authmap['ldapauth'])) return; // Three cases here: // 1. User logged on and editing his LDAP entry attributes ($category == LDAPDATA_USER_DATA). // 2. User logged on and editing his Drupal account settings ($category == 'account'). // 3. OBSOLETE FROM 4.7: Password lost and being updated (category == 'account'). // Additionally: // 4. User logged on and editing his profile.module fields ($category == *any*). $writeout = array(); $editables = _ldapdata_ldap_info($user, 'ldapdata_rwattrs'); if ($category == LDAPDATA_USER_DATA && $editables) { // Case 1: $writeout = array_merge($writeout, _ldapdata_user_update_ldap_attributes($edit, $user)); } else if ($category == 'account') { // Cases 2 && 3: $writeout = array_merge($writeout, _ldapdata_user_update_drupal_account($edit, $user)); } // And now, case 4: $writeout = array_merge($writeout, _ldapdata_user_update_profile($edit, $user)); if ($writeout) { $bind_info = _ldapdata_edition($user); if (!$_ldapdata_ldap->connect($bind_info['dn'], $bind_info['pass'])) { watchdog('ldapdata', "User update: user %name's data could not be updated in the LDAP directory", array('%name' => $user->name), WATCHDOG_NOTICE); return; } $_ldapdata_ldap->writeAttributes($user->ldap_dn, $writeout); } $_ldapdata_ldap->disconnect(); } /** * Implements hook_user() view operation. */ function _ldapdata_user_view(&$user) { global $_ldapdata_ldap; // Setup the global $_ldapdata_ldap object. if (!_ldapdata_init($user)) return; $authmap = user_get_authmaps($user->name); if (!isset($authmap['ldapauth'])) return; $bind_info = _ldapdata_edition($user); if (!$_ldapdata_ldap->connect($bind_info['dn'], $bind_info['pass'])) { watchdog('ldapdata', "User view: user %name's data could not be read in the LDAP directory", array('%name' => $user->name), WATCHDOG_WARNING); return; } $entry = $_ldapdata_ldap->retrieveAttributes($user->ldap_dn); $allowed_attrs = _ldapdata_ldap_info($user, 'ldapdata_roattrs'); $items = array(); $i = 0; foreach (_ldapdata_ldap_info($user, 'ldapdata_attrs') as $attr_name => $attr_info) { if (in_array($attr_name, $allowed_attrs)) { $item = array( '#type' => 'user_profile_item', '#title' => t($attr_info[2]), '#value' => theme('ldapdata_ldap_attribute', $entry[drupal_strtolower($attr_name)][0], $attr_info[0]), '#weight' => $i++, ); $items[$attr_name] = $item; } } if (!empty($items)) { $user->content[t(LDAPDATA_PROFILE)] = array_merge(array( '#type' => 'user_profile_category', '#title' => t(LDAPDATA_PROFILE), '#attributes' => array('class' => 'ldapdata-entry'), '#weight' => LDAPDATA_PROFILE_WEIGHT, ), $items); } } ////////////////////////////////////////////////////////////////////////////// // Auxiliary functions /** * Find out which LDAP attributes should be synced back to LDAP.. * * @param $edit * A submitted form data. * @param $user * A user object. * * @return * An associated array of attributes to write to LDAP. */ function _ldapdata_user_update_ldap_attributes(&$edit, &$user) { $writeout = array(); $editables = _ldapdata_ldap_info($user, 'ldapdata_rwattrs'); foreach ($edit as $edit_attr => $value) { // Preventing a POST data injection: we check allowance to write value. if (($ldap_attr = preg_replace('/^ldap_(.*)$/', '$1', $edit_attr)) && in_array($ldap_attr, $editables)) $writeout[$ldap_attr] = $value; unset($edit[$edit_attr]); } return $writeout; } /** * Find out which Drupal attributes should be synced back to LDAP.. * * @param $edit * A submitted form data. * @param $user * A user object. * * @return * An associated array of attributes to write to LDAP. */ function _ldapdata_user_update_drupal_account(&$edit, &$user) { global $_ldapdata_ldap; $writeout = array(); if (isset($user->ldap_dn) && _ldapdata_ldap_info($user, 'mapping_type') == LDAPDATA_MAP_ATTRIBUTES) { // Case 2: updating account data. $d2l_map = _ldapdata_reverse_mappings($user->ldap_config); foreach ($edit as $key => $value) { if ($ldap_attr = isset($d2l_map[$key]) ? $d2l_map[$key] : NULL) { if ($key == 'pass') { if ($value) { $pw = $_ldapdata_ldap->getOption('encrypted') ? '{md5}'. base64_encode(pack('H*', md5($value))) : $value; $writeout[$ldap_attr] = $pw; } } else { $writeout[$ldap_attr] = $value; } } } } return $writeout; } /** * Find out which profile attributes should be synced back to LDAP. * * @param $edit * A submitted form data. * @param $user * A user object. * * @return * An associated array of attributes to write to LDAP. */ function _ldapdata_user_update_profile(&$edit, &$user) { if (_ldapdata_ldap_info($user, 'mapping_type') != LDAPDATA_MAP_ATTRIBUTES) return array(); $ldap_drupal_reverse_mappings = _ldapdata_reverse_mappings($user->ldap_config); // Retrieve profile fields list. $profile_fields = _ldapdata_retrieve_profile_fields(); // Compare against $edit list. $writeout = array(); foreach ($profile_fields as $key => $field) { if (isset($edit[$field]) && isset($ldap_drupal_reverse_mappings[$key])) { $writeout[$ldap_drupal_reverse_mappings[$key]] = $edit[$field]; } } return $writeout; } /** * Create HTML form element for the writtable LDAP attribute. * * @param $value * A current value in LDAP. * @param $info * An array with the HTML from element definitions. * * @return * An array of the form element. */ function _ldapdata_attribute_form($value, $info) { switch (array_shift($info)) { case 'textfield': $form = array( '#type' => 'textfield', '#title' => array_shift($info), '#default_value' => $value, '#size' => array_shift($info), '#maxlength' => array_shift($info), '#description' => array_shift($info), '#attributes' => array_shift($info), '#required' => array_shift($info), ); break; case 'password': $form = array( '#type' => 'password', '#title' => array_shift($info), '#default_value' => $value, '#size' => array_shift($info), '#maxlength' => array_shift($info), '#description' => array_shift($info), ); break; } return $form; } /** * Retrieve profile fields. * * @return * An array of the form element. */ function _ldapdata_retrieve_profile_fields() { $fields = array(); if (module_exists('profile')) { $result = db_query("SELECT * FROM {profile_fields}"); while ($row = db_fetch_object($result)) { $fields[$row->fid] = $row->name; } } return $fields; } /** * Retrieve drupal user fields which can be synced with LDAP. * * @return * An array of user fields. */ function _ldapdata_retrieve_standard_user_fields() { // pablom - // This commented code below would return all possible values, // but maybe that's not appropriate. // // $fields = array(); // $result = db_query('SHOW COLUMNS FROM {users}'); // while ($row = db_fetch_object($result)) { // $fields[] = $row->Field; // } // Rather, I'll use my benevolent dictator powers // to return only sensible ones. $fields = array( 'mail' => 'mail', 'pass' => 'pass', 'signature' => 'signature', ); return $fields; } /** * Retrieve reverse LDAP to drupal mappings. * * @return * An array of drupal keys pointing to LDAP attributes. */ function _ldapdata_reverse_mappings($sid) { $map = array(); foreach (_ldapdata_ldap_info($sid, 'ldapdata_mappings') as $key => $value) { if (($drupal_key = preg_replace('/^ldap_amap-(.*)$/', '$1', $key)) && !in_array($drupal_key, array('access', 'status'))) $map[$drupal_key] = $value; } return $map; } /** * Retrieve LDAP write credentials. * * @param $sid * A server ID or user object. * * @return * An array with the LDAP write username and password. */ function _ldapdata_edition($sid) { if (!($sid = is_object($sid) ? (isset($sid->ldap_config) ? $sid->ldap_config : NULL) : $sid)) return; $row = db_fetch_object(db_query("SELECT ldapdata_binddn, ldapdata_bindpw FROM {ldapauth} WHERE sid = %d", $sid)); return array( 'dn' => $row->ldapdata_binddn ? $row->ldapdata_binddn : (isset($_SESSION['ldap_login']['dn']) ? $_SESSION['ldap_login']['dn'] : ''), 'pass' => $row->ldapdata_bindpw ? $row->ldapdata_bindpw : (isset($_SESSION['ldap_login']['pass']) ? $_SESSION['ldap_login']['pass'] : ''), ); } /** * Filter LDAP attributes. * * @param $sid * A LDAP server ID. * @param $attributes * An array of LDAP attributes. * * @return * A filtered array of LDAP attributes. */ function _ldapdata_attribute_filter($sid, $attributes) { if ($code = _ldapdata_ldap_info($sid, 'ldapdata_filter_php')) $attributes = eval($code); return $attributes; } /** * Initiates the LDAPInterfase class. * * @param $sid * A server ID or user object. * * @return */ function _ldapdata_init($sid) { global $_ldapdata_ldap; if (!($sid = is_object($sid) ? (isset($sid->ldap_config) ? $sid->ldap_config : NULL) : $sid)) return; static $servers = array(); if (!isset($servers[$sid])) $servers[$sid] = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE status = 1 AND sid = %d", $sid)); if ($servers[$sid]) { // Other modules can invoke user load from hook_init() before ldapdata. require_once(drupal_get_path('module', 'ldapdata') .'/includes/LDAPInterface.inc'); $_ldapdata_ldap = new LDAPInterface(); $_ldapdata_ldap->setOption('sid', $sid); $_ldapdata_ldap->setOption('name', $servers[$sid]->name); $_ldapdata_ldap->setOption('server', $servers[$sid]->server); $_ldapdata_ldap->setOption('port', $servers[$sid]->port); $_ldapdata_ldap->setOption('tls', $servers[$sid]->tls); $_ldapdata_ldap->setOption('encrypted', $servers[$sid]->encrypted); $_ldapdata_ldap->setOption('basedn', $servers[$sid]->basedn); $_ldapdata_ldap->setOption('user_attr', $servers[$sid]->user_attr); $_ldapdata_ldap->setOption('attr_filter', '_ldapdata_attribute_filter'); return $_ldapdata_ldap; } return FALSE; } /** * Retrieve the saved ldapdata saved setting. * * @param $sid * A server ID or user object. * @param $req * An attribute name. * * @return * The attribute value. */ function _ldapdata_ldap_info($sid, $req) { if (!($sid = is_object($sid) ? (isset($sid->ldap_config) ? $sid->ldap_config : NULL) : $sid)) return; static $servers = array(); if (!isset($servers[$sid])) $servers[$sid] = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE sid = %d", $sid)); switch ($req) { case 'mapping_type': $ldapdata_mappings = !empty($servers[$sid]->ldapdata_mappings) ? unserialize($servers[$sid]->ldapdata_mappings) : array(); return isset($ldapdata_mappings['access']) ? $ldapdata_mappings['access'] : LDAPDATA_MAP_NOTHING; case 'ldapdata_mappings': return !empty($servers[$sid]->ldapdata_mappings) ? unserialize($servers[$sid]->ldapdata_mappings) : array(); case 'ldapdata_roattrs': return !empty($servers[$sid]->ldapdata_roattrs) ? unserialize($servers[$sid]->ldapdata_roattrs) : array(); case 'ldapdata_rwattrs': return !empty($servers[$sid]->ldapdata_rwattrs) ? unserialize($servers[$sid]->ldapdata_rwattrs) : array(); case 'ldapdata_binddn': return $servers[$sid]->ldapdata_binddn; case 'ldapdata_bindpw': return $servers[$sid]->ldapdata_bindpw; case 'ldapdata_attrs': return !empty($servers[$sid]->ldapdata_attrs) ? unserialize($servers[$sid]->ldapdata_attrs) : array(); case 'ldapdata_filter_php': return $servers[$sid]->ldapdata_filter_php; } }