diff --git a/ldap_authentication/LdapAuthenticationConf.class.php b/ldap_authentication/LdapAuthenticationConf.class.php index 4a15262..6c53bd7 100644 --- a/ldap_authentication/LdapAuthenticationConf.class.php +++ b/ldap_authentication/LdapAuthenticationConf.class.php @@ -107,6 +107,73 @@ class LdapAuthenticationConf { * @var int */ public $emailUpdate = LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_DEFAULT; + + /** + * Email default handling option + * + * This affects how email addresses that are empty are handled by + * the authentication process. + * + * LDAP_AUTHENTICATION_EMAIL_TEMPLATE_NONE -- leaves the email empty + * LDAP_AUTHENTICATION_EMAIL_TEMPLATE_IF_EMPTY (default) -- if the email is empty, it will be replaced + * LDAP_AUTHENTICATION_EMAIL_TEMPLATE_ALWAYS -- always use the template + * + * @var int + */ + public $emailTemplateHandling = LDAP_AUTHENTICATION_EMAIL_TEMPLATE_DEFAULT; + + /** + * Email template. + * + * @var string + */ + public $emailTemplate = LDAP_AUTHENTICATION_DEFAULT_TEMPLATE; + + /** + * Whether or not to display a notification to the user on login, prompting + * them to change their email. + * + * @var boolean + */ + public $templateUsagePromptUser = LDAP_AUTHENTICATION_TEMPLATE_USAGE_PROMPT_USER_DEFAULT; + + /** + * Whether or not to avoid updating the email address of the user if the + * template was used to generate it. + * + * @var boolean + */ + public $templateUsageNeverUpdate = LDAP_AUTHENTICATION_TEMPLATE_USAGE_NEVER_UPDATE_DEFAULT; + + /** + * Whether or not to use the email template if there is a user with a different + * login name but same email address in the system. + * + * @var boolean + */ + public $templateUsageResolveConflict = LDAP_AUTHENTICATION_TEMPLATE_USAGE_RESOLVE_CONFLICT_DEFAULT; + + /** + * A PCRE regular expression (minus the delimiter and flags) that will be used + * if $templateUsagePromptUser is set to true to determine if the email + * address is a fake one or not. + * + * By allowing this to be customized, we let the administrators handle older + * patterns should they decide to change the existing one, as well as avoiding + * the complexity of determining a proper regex from the template. + * + * @var string + */ + public $templateUsagePromptRegex = LDAP_AUTHENTICATION_DEFAULT_TEMPLATE_REGEX; + + /** + * Controls whether or not we should check on login if the email template was + * used and redirect the user if needed. + * + * @var boolean + */ + public $templateUsageRedirectOnLogin = LDAP_AUTHENTICATION_REDIRECT_ON_LOGIN_DEFAULT; + /** @@ -191,6 +258,13 @@ class LdapAuthenticationConf { 'ssoNotifyAuthentication', 'ldapImplementation', 'cookieExpire', + 'emailTemplate', + 'emailTemplateHandling', + 'templateUsagePromptUser', + 'templateUsageNeverUpdate', + 'templateUsageResolveConflict', + 'templateUsagePromptRegex', + 'templateUsageRedirectOnLogin', ); public function hasEnabledAuthenticationServers() { diff --git a/ldap_authentication/LdapAuthenticationConfAdmin.class.php b/ldap_authentication/LdapAuthenticationConfAdmin.class.php index e2a6d40..59a5d36 100644 --- a/ldap_authentication/LdapAuthenticationConfAdmin.class.php +++ b/ldap_authentication/LdapAuthenticationConfAdmin.class.php @@ -79,6 +79,11 @@ class LdapAuthenticationConfAdmin extends LdapAuthenticationConf { LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE => t('Update stored email if LDAP email differs at login but don\'t notify user.'), LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_DISABLE => t('Don\'t update stored email if LDAP email differs at login.'), ); + $values['emailTemplateHandlingOptions'] = array( + LDAP_AUTHENTICATION_EMAIL_TEMPLATE_NONE => t('Never use the template.'), + LDAP_AUTHENTICATION_EMAIL_TEMPLATE_IF_EMPTY => t('Use the template if no email address was provided by the LDAP server.'), + LDAP_AUTHENTICATION_EMAIL_TEMPLATE_ALWAYS => t('Always use the template.'), + ); /** @@ -182,7 +187,17 @@ class LdapAuthenticationConfAdmin extends LdapAuthenticationConf { public $emailUpdateDefault = LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE_NOTIFY; public $emailUpdateOptions; - + + public $emailTemplateHandlingDefault = LDAP_AUTHENTICATION_EMAIL_TEMPLATE_DEFAULT; + public $emailTemplateHandlingOptions; + + public $emailTemplateDefault = LDAP_AUTHENTICATION_DEFAULT_TEMPLATE; + + public $templateUsagePromptUserDefault = LDAP_AUTHENTICATION_TEMPLATE_USAGE_PROMPT_USER_DEFAULT; + + public $templateUsagePromptRegexDefault = LDAP_AUTHENTICATION_DEFAULT_TEMPLATE_REGEX; + + public $templateUsageNeverUpdateDefault = LDAP_AUTHENTICATION_TEMPLATE_USAGE_NEVER_UPDATE_DEFAULT; /** * 5. Single Sign-On / Seamless Sign-On @@ -391,7 +406,66 @@ class LdapAuthenticationConfAdmin extends LdapAuthenticationConf { '#default_value' => $this->emailUpdate, '#options' => $this->emailUpdateOptions, ); - + + $form['email']['template'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#title' => t('Email Templates'), + ); + + $form['email']['template']['emailTemplateHandling'] = array( + '#type' => 'radios', + '#title' => t('Email Template Handling'), + '#required' => 1, + '#default_value' => $this->emailTemplateHandling, + '#options' => $this->emailTemplateHandlingOptions + ); + + $form['email']['template']['emailTemplate'] = array( + '#type' => 'textfield', + '#title' => t('Email Template'), + '#required' => 0, + '#default_value' => $this->emailTemplate, + ); + + $form['email']['template']['templateUsageResolveConflict'] = array( + '#type' => 'checkbox', + '#title' => t('If a Drupal account already exists with the same email, but different account name, use the email template instead of the LDAP email.'), + '#default_value' => $this->templateUsageResolveConflict, + ); + + $form['email']['template']['templateUsageNeverUpdate'] = array( + '#type' => 'checkbox', + '#title' => t('Ignore the Email Update settings and never update the stored email if the template is used.'), + '#default_value' => $this->templateUsageNeverUpdate, + ); + + $form['email']['prompts'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#title' => t('User Email Prompt'), + '#description' => t('These settings allow the user to fill in their email address after logging in if the template was used to generate their email address.'), + ); + + $form['email']['prompts']['templateUsagePromptUser'] = array( + '#type' => 'checkbox', + '#title' => t('Prompt user for email on every page load.'), + '#default_value' => $this->templateUsagePromptUser, + ); + + $form['email']['prompts']['templateUsageRedirectOnLogin'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect the user to the form after logging in.'), + '#default_value' => $this->templateUsageRedirectOnLogin, + ); + + $form['email']['prompts']['templateUsagePromptRegex'] = array( + '#type' => 'textfield', + '#default_value' => $this->templateUsagePromptRegex, + '#title' => t('Template Regex'), + '#description' => t('This regex will be used to determine if the template was used to create an account.'), + ); + $form['password'] = array( '#type' => 'fieldset', @@ -559,6 +633,13 @@ class LdapAuthenticationConfAdmin extends LdapAuthenticationConf { $this->ssoNotifyAuthentication = ($values['ssoNotifyAuthentication']) ? (int)$values['ssoNotifyAuthentication'] : NULL; $this->cookieExpire = ($values['cookieExpire']) ? (int)$values['cookieExpire'] : NULL; $this->ldapImplementation = ($values['ldapImplementation']) ? (string)$values['ldapImplementation'] : NULL; + $this->emailTemplateHandling = ($values['emailTemplateHandling']) ? (int) $values['emailTemplateHandling'] : NULL; + $this->emailTemplate = ($values['emailTemplate']) ? $values['emailTemplate'] : ''; + $this->templateUsagePromptUser = ($values['templateUsagePromptUser']) ? 1 : 0; + $this->templateUsageResolveConflict = ($values['templateUsageResolveConflict']) ? 1 : 0; + $this->templateUsagePromptRegex = ($values['templateUsagePromptRegex']) ? $values['templateUsagePromptRegex'] : ''; + $this->templateUsageRedirectOnLogin = ($values['templateUsageRedirectOnLogin']) ? 1 : 0; + $this->templateUsageNeverUpdate = ($values['templateUsageNeverUpdate']) ? 1 : 0; } public function drupalFormSubmit($values) { diff --git a/ldap_authentication/ldap_authentication.inc b/ldap_authentication/ldap_authentication.inc index 9319c89..bc10e0b 100644 --- a/ldap_authentication/ldap_authentication.inc +++ b/ldap_authentication/ldap_authentication.inc @@ -60,6 +60,9 @@ function _ldap_authentication_login_form_alter(&$form, &$form_state, $form_id) { if (isset($auth_conf->loginUIPasswordTxt)) { $form['pass']['#description'] = t($auth_conf->loginUIPasswordTxt); } + if ($auth_conf->templateUsageRedirectOnLogin) { + $form['#submit'][] = 'ldap_authentication_check_for_email_template'; + } } } @@ -115,6 +118,23 @@ function _ldap_authentication_form_user_profile_form_alter(&$form, $form_state) } } +/** + * Replaces the email address in $ldap_user with one from the template in + * $auth_conf. + * + * @param array $ldap_user + * LDAP user entry + * @param LdapAuthenticationConf $auth_conf + * LDAP authentication configuration class. + */ +function _ldap_authentication_replace_user_email(&$ldap_user, $auth_conf, $tokens) { + // fallback template in case one was not specified. + $template = '@username@localhost'; + if (!empty($auth_conf->emailTemplate)) { + $template = $auth_conf->emailTemplate; + } + $ldap_user['mail'] = format_string($template, $tokens); +} /** * user form validation will take care of username, pwd fields @@ -242,6 +262,33 @@ function _ldap_authentication_user_login_authenticate_validate(&$form_state, $re } $watchdog_tokens['%drupal_accountname'] = $drupal_accountname; + // @todo maybe we can add more tokens? + $email_template_tokens = array( + '@username' => $drupal_accountname, + ); + + $email_template_used = FALSE; + + /** + * Ensures that we respect the email template handling settings. + */ + if (!empty($auth_conf->emailTemplate)) { + switch ($auth_conf->emailTemplateHandling) { + case LDAP_AUTHENTICATION_EMAIL_TEMPLATE_IF_EMPTY: + if (!empty($ldap_user['mail'])) { + break; + } + // deliberate fallthrough + case LDAP_AUTHENTICATION_EMAIL_TEMPLATE_ALWAYS: + _ldap_authentication_replace_user_email($ldap_user, $auth_conf, $email_template_tokens); + if ($detailed_watchdog_log) { + watchdog('ldap_authentication', 'Using template generated email for %username', $watchdog_tokens, WATCHDOG_DEBUG); + } + $email_template_used = TRUE; + break; + } + } + /** * VI. Find or create corresponding drupal account and set authmaps * @@ -307,10 +354,13 @@ function _ldap_authentication_user_login_authenticate_validate(&$form_state, $re * VI.C: existing Drupal account with incorrect email. fix email if appropriate * */ - if ($drupal_account_exists && $drupal_account->mail != $ldap_user['mail'] && ( - $auth_conf->emailUpdate == LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE_NOTIFY || - $auth_conf->emailUpdate == LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE - )) { + if ((!($auth_conf->templateUsageNeverUpdate && $email_template_used)) && + $drupal_account_exists && + $drupal_account->mail != $ldap_user['mail'] && + ( + $auth_conf->emailUpdate == LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE_NOTIFY || + $auth_conf->emailUpdate == LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE + )) { $user_edit = array('mail' => $ldap_user['mail']); $watchdog_tokens['%username'] = $drupal_account->name; @@ -337,15 +387,29 @@ function _ldap_authentication_user_login_authenticate_validate(&$form_state, $re // VI.C.1 Do not provision Drupal account if another account has same email. if ($account_with_same_email = user_load_by_mail($ldap_user['mail'])) { + $error = TRUE; /** * username does not exist but email does. Since user_external_login_register does not deal with * mail attribute and the email conflict error needs to be caught beforehand, need to throw error here */ - $watchdog_tokens['%duplicate_name'] = $account_with_same_email->name; - watchdog('ldap_authentication', 'LDAP user with DN %dn has email address - (%mail) conflict with a drupal user %duplicate_name', $watchdog_tokens, WATCHDOG_ERROR); - drupal_set_message(t('Another user already exists in the system with the same email address. You should contact the system administrator in order to solve this conflict.'), 'error'); - return; + if ($auth_conf->templateUsageResolveConflict && (!$email_template_used)) { + if ($detailed_watchdog_log) { + watchdog('ldap_authentication', 'Conflict detected, using template generated email for %username', $watchdog_tokens, WATCHDOG_DEBUG); + } + _ldap_authentication_replace_user_email($ldap_user, $auth_conf, $email_template_tokens); + $email_template_used = TRUE; + // recheck with the template email to make sure it doesn't also exist. + if ($account_with_same_email = user_load_by_mail($ldap_user['mail'])) { + $error = TRUE; + } + } + if ($error) { + $watchdog_tokens['%duplicate_name'] = $account_with_same_email->name; + watchdog('ldap_authentication', 'LDAP user with DN %dn has email address + (%mail) conflict with a drupal user %duplicate_name', $watchdog_tokens, WATCHDOG_ERROR); + drupal_set_message(t('Another user already exists in the system with the same email address. You should contact the system administrator in order to solve this conflict.'), 'error'); + return; + } } // VI.C.2 Do not provision Drupal account if provisioning disabled @@ -384,6 +448,12 @@ function _ldap_authentication_user_login_authenticate_validate(&$form_state, $re else { $user_edit = array('name' => $drupal_accountname, 'status' => 1); } + + // If the email template was used, we want to pass in the email that was + // generated so that its not overridden by the provisioner. + if ($email_template_used) { + $user_edit['mail'] = $ldap_user['mail']; + } // don't pass in ldap user to provisionDrupalAccount, because want to requery with correct attributes needed // this may be a case where efficiency dictates querying for all attributes @@ -396,6 +466,11 @@ function _ldap_authentication_user_login_authenticate_validate(&$form_state, $re } else { user_set_authmaps($drupal_account, array('authname_ldap_user' => $authname)); + // Using Rules allows emails to be fired and many other possible reactions + // to the creation of a user. + if (module_exists('rules')) { + rules_invoke_event('ldap_user_created', $drupal_account, $email_template_used); + } } } diff --git a/ldap_authentication/ldap_authentication.module b/ldap_authentication/ldap_authentication.module index 96bec83..965a4d9 100644 --- a/ldap_authentication/ldap_authentication.module +++ b/ldap_authentication/ldap_authentication.module @@ -29,6 +29,19 @@ define('LDAP_AUTHENTICATION_EMAIL_FIELD_DISABLE', 3); define('LDAP_AUTHENTICATION_EMAIL_FIELD_ALLOW', 4); define('LDAP_AUTHENTICATION_EMAIL_FIELD_DEFAULT', 3); +define('LDAP_AUTHENTICATION_EMAIL_TEMPLATE_NONE', 1); +define('LDAP_AUTHENTICATION_EMAIL_TEMPLATE_ALWAYS', 2); +define('LDAP_AUTHENTICATION_EMAIL_TEMPLATE_IF_EMPTY', 3); +define('LDAP_AUTHENTICATION_EMAIL_TEMPLATE_DEFAULT', 3); + +define('LDAP_AUTHENTICATION_DEFAULT_TEMPLATE', '@username@fake-domain.com'); + +define('LDAP_AUTHENTICATION_DEFAULT_TEMPLATE_REGEX', '.*@fake-domain\\.com'); + +define('LDAP_AUTHENTICATION_TEMPLATE_USAGE_NEVER_UPDATE_DEFAULT', 0); +define('LDAP_AUTHENTICATION_TEMPLATE_USAGE_RESOLVE_CONFLICT_DEFAULT', 0); +define('LDAP_AUTHENTICATION_TEMPLATE_USAGE_PROMPT_USER_DEFAULT', 0); + define('LDAP_AUTHENTICATION_PASSWORD_FIELD_SHOW', 2); define('LDAP_AUTHENTICATION_PASSWORD_FIELD_HIDE', 3); define('LDAP_AUTHENTICATION_PASSWORD_FIELD_ALLOW', 4); @@ -52,12 +65,23 @@ define('LDAP_AUTHENTICATION_HELP_LINK_TEXT_DEFAULT', 'Logon Help'); define('LDAP_AUTHENTICATION_DISABLED_FOR_BAD_CONF_MSG' , 'The site logon is currently not working due to a configuration error. Please see logs for additional details.'); define('LDAP_AUTHENTICATION_COOKIE_EXPIRE', 0); +define('LDAP_AUTHENTICATION_REDIRECT_ON_LOGIN_DEFAULT', 0); + /** * Implements hook_menu(). */ function ldap_authentication_menu() { $items = array(); + $items['user/ldap-profile-update'] = array( + 'title' => 'Update Profile', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ldap_authentication_profile_update_form'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + 'file' => 'ldap_authentication.pages.inc', + ); + $items['admin/config/people/ldap/authentication'] = array( 'title' => 'Authentication', 'description' => 'Configure LDAP Authentication', @@ -157,6 +181,72 @@ function ldap_authentication_help($path, $arg) { } /** + * Helper function that determines whether or not the user's profile + * is valid or needs to be updated on login. + * + * Currently this only checks if mail is valid or not according to the + * authentication settings. + * + * @return boolean + * TRUE if the user's profile is valid, otherwise FALSE. + * + */ +function _ldap_authentication_verify_user_profile() { + global $user; + $result = TRUE; + // We only want non-anonymous and non-1 users. + if ($user->uid > 1) { + // we store the value in the session for speed. + if (isset($_SESSION['ldap_authentication_template']) && + isset($_SESSION['ldap_authentication_template']['verify_user_profile'])) { + return $_SESSION['ldap_authentication_template']['verify_user_profile']; + } + if (ldap_authentication_ldap_authenticated($user)) { + $auth_conf = ldap_authentication_get_valid_conf(); + $regex = '`' . $auth_conf->templateUsagePromptRegex . '`i'; + if (preg_match($regex, $user->mail)) { + $result = FALSE; + } + $_SESSION['ldap_authentication_template'] = array( + 'verify_user_profile' => $result, + ); + } + } + return TRUE; +} + +/** + * Implements hook_init(). + */ +function ldap_authentication_init() { + $auth_conf = ldap_authentication_get_valid_conf(); + if ($auth_conf->templateUsagePromptUser) { + ldap_authentication_check_for_email_template(); + } +} + +/** + * Form submit callback to check for an email template and redirect if needed. + */ +function ldap_authentication_check_for_email_template() { + if (!_ldap_authentication_verify_user_profile()) { + $url = 'user/ldap-profile-update'; + $cp = current_path(); + // avoid redirects on these two pages. + if ($cp != $url && $cp != 'user/logout') { + if (isset($_GET['destination'])) { + unset($_GET['destination']); + } + drupal_goto($url, array( + 'query' => array( + 'next' => current_path(), + ), + )); + } + } +} + +/** * Implements hook_info(). */ function ldap_authentication_info($field = 0) { diff --git a/ldap_authentication/ldap_authentication.pages.inc b/ldap_authentication/ldap_authentication.pages.inc new file mode 100644 index 0000000..06678fa --- /dev/null +++ b/ldap_authentication/ldap_authentication.pages.inc @@ -0,0 +1,61 @@ + 'textfield', + '#required' => TRUE, + '#title' => t('Email Address'), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Update Profile'), + ); + return $form; +} + +/** + * Form validator for updating the profile. + * + * @see ldap_authentication_profile_update_form(). + */ +function ldap_authentication_profile_update_form_validate($form, &$form_state) { + if (!filter_var($form_state['values']['mail'], FILTER_VALIDATE_EMAIL)) { + form_set_error('mail', t('You must specify a valid email address.')); + } + $existing = user_load_by_mail($form_state['values']['mail']); + if ($existing) { + form_set_error('mail', t('This email address is already in user.')); + } + $auth = ldap_authentication_get_valid_conf(); + $regex = '`' . $auth->templateUsagePromptRegex . '`i'; + if (preg_match($regex, $form_state['values']['mail'])) { + form_set_error('mail', t('This email address still matches the invalid email template.')); + } +} + +/** + * Form submit handler for updating the profile. + * + * @see ldap_authentication_profile_update_form(). + */ +function ldap_authentication_profile_update_form_submit($form, &$form_state) { + global $user; + if (user_save($user, array( + 'mail' => $form_state['values']['mail'], + ))) { + // prevents the cached setting from being used again. + unset($_SESSION['ldap_authentication_template']); + $form_state['redirect'] = isset($_GET['next']) ? $_GET['next'] : ''; + drupal_set_message(t('Your profile has been updated.')); + } +} diff --git a/ldap_authentication/ldap_authentication.rules.inc b/ldap_authentication/ldap_authentication.rules.inc new file mode 100644 index 0000000..edba931 --- /dev/null +++ b/ldap_authentication/ldap_authentication.rules.inc @@ -0,0 +1,27 @@ + array( + 'label' => t('User created from LDAP entry'), + 'module' => 'ldap_authentication', + 'arguments' => array( + 'user' => array( + 'type' => 'user', + 'label' => t('The user created.'), + ), + 'email_template_used' => array( + 'type' => 'boolean', + 'label' => t('Whether or not the email template was used to create the user.'), + ), + ), + ), + ); +} diff --git a/ldap_user/LdapUserConf.class.php b/ldap_user/LdapUserConf.class.php index 608be84..2e2e840 100644 --- a/ldap_user/LdapUserConf.class.php +++ b/ldap_user/LdapUserConf.class.php @@ -767,7 +767,7 @@ class LdapUserConf { * */ public function synchToDrupalAccount($drupal_user, &$user_edit, $prov_event = LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user = NULL, $save = FALSE) { - + $debug = array( 'account' => $drupal_user, 'user_edit' => $user_edit,