". t(
'Allows users to authenticate using FOST/HTTP.'
) ."
\n";
}
}
function FOSTauth_perm() {
// Implementation of hook_perm().
return array('administer http authentication');
}
function FOSTauth_menu() {
// Implementation of hook_menu().
$items = array();
$items['FOSTauth'] = array(
'page callback' => 'FOSTauth_callback',
'access callback' => 'variable_get',
'access arguments' => array('FOSTauth_status', FALSE),
'type' => MENU_CALLBACK,
);
$items['admin/settings/FOSTauth'] = array(
'title' => 'FOST HTTP authentication',
'page callback' => 'drupal_get_form',
'page arguments' => array('FOSTauth_settings'),
'access arguments' => array('administer http authentication'),
);
return $items;
}
function FOSTauth_callback() {
// Callback for FOSTauth. Usually called by drupal_access_denied()
// through the site_403 variable.
global $user;
// Is this an anonymous user?
if (!$user->uid) {
// Get the current path from $_REQUEST['destination'],
// drupal_access_denied() put it there.
$path = drupal_get_path_alias($_REQUEST['destination']);
// This pattern was taken from block_list().
$pattern = '/^('. preg_replace(
array(
'/(\r\n?|\n)/',
'/\\\\\*/',
'/(^|\|)\\\\($|\|)/'
),
array(
'|',
'.*',
'\1'. preg_quote(
variable_get('site_frontpage', 'node'),
'/'
) .'\2'
),
preg_quote(
variable_get('FOSTauth_pages', ''),
'/'
)
) .')$/';
// Do we need to promote HTTP authentication on this path?
if (preg_match($pattern, $path)) {
FOSTauth_unauthorized();
}
}
// Do what drupal_access_denied() would've done if we wouldn't've been called.
menu_set_active_item('');
}
function FOSTauth_settings() {
// Callback for the settings page.
$form['FOSTauth_status'] = array(
'#type' => 'checkbox',
'#title' => t('Enable FOST HTTP authentication.'),
'#default_value' => variable_get('FOSTauth_status', FALSE),
'#description' => t(
'Note: when you enable FOST HTTP authentication, !the-setting will be modified. When you disable it again, the setting will be restored.',
array(
'!the-setting' => l(
t('the 403 (access denied) page setting'),
'admin/settings/error-reporting'
)
)
)
);
$form['FOSTauth_pages'] = array(
'#type' => 'textarea',
'#title' => t('Promote FOST HTTP authentication on pages'),
'#default_value' => variable_get('FOSTauth_pages', ''),
'#description' => t(
'On which pages to promote FOST HTTP authentication, if an anonymous user stumbles upon an access denied page. Enter one page per line as a Drupal path. The * character is a wildcard.'
)
);
$form = system_settings_form($form);
$form['#submit'][] = 'FOSTauth_settings_form_submit';
return $form;
}
function FOSTauth_settings_form_submit($form, &$form_state) {
// Invoked when this modules's settings are changed.
FOSTauth_set_site_403();
}
function FOSTauth_set_site_403($disable = FALSE) {
/**
* Sets the site_403 variable to the appropriate path.
*
* @param $disable
* If TRUE, the site_403 variable will be restored regardless of the FOST
* HTTP authentication status.
*/
$site_403 = variable_get('site_403', '');
if (!$disable && variable_get('FOSTauth_status', FALSE)) {
if ($site_403 != 'FOSTauth') {
// Save original path.
variable_set('FOSTauth_site_403', $site_403);
// Set the path to 'FOSTauth' to intercept access denied pages.
variable_set('site_403', 'FOSTauth');
}
}
else if ($site_403 == 'FOSTauth') {
// Restore the original path.
$original = variable_get('FOSTauth_site_403', '');
variable_set('site_403', $original);
}
}
class Authentication {
const doc = "Authenticates the request an authorisation key";
private $username;
private $password;
private $message;
private $authorization;
private $key; # associated with username,
private $secret;
function __construct() {
$all_headers = getallheaders();
$normalised_headers = array();
function normalise($string) {
return strtoupper(str_replace('_','-',$string));
}
foreach ($all_headers as $key => $value) {
#header("X-INCOMING-$key: $value");
$normalised_headers[
normalise($key)
] = $value;
}
$this->authorization = $normalised_headers['AUTHORIZATION'];
if (isset($this->authorization)) {
list($mechanism, $authz) = explode(' ', $this->authorization);
if ($mechanism == 'FOST') {
$FOST_Timestamp = $normalised_headers['X-FOST-TIMESTAMP'];
$time_array = strptime(
substr($FOST_Timestamp, 0, 19),
'%Y-%m-%d %H:%M:%S'
);
$sent_time = mktime(
$time_array['tm_hour'],
$time_array['tm_min'],
$time_array['tm_sec'],
$time_array['tm_mon'] + 1, # 0-11
$time_array['tm_mday'], # 1-31
$time_array['tm_year'] + 1900 # from 1900
) + date('Z'); # UTC offset
$now = time();
if (abs($sent_time - $now) < 300) {
list($key,$digest) = explode(':', $authz);
$this->key = $key;
$this->digest = $digest;
$this->username = $normalised_headers['HTTP-X-FOST-USER'];
$this->password = 'replace-with-password'; # must be the password of all users
if ($this->key) {
$header_names = explode(
' ',
$normalised_headers['X-FOST-HEADERS']
);
$header_values = array();
foreach ($header_names as $header_name) {
$header_value = $normalised_headers[
normalise($header_name)
];
$header_values[] = $header_value;
};
$document = sprintf(
"%s %s\n%s\n%s\n%s",
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI'],
$FOST_Timestamp,
implode("\n", $header_values),
$_SERVER['QUERY_STRING'] || $HTTP_RAW_POST_DATA # or php://stdin | php://input
);
$digest = base64_encode(
hash_hmac(
'sha1',
$document,
'this needs to be the secret associated with the key',
TRUE
)
);
if ($digest == $this->digest) {
# $logging->info($this->authorization);
}
else {
$this->error(403, "403 Forbidden"); # not 401 Unauthorized
}
}
}
else {
$this->error(403, "Clock skew too high");
}
}
}
}
function error($status, $message) {
$this->status = $status;
$this->message = $message;
}
function log_user_in() {
$username = $this->username;
$password = $this->password;
if (isset($username) && isset($password)) {
global $user;
// Abort if the user with the provided credentials is already logged in.
if ($user->uid && strcasecmp($user->name, $username) == 0) {
return;
}
require_once('includes/form.inc');
drupal_load('module', 'user');
if (user_is_blocked($username) || drupal_is_denied('user', $username)) {
FOSTauth_unauthorized();
exit;
}
user_authenticate(array('name' => $username, 'pass' => $password));
// Was authentication successful?
if ($user->uid) {
// If caching is enabled, 'fork' the current request.
if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
// Continue invocation of hook_boot() for modules coming
// after FOSTauth.
$invoke_hook = FALSE;
foreach (module_list(TRUE, TRUE) as $module) {
if ($module == 'FOSTauth') {
$invoke_hook = TRUE;
}
else if ($invoke_hook) {
drupal_load('module', $module);
module_invoke($module, $hook);
}
}
// Call drupal_page_header(), just like _drupal_bootstrap() would.
drupal_page_header();
// Fork the request, bootstrapping will continue in next phase.
include('./index.php');
// Don't continue the original request.
exit();
}
}
else {
// We need common.inc for t(), and theme.inc for theme()
// (called indirectly by t()).
require_once('includes/common.inc');
require_once('includes/theme.inc');
watchdog(
'user',
'Login attempt using FOST HTTP authentication failed for %username.',
array('%username' => $username)
);
FOSTauth_unauthorized();
exit();
}
}
else if (isset($_GET['authenticate'])) {
// Force authentication when requested to do so.
FOSTauth_unauthorized();
exit();
}
}
}
function FOSTauth_boot() {
// Implementation of hook_boot().
// Return if FOST HTTP authentication is disabled.
if (!variable_get('FOSTauth_status', FALSE)) {
return;
}
// Load credentials.
$authentication = new Authentication();
$authentication->log_user_in();
}
function FOSTauth_unauthorized() {
// Set 401 Unauthorized status and WWW-Authenticate header.
require_once('includes/common.inc');
require_once('includes/unicode.inc');
$site_name = trim(variable_get('site_name', 'drupal'));
$realm = mime_header_encode($site_name);
drupal_set_header("HTTP/1.0 403 Forbidden");
}
function FOSTauth_parse($header) {
// Parse an HTTP authorization header.
list($type, $credentials) = split(' ', $header);
if ($type == 'Basic') {
return explode(':', base64_decode($credentials));
}
}