data); if (isset($userdata['origo_session'])) { $_origo_auth_session = $userdata['origo_session']; } else { $_origo_auth_session = ''; } } return $_origo_auth_session; } /** * Implementation of hook_menu(). * Defines Menu entries and path callbacks. */ function origo_auth_menu() { // Handles activation links of the form // '/activation/// $items['activation/%origo_auth_special/%/%'] = array( 'title' => 'Special Account Activation', 'load arguments' => array('%map', '%index'), 'page callback' => 'origo_auth_account_activation_page', 'page arguments' => array(1, 2, 3), 'access callback' => 1, 'type' => MENU_CALLBACK ); // Handles password-reset links of the form // '/reset/// $items['reset/%origo_auth_special/%/%'] = array( 'title' => 'Password Recovery', 'load arguments' => array('%map', '%index'), 'page callback' => 'origo_auth_reset_password_page', 'page arguments' => array(1, 2, 3), 'access callback' => 1, 'type' => MENU_CALLBACK ); return $items; } /** * Menu loader callback for paths '(activation | reset)/$user/$timestamp/$hash'. * Checks if the url has the right form. */ function origo_auth_special_load($first, $map, $index) { // check that there were enough arguments supplied in the URL if (count($map) < 4) { return FALSE; } // check if the timestamp is numeric, and that the hash is a string if (!is_numeric($map[2]) || !is_string($map[3])) { return FALSE; } // The return value will be entered into $map at $index. // As we check for a valid username later on, just return the first argument (the username). return $first; } /* * Calls an xml-rpc function in the backend without providing a session */ function origo_auth_xml_rpc($url) { global $user; $args = func_get_args(); $result = call_user_func_array('_xmlrpc', $args); return $result; } function origo_auth_xmlrpc_session($url) { global $_origo_auth_session; $args_old = func_get_args(); for ($i = 0; $i < func_num_args(); $i++) { $args[] = $args_old[$i]; if ($i == 1) { $args[] = origo_auth_origo_session_id(); } } $result = call_user_func_array('xmlrpc', $args); if ($result === FALSE && (int)xmlrpc_errno() > 0) { if (xmlrpc_error_msg() == 'Session not valid or timed out.') { // session timed out, try to relogin // relogin _sess_load_origo_user(); $_origo_auth_session = ''; $args[2] = origo_auth_origo_session_id(); // call again $result = call_user_func_array('xmlrpc', $args); } } return $result; } /** * Implementation of hook_cron(). * Prunes sessions older than the defined lifetime. */ function origo_auth_cron() { db_query("DELETE FROM {sessions} WHERE timestamp < UNIX_TIMESTAMP() - " . variable_get('origo_cookie_lifetime', 3600)); } /** * Display help and module information. * @param section which section of the site we're displaying help * @return help text for section */ function origo_auth_help($path, $arg) { $output = ''; switch ($path) { case "admin/help#origo_auth": $output = '

'. t("Performs Origo user authentication") .'

'; break; } return $output; } /** * Implements hook form_alter. * Adds validation- and submit-handler of the login block, i.e. for * - normal login * - password recovery * - password changing * - new registration */ function origo_auth_form_alter(&$form, $form_state, $form_id) { // Password recovery if ($form_id == 'user_pass') { unset($form['#submit']); $form['#submit'][] = 'origo_auth_pass_submit'; $form['submit'] = array( '#type' => 'submit', '#value' => t('Request password recovery'), '#weight' => 2, ); // Also remove validation handler, which checks Drupal's db for an existing record // (and possibly fails when looking up the email-address). // NOTE: this may cause problems when barring users via Drupal's administrative // functions instead via Origo's backend (as the validation handler also checks for blocked users). // This can be remedied by re-implementing Drupal's validation handler sans email-check. unset($form['#validate']); } // Password changing if ($form_id == 'origo_home_settings_password_form' && ((arg(0) == 'auth') || (arg(0) == 'reset'))) { unset($form['#submit']); $form['#submit'][] = 'origo_auth_settings_password_form_submit'; } if (in_array($form_id, array('user_register', 'user_login'))) { if (isset($form['#validate'])) { if (in_array('user_login_authenticate_validate', $form['#validate'])) { // normal user login: // remove drupal's validate-handlers and install our own. unset($form['#validate']); $form['#validate'][] = 'origo_auth_login_validate'; } // registration: install our own validation handler if (in_array('user_register_validate', $form['#validate'])) { $form['#validate'][] = 'origo_auth_register_validate'; } } // registration: submit handler if (isset($form['#submit']) && in_array('user_register_submit', $form['#submit'])) { unset($form['#submit']); $form['#submit'][] = 'origo_auth_register_submit'; // change the description of the username field $form['name']['#description'] = t('Name must start with a letter, and end in either a letter or a digit.') . "
" . t('Other characters must only consist of lower case letters, numbers, hyphens (-) or underscores (_). The maximum length is 50 characters.'); } } } /** * Submit handler for password-recovery. * Tries to retrieve the user and its email from the DB, and sends a mail containing a password-reset link. */ function origo_auth_pass_submit($form, &$form_state) { $name = $form_state['values']['name']; $errors = 0; // since the name by definition cannot contain an @, we can check what has been entered if (strstr($name, '@')) { $name = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_user_from_email', $form_state['values']['name']); if ((int)xmlrpc_errno() > 0) { form_set_error('name', t('Sorry, the e-mail address %email is not is not associated to any of our users.', array('%email' => $form_state['values']['name']))); $errors++; } } if ($errors == 0) { if (!empty($name)) { $result = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_password', $name); if ((int)xmlrpc_errno() > 0) { form_set_error('name', t('Sorry, the entered user name "%name" was not found.', array('%name' => $form_state['values']['name']))); $errors++; } elseif (!empty($result)) { $password = $result; } } //empty name if (empty($name) || empty($password) || $errors > 0) { form_set_error('name', t('Sorry, the entered user name "%name" was not found.', array('%name' => $form_state['values']['name']))); $errors++; } } if ($errors == 0) { // we have $password and $name, generate authorization URL global $base_url; $email = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_email', $name); if ((int)xmlrpc_errno() > 0 || !strstr($email, '@')) { form_set_error('name', t('There was a problem retrieving your e-mail address, please contact a site administrator. ')); } $from = variable_get('origo_support_email', ''); $subject = t('Your Password Recovery Link for Origo'); $body = t('Hello '. $name .', '."\n\n".'A request to reset the password for your account has been made at Origo.') . t( 'You may now log in to '. variable_get('origo_web_home_url', '') .' by clicking on this link or copying and pasting it in your browser: '."\n") . t( origo_auth_pass_reset_url($password, $name) .' '."\n\n".'This password recovery link is only valid for 1 week or until a new password has been set. '."\n\n") . t( 'The Origo Team'); $mail_success = origo_mail('origouserpass', $email, $subject, $body, $from, array(), '-f '. variable_get('origo_support_email', '')); if ($mail_success) { watchdog('user', 'Password reset instructions mailed to %name at %email.', array('%name' => $name, '%email' => $email)); drupal_set_message(t('Further instructions have been sent to your e-mail address.')); } else { watchdog('user', 'Error mailing password reset instructions to %name at %email.', array('%name' => $name, '%email' => $email), WATCHDOG_ERROR); drupal_set_message(t('Unable to send mail. Please contact the site admin.'), 'error'); } } } /** * Page for resetting the password */ function origo_auth_reset_password_page($username, $timestamp, $hash) { drupal_set_breadcrumb(''); $result = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_password', $username); if ((int)xmlrpc_errno() > 0) { form_set_error('name', t('Sorry, the entered user name "%name" was not found.', array('%name' => $username))); $errors++; } elseif (!empty($result)) { $password = $result; } if ($hash == user_pass_rehash($password, $timestamp, $username)) { if ($timestamp + 3600*24*7 < time()) { // the authentification link is too old return t('The authentification link is older than 1 week! Please resend your ') . l('password recovery link', 'user/password', array( 'attributes' => NULL, 'query' => NULL, 'fragment' => NULL, 'absolute' => FALSE, 'html' => TRUE )) . t('. '); } else { // the link is valid, ready to reset the password! $output = t('This password recovery link is valid, you are now ready to set a new password for your account %name.', array('%name' => $username)); $output .= drupal_get_form('origo_home_settings_password_form', $username); } } else { drupal_set_message(t('Authentification failed'), 'error'); return t('The authentification link is not valid! Please recheck if the link provided in your email has been entered correctly. '); } return $output; } /** * Page for account activation */ function origo_auth_account_activation_page($username, $timestamp, $hash) { drupal_set_breadcrumb(''); $result = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_password', $username); if ((int)xmlrpc_errno() > 0) { drupal_set_message(t('Sorry, the entered user name "%name" was not found.', array('%name' => $username)), 'error'); $errors++; } elseif (!empty($result)) { $password = $result; } $result = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_email', $username); if ((int)xmlrpc_errno() > 0) { drupal_set_message(t('Sorry, the entered user name "%name" was not found.', array('%name' => $username)), 'error'); $errors++; } elseif (!empty($result)) { $email = $result; } if ($errors == 0 && $hash == md5($password . $timestamp . $username . $email)) { // the link is valid, ready to reset the password! $result = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.change_account_enabled', $username, TRUE); if ((int)xmlrpc_errno() > 0) { drupal_set_message(t('There was an error activating your account: %error', array('%error' => xmlrpc_error_msg())), 'error'); } else { $output = t('Your account %name has successfully been activated.
', array('%name' => $username)); $output .= l('Sign in', 'user') . t(' now!'); } // TODO: autologin with internal_user.login_authenticated() // origo_auth_authenticate($username, $form_values['pass']); } else { drupal_set_message(t('Authentification failed'), 'error'); return t('The authentification link is not valid! Please recheck if the link provided in your email has been entered correctly. '); } return $output; } /** * This is an override of origo_home_settings_password_form_submit */ function origo_auth_settings_password_form_submit($form, &$form_state) { $username = arg(1); $test = origo_auth_xml_rpc(variable_get('origo_api_internal', ''), 'internal_user.change_password', $username, $form_state['values']['pass']); if ((int)xmlrpc_errno() > 0) { form_set_error('pass', t(xmlrpc_error_msg())); } else { // log in user artificially origo_auth_authenticate($username, $form_state['values']['pass']); drupal_set_message(t('Password saved.')); drupal_goto('/'); } } /** * password request URL */ function origo_auth_pass_reset_url($pass, $name) { $timestamp = time(); return url( "reset/". $name ."/". $timestamp ."/". user_pass_rehash($pass, $timestamp, $name), array('query' => NULL, 'fragment' => NULL, 'absolute' => TRUE) ); } /** * Account activation (email verification) email */ function origo_auth_account_activation_url($pass, $name, $email) { $timestamp = time(); return url( "activation/". $name ."/". $timestamp ."/". md5($pass . $timestamp . $name . $email), array('query' => NULL, 'fragment' => NULL, 'absolute' => TRUE) ); } /** * User registration validation handler. */ function origo_auth_register_validate($form, &$form_state) { // Validate the e-mail address: if ($error = user_validate_mail($form_state['values']['mail'])) { form_set_error('mail', t($error)); } $blacklist= variable_get('origo_email_blacklist_file', ''); $denied= false; if (file_exists($blacklist)) { $entries = file($blacklist, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($entries as $entry) { $entry= rtrim($entry); if (strpos($form_state['values']['mail'], $entry) !== False) { $denied= True; } } } if ($denied) { form_set_error('mail', t("Invalid Email Address.")); } // Validate the username // Note: keep this regex in sync with the backend (see api-node, argument_validator.e) if (!preg_match("/^([a-z])([a-z]|[0-9]|\-|_){1,48}([a-z]|[0-9])$/", $form_state['values']['name'])) { form_set_error('name', t('Invalid username specified. Name must start with a letter, and end in either a letter or a digit. Other characters must only consist of lower case letters, numbers, hyphens (-) or underscores (_). The maximum length is 50 characters.')); } } /** * User registration submit handler. */ function origo_auth_register_submit($form, &$form_state) { $mail = $form_state['values']['mail']; $name = $form_state['values']['name']; $pass = $form_state['values']['pass']; try { // create a disabled account $result = xmlrpc(variable_get('origo_api_internal', ''), 'internal_user.add', $name, $pass, $mail); if ((int)xmlrpc_errno() != 0) { throw new Exception(xmlrpc_error_msg(), xmlrpc_errno()); } $enc_pass = xmlrpc(variable_get('origo_api_internal', ''), 'internal_user.retrieve_password', $name); if ((int)xmlrpc_errno() != 0) { throw new Exception(xmlrpc_error_msg(), xmlrpc_errno()); } // disable the account by default. the email verification link will enable the account again. $disable = xmlrpc(variable_get('origo_api_internal', ''), 'internal_user.change_account_enabled', $name, False); // generate activation email $from = variable_get('origo_support_email', ''); $subject = t('Your Activation Link for Origo'); $body = t('Hello '. $name .', '."\n\n".'Welcome to Origo, your account has been successfully created. In order to log in, you will first be asked to verify this email address, by clicking on the following link: '."\n\n") . origo_auth_account_activation_url($enc_pass, $name, $mail) ."\n\n". t( 'You may now log in to Origo at '. variable_get('origo_web_home_url', '') .'/user . '."\n") . t( 'The Origo Team'); $mail_success = origo_mail('origoregister', $mail, $subject, $body, $from, array(), '-f '. variable_get('origo_support_email', '')); if (!$mail_success) { watchdog('user', 'Error mailing password reset instructions to %name at %email.', array('%name' => $name, '%email' => $email), WATCHDOG_ERROR); drupal_set_message(t('Unable to send activation email. Please contact a site admin ('. variable_get('origo_support_email', '') .').')); } drupal_set_message(t('Your account has been created, your activation link will arrive shortly. If you did not receive the email, please check your spam folders. ')); $form_state['redirect'] = variable_get('site_frontpage', 'node/5'); } catch (Exception $e) { $msg= t( "Failed to create account. Error \"@errormsg\" (Errorno. @errorno)", array( "@errormsg" => $e->getMessage(), "@errorno" => $e->getCode(), )); form_set_error('name', $msg); } } /** * validate login form */ function origo_auth_login_validate($form, &$form_state) { if ($form_state['values']['name']) { if (user_is_blocked($form_state['values']['name'])) { // blocked in user administration form_set_error('login', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name']))); } elseif (drupal_is_denied('user', $form_state['values']['name'])) { // denied by access controls form_set_error('login', t('The name %name is a reserved username.', array('%name' => $form_state['values']['name']))); } elseif ($form_state['values']['pass']) { $user = origo_auth_authenticate($form_state['values']['name'], trim($form_state['values']['pass'])); if (!$user->uid) { form_set_error('login', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password')))); watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); } } } } /** * Encrypt text (helper function to encode the origo cookie) * * @param $content * The text to encrypt * @param $key * The secret key * * @return * The encrypted text */ function _origo_auth_encryptAES($content, $key) { // set algorithm $cp = mcrypt_module_open('rijndael-128', '', 'ofb', ''); // initialization vector if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') $enc['iv'] = mcrypt_create_iv(mcrypt_enc_get_iv_size($cp), MCRYPT_RAND); else $enc['iv'] = mcrypt_create_iv(mcrypt_enc_get_iv_size($cp), MCRYPT_DEV_RANDOM); // key size $ks = mcrypt_enc_get_key_size($cp); // key $key = substr(md5($key), 0, $ks); // initialize encryption mcrypt_generic_init($cp, $key, $enc['iv']); // encrypt string $enc['encrypted'] = mcrypt_generic($cp, $content); // deinit mcrypt_generic_deinit($cp); // close mcrypt module mcrypt_module_close($cp); return $enc; } function origo_auth_authenticate($name, $pass) { global $user; //quick and dirty hack to prevent a bug after role upgrade //TODO: remove this hack db_query("TRUNCATE {cache};"); db_query("TRUNCATE {cache_filter};"); db_query("TRUNCATE {cache_menu};"); db_query("TRUNCATE {cache_page};"); //try to log in the user $session = xmlrpc(variable_get('origo_api_internal', ''), 'internal_user.login', $name, $pass); if ($session || (int)xmlrpc_errno() == 0) { //get user role $is_owner = xmlrpc(variable_get('origo_api', ''), 'authorization.is_allowed_project', $session, 'website_edit_owner', (int) variable_get('origo_project_id', 0)); $is_member = xmlrpc(variable_get('origo_api', ''), 'authorization.is_allowed_project', $session, 'website_edit_member', (int) variable_get('origo_project_id', 0)); $is_admin = xmlrpc(variable_get('origo_api', ''), 'authorization.is_allowed_project', $session, 'project_global_admin', 0); if ($is_admin) { $roles = array('5' => TRUE); } elseif ($is_owner) { $roles = array('3' => TRUE); } elseif ($is_member) { $roles = array('4' => TRUE); } else { $roles = array(); } // Custom hook to allow altering origo's authentication process from other // modules. // Implementing hook functions are exspected to return an (un-associative) // array of roles. $ret_val = module_invoke_all('origo_auth_authenticate_roles', $session, $name); if (isset($ret_val) && sizeof($ret_val) > 0) { foreach ($ret_val as $rid) { $roles[$rid] = True; } } if ($account = user_load(array('name' => $name))) { // user already has a local account if ($account->pass == md5($pass) && $account->status == 1) { // login correct $userinfo = array('origo_session' => $session, 'roles' => $roles); $user = user_save($account, $userinfo); } else { // login incorrect $userinfo = array('pass' => md5($pass), 'status' => 1, 'origo_session' => $session, 'roles' => $roles); $user = user_save($account, $userinfo); } } else { // create user $userinfo = array('name' => $name, 'pass' => $pass, 'init' => $name, 'status' => 1, 'origo_session' => $session, 'roles' => $roles); $user = user_save('', $userinfo); } // Create Origo cookie $userdata['username'] = $name; $userdata['password'] = _origo_auth_encryptAES($pass, variable_get('origo_cookie_secret', '')); $userdata['session'] = $session; setcookie('origo_session', serialize($userdata), time() + 4200000, '/', variable_get('origo_cookie_domain', '')); } return $user; } /** * Implementation of hook_user(). */ function origo_auth_user($type, &$edit, &$user, $category = NULL) { if ($type == 'logout') { // destroy the Origo cookie on logout setcookie('origo_session', '', time() - 42000, '/', variable_get('origo_cookie_domain', '')); } if ($type = 'login') { $foo = 'logged in!'; } }