ga_login.module in Google Authenticator login 7
Same filename and directory in other branches
Main ga_login module.
File
ga_login.moduleView source
<?php
/**
* @file
* Main ga_login module.
*/
/**
* Token is valid.
*/
define('GA_LOGIN_TOKEN_VALID', 'valid');
/**
* Token is invalid.
*/
define('GA_LOGIN_TOKEN_INVALID', 'invalid');
/**
* Token is missing. Needs to be generated.
*/
define('GA_LOGIN_TOKEN_MISSING', 'missing');
/**
* Implements hook_menu().
*/
function ga_login_menu() {
$items = array();
$items['user/%user/ga_login'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'GA login',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ga_login_create_form',
1,
),
'access callback' => 'ga_login_create_access',
'access arguments' => array(
1,
),
'file' => 'ga_login.pages.inc',
);
$items['user/%user/ga_login/view'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'GA login',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ga_login_create_form',
1,
),
'access callback' => 'ga_login_create_access',
'access arguments' => array(
1,
),
'file' => 'ga_login.pages.inc',
'weight' => 0,
);
$items['user/%user/ga_login/delete'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'GA login delete',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ga_login_delete_form',
1,
),
'access callback' => 'ga_login_delete_access',
'access arguments' => array(
1,
),
'file' => 'ga_login.pages.inc',
'weight' => 1,
);
$items['admin/config/people/ga_login'] = array(
'type' => MENU_NORMAL_ITEM,
'title' => 'GA login',
'description' => 'Administer Google Authenticator login settings',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ga_login_admin_settings_form',
),
'access arguments' => array(
'administer ga_login settings',
),
'file' => 'ga_login.admin.inc',
);
return $items;
}
/**
* Access callback for creating codes.
*/
function ga_login_create_access($target_account, $account = NULL) {
if (is_null($account)) {
global $user;
$account = $user;
}
if ($account->uid == $target_account->uid) {
if (user_access('create own login code', $account) || user_access('create others login codes', $account)) {
return TRUE;
}
elseif (user_access('create own login code once', $account)) {
// Check if the user already has a code.
return !_ga_login_account_has_code($account);
}
}
return user_access('create others login codes', $account);
}
/**
* Access callback for deleting codes.
*/
function ga_login_delete_access($target_account, $account = NULL) {
// If the account does not have a code, no need to delete it.
if (!_ga_login_account_has_code($target_account)) {
return FALSE;
}
if (is_null($account)) {
global $user;
$account = $user;
}
if ($account->uid == $target_account->uid) {
// Only allow deleting of own code if they still can login.
if (user_access('login without code', $account) && (user_access('delete own login code', $account) || user_access('delete others login codes', $account))) {
return TRUE;
}
}
return user_access('delete others login codes', $account);
}
/**
* Implements hook_permission().
*/
function ga_login_permission() {
return array(
'create own login code once' => array(
'title' => t('Create own login code only once'),
'description' => t('Allows users to create their own GA login code only once and deny further generations.'),
),
'create own login code' => array(
'title' => t('Create own login code'),
'description' => t('Allows users to create their own GA login code (more than once).'),
),
'delete own login code' => array(
'title' => t('Delete own login code'),
'description' => t('Allows users to delete their own GA login code.'),
),
'create others login codes' => array(
'title' => t("Create others' login codes"),
'description' => t("Allows users to create others' GA login codes"),
'restrict access' => TRUE,
),
'delete others login codes' => array(
'title' => t("Delete others' login codes"),
'description' => t("Allows users to delete others' GA login codes"),
'restrict access' => TRUE,
),
'login without code' => array(
'title' => t('Login without code'),
'description' => t("With this permission, users don't have to fill in the GA login code"),
'restrict access' => TRUE,
),
'require code' => array(
'title' => t('Require code'),
'description' => t('With this permission, users are required to fill in the GA login code. Trumps "login without code".'),
'restrict access' => TRUE,
),
'administer ga_login settings' => array(
'title' => t('Administer GA login settings'),
'description' => t('Administer Google Authenticator login settings'),
'restrict access' => TRUE,
),
);
}
/**
* Check if the given account wants to be forced to use tfa.
*/
function _ga_login_force_tfa($account) {
$cant_make = !user_access('create own login code once', $account) && !user_access('create own login code', $account);
// If the user can't create their own code and they don't already have one,
// then we shouldn't force them to use it.
if ($cant_make && !_ga_login_account_has_code($account)) {
return FALSE;
}
// If the user belongs to any role that is required to use the code,
// it is required, unless uid 1 and we don't require uid 1 to use a code.
if (user_access('require code', $account) && ($account->uid != 1 || variable_get('ga_login_always_for_uid1', 0))) {
return TRUE;
}
if (user_access('login without code', $account)) {
return isset($account->data['ga_login_force_tfa']) ? $account->data['ga_login_force_tfa'] : FALSE;
}
return TRUE;
}
/**
* Check if the given account does have a code.
*/
function _ga_login_account_has_code($account) {
$ga = _ga_login_get_class();
$username = _ga_login_username($account);
return $ga
->hasToken($username);
}
/**
* Returns the GALoginGA class.
*/
function _ga_login_get_class() {
module_load_include('php', 'ga_login', 'ga_login.class');
return new GALoginGA(variable_get('ga_login_totp_skew', 10), variable_get('ga_login_hotp_skew', 10));
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add GA Login Code field to user login form.
*/
function ga_login_form_user_login_alter(&$form, &$form_state, $form_id) {
$form['gacode'] = array(
'#type' => 'gacode',
'#title' => t('Code'),
'#required' => FALSE,
);
$form['name']['#weight'] = 1;
$form['pass']['#weight'] = 2;
$form['gacode']['#weight'] = 3;
$form['submit']['#weight'] = 4;
if (isset($form['links'])) {
$form['links']['#weight'] = 5;
}
// Normalize keys to start from 0.
$form['#validate'] = array_values($form['#validate']);
$validate_before = array_slice($form['#validate'], 0, array_search('user_login_final_validate', $form['#validate']));
$validate_after = array_slice($form['#validate'], array_search('user_login_final_validate', $form['#validate']));
// Insert our validation function directly before user_login_final_validate.
$form['#validate'] = array_merge($validate_before, array(
'ga_login_user_login_validate',
), $validate_after);
// Add submit handler to conditionally redirect the user to create
// a new GA login code.
$form['#submit'][] = 'ga_login_user_login_submit_code_needed';
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add GA Login Code field to user login block.
*/
function ga_login_form_user_login_block_alter(&$form, &$form_state, $form_id) {
ga_login_form_user_login_alter($form, $form_state, $form_id);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add checkbox to enable two factor authentication.
*/
function ga_login_form_user_profile_form_alter(&$form, &$form_state, $form_id) {
$account = $form['#user'];
$register = $account->uid > 0 ? FALSE : TRUE;
// Add some more settings to the user profile form.
$form['ga_login'] = array(
'#type' => 'fieldset',
'#title' => t('Two factor authentication'),
'#weight' => 1,
'#access' => !$register && user_access('login without code', $account),
);
$form['ga_login']['ga_login_force_tfa'] = array(
'#type' => 'checkbox',
'#title' => t('Protect my account with two-factor-authentication'),
'#default_value' => isset($account->data['ga_login_force_tfa']) ? $account->data['ga_login_force_tfa'] : FALSE,
'#description' => t('Check this box to force two-factor-authentication during login. If you decide to do so and haven\'t yet created your key, then please also refer to <a href="@url">GA Login</a>.', array(
'@url' => url('user/' . $account->uid . '/ga_login'),
)),
);
if (ga_login_delete_access($account)) {
$form['ga_login']['ga_login_delete_code'] = array(
'#type' => 'submit',
'#value' => t('Delete GA Login Code'),
'#submit' => array(
'ga_login_delete_code_confirm_redirect',
),
);
}
}
/**
* A simple redirect to a confirmation page before deleting a code.
*/
function ga_login_delete_code_confirm_redirect($form, &$form_state) {
drupal_goto('user/' . $form['#user']->uid . '/ga_login/delete');
}
/**
* Implements hook_user_presave().
*
* If a user enables 'Protect my account with two-factor-authentication'
* make sure he has setup a code, if not redirect to the creation page.
*/
function ga_login_user_presave(&$edit, $account, $category) {
if (isset($edit['ga_login_force_tfa'])) {
$edit['data']['ga_login_force_tfa'] = $edit['ga_login_force_tfa'];
if ($edit['ga_login_force_tfa'] && empty($account->data['ga_login_force_tfa']) && !_ga_login_account_has_code($account)) {
// If force tfa got switched on and the user has no code yet,
// redirect to the code creation page after saving.
$edit['data']['ga_login_force_tfa'] = FALSE;
$_GET['destination'] = 'user/' . $account->uid . '/ga_login';
}
}
}
/**
* Validate callback for login form.
*
* Checks if the ga_login code is needed and valid.
*
* @see ga_login_form_alter()
*/
function ga_login_user_login_validate($form, &$form_state) {
$code = $form_state['values']['gacode'];
if (!empty($form_state['uid']) && !form_get_errors()) {
// Authentication was successful, check the GA code.
$name = $form_state['values']['name'];
$account = user_load_by_name($name);
if (_ga_login_force_tfa($account) || !empty($code) || $account->uid == 1 && variable_get('ga_login_always_for_uid1', 0)) {
$ga = _ga_login_get_class();
$username = _ga_login_username($account);
if ($ga
->hasToken($username)) {
$keyok = $ga
->authenticateUser($username, $code);
if (!$keyok) {
$form_state['ga_code'] = GA_LOGIN_TOKEN_INVALID;
// Clear uid so that the login fails and a flood event is registered.
$form_state['uid'] = FALSE;
}
else {
$form_state['ga_code'] = GA_LOGIN_TOKEN_VALID;
}
}
else {
$form_state['ga_code'] = GA_LOGIN_TOKEN_MISSING;
if ($account->uid != 1 && user_access('require code', $account)) {
// A code is required but not provided.
form_set_error('gacode', t("Your code is required to log in."));
}
}
}
elseif ($account->uid != 1 && user_access('require code', $account)) {
// A code is required but not provided.
form_set_error('gacode', t("Your code is required to log in."));
}
}
if (!empty($code) && (!isset($form_state['ga_code']) || $form_state['ga_code'] == GA_LOGIN_TOKEN_INVALID)) {
form_set_error('gacode', t("Your code isn't valid or has already been used."));
}
}
/**
* Submit callback for login form.
*
* Checks if the user has to use ga_login, but doesn't yet have a code.
*
* @see ga_login_form_alter()
*/
function ga_login_user_login_submit_code_needed($form, &$form_state) {
$name = $form_state['values']['name'];
$code = $form_state['values']['gacode'];
$account = user_load_by_name($name);
// Check if user needs to generate a token.
if (isset($form_state['ga_code']) && $form_state['ga_code'] == GA_LOGIN_TOKEN_MISSING) {
// Make sure the user can create a code.
if (user_access('create own login code once', $account) || user_access('create own login code', $account) || user_access('create others login codes', $account)) {
unset($_GET['destination']);
drupal_set_message(t("You don't have a login code yet. Please add one to your account below."), 'warning');
$form_state['redirect'] = "user/{$account->uid}/ga_login";
}
}
}
/**
* Implements hook_mobile_codes_default_mobile_codes_preset_alter().
*/
function ga_login_mobile_codes_default_mobile_codes_preset_alter(&$export) {
$preset = new stdClass();
$preset->disabled = FALSE;
/* Edit this to true to make a default preset disabled initially */
$preset->api_version = 2;
$preset->name = 'ga_login';
$preset->provider = 'google';
$preset->defaults = array(
'width' => '200',
'height' => '200',
'output_encoding' => 'UTF-8',
);
$export['ga_login'] = $preset;
}
/**
* Create a site specific username.
*/
function _ga_login_username($account, $encode = TRUE) {
$realm = variable_get('ga_login_textname', variable_get('site_name', 'Drupal'));
$suffix = variable_get('ga_login_textid', '');
$username = format_string('!account@!realm!suffix', array(
'!account' => $account->name,
'!realm' => $realm,
'!suffix' => $suffix,
));
return $encode ? rawurlencode($username) : $username;
}
/**
* Removes the GA login code associated with an account.
*/
function ga_login_delete_code($account) {
$username = _ga_login_username($account);
$result = db_delete('ga_login')
->condition('name', $username)
->execute();
if ($result) {
drupal_set_message(t("Successfully deleted the GA Login code for @name", array(
'@name' => format_username($account),
)));
// Disable TFA for this account, since they no longer have a code.
user_save($account, array(
'data' => array(
'ga_login_force_tfa' => FALSE,
),
));
}
else {
drupal_set_message(t("There was a problem deleting the GA Login code for @name", array(
'@name' => format_username($account),
)), 'error');
}
}
/**
* Implements hook_element_info().
*/
function ga_login_element_info() {
$types['gacode'] = array(
'#input' => TRUE,
'#uid' => NULL,
'#size' => 6,
'#maxlength' => 6,
'#autocomplete_path' => FALSE,
'#process' => array(
'ajax_process_form',
),
'#element_validate' => array(
'ga_login_validate_gacode',
),
'#theme' => 'gacode',
'#theme_wrappers' => array(
'form_element',
),
'#_new_ga_code' => FALSE,
);
return $types;
}
/**
* Form element validation handler for gacode field.
*
* Note that #required is validated by _form_validate() already.
*/
function ga_login_validate_gacode(&$element, &$form_state) {
$code = $element['#value'];
if ($code === '') {
return;
}
$name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
// Ensure the input exactly 6 digits.
if (strlen($code) != 6 || !ctype_digit($code)) {
form_error($element, t('%name has to be exactly 6 digits.', array(
'%name' => $name,
)));
return;
}
// Load the associated user account.
$account = NULL;
if (is_null($element['#uid'])) {
global $user;
// Make sure we have a user, if not bail out.
if ($user->uid === 0) {
return;
}
$account = user_load($user->uid);
}
else {
$account = user_load($element['#uid']);
}
$ga = _ga_login_get_class();
$username = _ga_login_username($account);
if ($ga
->hasToken($username) && !$element['#_new_ga_code']) {
$keyok = $ga
->authenticateUser($username, $code);
if (!$keyok) {
form_error($element, t('%name has an invalid code.', array(
'%name' => $name,
)));
}
}
}
/**
* Implements hook_theme().
*/
function ga_login_theme() {
return array(
'gacode' => array(
'arguments' => array(
'element' => NULL,
),
'render element' => 'element',
'file' => 'ga_login.theme.inc',
),
);
}
/**
* Implements hook_user_operations().
*/
function ga_login_user_operations() {
if (user_access('delete others login codes')) {
$operations = array(
'reset_ga_login' => array(
'label' => t('Reset GA login code'),
'callback' => 'ga_login_user_operations_reset_ga_login',
),
);
}
return $operations;
}
/**
* Callback function for admin bulk GA code reset.
*/
function ga_login_user_operations_reset_ga_login($accounts) {
$accounts = user_load_multiple($accounts);
foreach ($accounts as $account) {
ga_login_delete_code($account);
}
}
/**
* Implements hook_user_delete().
*/
function ga_login_user_delete($account) {
ga_login_delete_code($account);
}
Functions
Constants
Name | Description |
---|---|
GA_LOGIN_TOKEN_INVALID | Token is invalid. |
GA_LOGIN_TOKEN_MISSING | Token is missing. Needs to be generated. |
GA_LOGIN_TOKEN_VALID | Token is valid. |