'admin/settings/securesite',
'title' => t('Secure Site'),
'callback' => 'drupal_get_form',
'callback arguments' => 'securesite_admin_settings',
'description' => t('Enables HTTP Auth security or an HTML form to restrict site access.'),
'access' => user_access('administer site configuration'),
);
}
return $items;
}
/**
* FAPI definition for Secure Site admin settings form
*
* @see system_settings_form()
*/
function securesite_admin_settings() {
global $base_url;
// Authentication settings
$form['authentication'] = array(
'#type' => 'fieldset',
'#title' => t('Authentication'),
'#description' => t('Enable Secure Site below. Users must have the "access secured pages" permission in order to access the site once the following setting is enabled.', array('!access' => url('admin/user/access', NULL, 'module-securesite')))
);
$form['authentication']['securesite_enabled'] = array(
'#type' => 'radios',
'#title' => t('Forced authentication'),
'#default_value' => variable_get('securesite_enabled', SECURESITE_DISABLED),
'#options' => array(
SECURESITE_DISABLED => t('Disabled'),
SECURESITE_AUTH => t('Use HTTP Auth'),
SECURESITE_FORM => t('Use HTML login form'),
),
'#description' => t('HTTP Auth requires extra configuration if PHP is not installed as an Apache module. See the Known Issues section of the README.txt included with Secure Site for details.'),
);
$form['authentication']['securesite_realm'] = array(
'#type' => 'textfield',
'#title' => t('Authentication realm'),
'#default_value' => variable_get('securesite_realm', variable_get('site_name', 'Drupal')),
'#length' => 30,
'#maxlength' => 40,
'#description' => t('Name to identify login area in HTTP Auth dialog'),
);
// Guest access
$form['guest'] = array(
'#type' => 'fieldset',
'#title' => t('Guest access'),
'#description' => t('Guest access allows unregistered (anonymous) users to view secure pages, though a username and password are still required. You can set the username and password for all anonymous users below. If left blank, guest user access will be disabled.'),
);
$form['guest']['securesite_guest_name'] = array(
'#type' => 'textfield',
'#title' => t('Guest user'),
'#default_value' => variable_get('securesite_guest_name', ''),
'#length' => 30,
'#maxlength' => 40,
'#description' => t('Leave empty to disable guest access'),
);
$form['guest']['securesite_guest_pass'] = array(
'#type' => 'textfield',
'#title' => t('Guest password'),
'#default_value' => variable_get('securesite_guest_pass', ''),
'#length' => 30,
'#maxlength' => 40,
'#description' => t('Leave empty to disable guest access'),
);
// HTML login form settings
$form['login_form'] = array(
'#type' => 'fieldset',
'#title' => t('Customize HTML forms'),
'#description' => t('Configure the message displayed on the HTML login form (if enabled) and password reset form below.')
);
$form['login_form']['securesite_login_form'] = array(
'#type' => 'textarea',
'#title' => t('Custom message for HTML login form'),
'#default_value' => variable_get('securesite_login_form', t('
Enter your username and password:
')),
'#length' => 60,
'#height' => 3,
);
$form['login_form']['securesite_request_form'] = array(
'#type' => 'textarea',
'#title' => t('Custom message for password reset form'),
'#default_value' => variable_get('securesite_request_form', t('Enter your username or e-mail address:
')),
'#length' => 60,
'#height' => 3,
'#description' => t('Leave empty to disable Secure Site\'s password reset form.'),
);
// Bypass login settings
$form['filter_pages'] = array(
'#type' => 'fieldset',
'#title' => t('Bypass login'),
'#description' => t('Some sites may wish to only use Secure Site for certain pages. Use the settings below to configure which pages force authentication via Secure Site. Select "On every page except the listed pages" and leave the "Pages" list empty to force authentication for all pages (this is the default configuration). The cron page is always accessible.'),
);
$form['filter_pages']['securesite_filter_pages_type'] = array(
'#type' => 'radios',
'#title' => t('Force authentication'),
'#default_value' => variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST),
'#options' => array(
SECURESITE_WHITELIST => t('On every page except the listed pages. Use this setting to restrict access of your entire site.'),
SECURESITE_BLACKLIST => t('Only on the listed pages. Use this setting only if you want to restrict access to certain pages, not your entire site.'),
),
);
$form['filter_pages']['securesite_filter_pages'] = array(
'#type' => 'textarea',
'#title' => t('Pages'),
'#default_value' => variable_get('securesite_filter_pages', ''),
'#length' => 60,
'#height' => 3,
'#description' => t("Enter one path per line. Enter <front> for your site's front page. \"*\" acts as a wildcard. For example, enter blog for the blog page and blog/* for every personal blog."),
);
return system_settings_form($form);
}
/**
* Implementation of hook_init()
*
* This is where Secure Site does most of its processing
*
* Note: When a user is logged in, but doesn't have the 'access secured pages'
* permission, they get a normal Access Denied message
*/
function securesite_init() {
global $user, $base_path;
$securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED);
$guest_name = variable_get('securesite_guest_name', '');
$guest_pass = variable_get('securesite_guest_pass', '');
// Step #1: Process conditions that bypass Secure Site authentication
if (!$securesite_enabled // Secure Site isn't enabled
|| ($securesite_enabled == SECURESITE_AUTH_ALT) // Old auth method, deprecated in 5.x-1.3.1
|| (php_sapi_name() == 'cli') // Running PHP from the command line, such as when using Drush
|| (request_uri() == $base_path .'cron.php') // Accessing cron page
|| ($user->uid == 1) // Site administrator
|| ($user->uid && user_access('access secured pages')) // User is logged in and has privileges to access the site
|| ((!empty($guest_name)) // Guest access is allowed and guest has previously logged in
&& (isset($_SESSION['securesite_guest']) ? $_SESSION['securesite_guest'] : ''))) {
return;
}
// Do a partial bootstrap when caching is enabled since Secure Site uses
// stuff in path.inc for the following bypass check
if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
}
// User not logged in, but accessing a page in the bypass list. This has to
// be after a partial bootstrap so path.inc functions are available and
// $_GET['q'] is set
if (!$user->uid && _securesite_filter_check((isset($_GET['q']) ? $_GET['q'] : ''))) {
return;
}
// Prevent a login/logout loop by redirecting off the logout page
if (strpos(request_uri(), $base_path .'logout') === 0) {
drupal_goto('');
}
// Finish bootstrap since Secure Site uses many functions throughout Core,
// such as user.module functions, t(), and theme()
if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
// Step #2: Process password resets
if (strpos(request_uri(), $base_path .'user/reset/') === 0) {
$args = explode('/', $_GET['q']);
// The password reset function doesn't work well if it doesn't have all the
// required parameters or if the UID parameter isn't valid
if (count($args) >= 5 && user_load(array('uid' => $args[2], 'status' => 1))) {
user_pass_reset($args[2], $args[3], $args[4], 'login');
}
else {
drupal_set_message(t('You have tried to use an invalid one-time login link. Please request a new one using the form below.'), 'error');
}
}
// Step #3: Set up variables
if ($securesite_enabled == SECURESITE_FORM && !empty($_POST)) {
$edit = $_POST['edit'];
}
elseif ($securesite_enabled == SECURESITE_AUTH) {
// PHP in CGI mode work-arounds. Sometimes, "REDIRECT_" prefixes $_SERVER
// variables. See http://www.php.net/reserved.variables
if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && empty($_SERVER['HTTP_AUTHORIZATION'])) {
$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
}
// Auth variables set via Rewrite rules need to be decoded
// See http://www.php.net/manual/en/features.http-auth.php#76708
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
}
// Process username and password normally. The correct $_SERVER variables
// are now set if PHP is run in CGI mode
if (isset($_SERVER['PHP_AUTH_USER'])) {
$edit['name'] = $_SERVER['PHP_AUTH_USER'];
}
if (isset($_SERVER['PHP_AUTH_PW'])) {
$edit['pass'] = $_SERVER['PHP_AUTH_PW'];
}
}
// Step #4: If needed, ask user for credentials
if (empty($edit) && ($user->uid == 0)) {
_securesite_user_auth();
}
// Step #5: Check if user is a guest and log them in if they are
if (!empty($guest_name) && !empty($guest_pass) && $guest_name == $edit['name'] && $guest_pass == $edit['pass']) {
// Mark this session to prevent re-login (note: guests can't logout)
$_SESSION['securesite_guest'] = TRUE;
return;
}
unset($_SESSION['securesite_guest']); // If not a guest, make sure to clear the guest session
// Step #6: Check user's credentials
// The LDAP auth module can't use the regular external user login system, so
// we have to call its login function directly
if (function_exists('_ldapauth_user_authenticate')) {
$account = _ldapauth_user_authenticate($edit['name'], $edit['pass']);
}
else {
$account = user_authenticate($edit['name'], $edit['pass']);
}
// Step #7: Process login attempt
if ((isset($account->uid) ? $account->uid : FALSE) && user_access('access secured pages', $account)) {
// Login successful
$user = $account;
// Mark the session so Secure Site will be triggered on logout
$_SESSION['securesite_login'] = TRUE;
watchdog('user', t('Session opened for %name.', array('%name' => $user->name)));
db_query("UPDATE {users} SET login = '%d' WHERE uid = '%s'", time(), $user->uid);
user_module_invoke('login', $edit, $user);
}
else {
// Login failed
if (!empty($edit['name'])) {
watchdog('user', t('Login attempt failed for %user.', array('%user' => $edit['name'])));
}
else {
watchdog('user', t('Login attempt failed for anonymous user.', array('%user' => $edit['name'])));
}
_securesite_user_auth();
}
}
/**
* Implementation of hook_user()
*
* When users logout, show the HTTP Auth dialog to make sure the HTTP Auth
* credentials are cleared
*
* @see _securesite_user_auth()
*/
function securesite_user($op, &$edit, &$user) {
if (($op == 'logout')
&& (variable_get('securesite_enabled', SECURESITE_DISABLED) == SECURESITE_AUTH)
&& ((isset($_SESSION['securesite_login']) ? $_SESSION['securesite_login'] : FALSE))) {
// Load the anonymous user
$user = drupal_anonymous_user();
// Clear stored credentials
_securesite_user_auth();
}
}
/**
* Display authentication dialog and send password reset mails
*/
function _securesite_user_auth() {
global $base_url;
include_once('securesite.inc');
$edit = isset($_POST['edit']) ? $_POST['edit'] : '';
$securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED);
$content = '';
// Step #1: Check if the user attempted to submit the login form. If so,
// getting here means they didn't enter their info correctly
if ((isset($_POST['securesite_login_form']) ? $_POST['securesite_login_form'] : '')) {
drupal_set_message(t('Unrecognized username and/or password.'), 'error');
}
// Step #2: Check if the user attempted to submit the password request form.
// If so, check if we have information for the name/mail they entered and
// send it if we do
if ((isset($_POST['securesite_request_form']) ? $_POST['securesite_request_form'] : '')) {
_securesite_password_reset($edit);
}
// Get content for dialog
if ($securesite_enabled == SECURESITE_FORM) {
$content .= _securesite_login_form();
}
$content .= _securesite_request_form();
// Step #3: If using HTTP Auth, send the appropriate headers, but only if the
// user isn't logged in and they haven't just submitted the password reset or
// login forms
if (($securesite_enabled == SECURESITE_AUTH) && empty($_POST['securesite_request_form']) && empty($_POST['securesite_login_form'])) {
$realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));
// If not on the home page of the site, Opera will not show the auth dialog
// the first time after logout. It will show the page displayed before
// logging out. Reloading will cause the dialog to display. Safari
// doesn't seem show the login/password request form when cancelling the
// auth dialog no matter what
$browsers = array('msie', 'opera', 'safari');
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
foreach ($browsers as $browser) {
if (strpos($user_agent, $browser) !== FALSE) {
$realm .= ' - '. mt_rand(10, 999);
break;
}
}
header('WWW-Authenticate: Basic realm="'. $realm .'"');
header('HTTP/1.0 401 Unauthorized');
}
// Step #4: Show the login form and/or password request form if user cancels
// HTTP Auth dialog
_securesite_dialog_page($content);
module_invoke_all('exit', request_uri());
session_write_close();
exit();
}
/**
* Check if pages should bypass Secure Site
*
* @param $path
* String containing the path to be filtered
*
* @return
* TRUE if Secure Site should be bypassed
*/
function _securesite_filter_check($path) {
// Don't allow empty paths
if (empty($path)) {
return FALSE;
}
// Fetch paths to ignore
$pages = variable_get('securesite_filter_pages', '');
// Check if the current path matches the list defined by the admin
$alias = drupal_get_path_alias($path);
$regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($pages, '/')) .')$/';
$page_match = preg_match($regexp, $alias);
// Check normal paths if the alias lookup fails to match
if (!$page_match) {
$alias = drupal_get_normal_path($path);
$page_match = preg_match($regexp, $alias);
}
// Whitelist or blacklist?
if (variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST) == !$page_match) {
return TRUE;
}
return FALSE;
}