User:Otheus/Auto Login via REMOTE USER/code

From Meta, a Wikimedia project coordination wiki

Note: You may want to refer to code compatible with more recent versions of MediaWiki.

<?php
// $Id: Auth_remoteuser.php,v 1.1 2006/01/14 02:01:41 otheus Exp otheus $
// $Log: Auth_remoteuser.php,v $
// Revision 1.1  2006/01/14 02:01:41  otheus
// Initial revision
// See http://meta.wikimedia.org/wiki/User:Otheus/Auto_Login_via_REMOTE_USER/code
// vim:sw=2:softtabstop=2

$wgGroupPermissions['*'    ]['createaccount']   = false;
$wgGroupPermissions['*'    ]['read']            = false;
$wgGroupPermissions['*'    ]['edit']            = false;

require_once('AuthPlugin.php');

//
// This gets called near the end of Setup.php 
//   It gets called after all the language setup
//   but before any user processing (other than loadFromSession)
//

// Here's the idea: 
//   If Apache has validated the user, REMOTE_USER is set. 
//   In most cases, this means just tell MediaWiki who the user is
//   Do this by setting the username and initalizing _REQUEST and $wgUser.
// 
//   However, if this is a new user, we have work to do. 
//   Hook into the create routines to add the user. This is a bit ugly. 
//   The MediaWiki processUser() causes bad things to happen, so...
//   ... one has to call the right routine. This appears to be initUser(). 
//   initUser() calls this module's initUser() which sets the default user
//   parameters. However, these settings are lost unless one calls saveSettings() 
//   
//   At the end, redirect to self ... MediaWiki magically takes care of the rest.
//   Note: if cookies are disabled, an infinite loop occurs. Maybe someone
//   can fix this. 
//
function Auth_remote_user_hook() {
    global $wgUser;
    global $wgRequest;
    global $_REQUEST;

    // For a few special pages, don't do anything.
    $title = $wgRequest->getVal('title') ;
    if ($title == 'Special:Userlogout' || $title == 'Special:Userlogin') {
      return;
    }

    // Do nothing if session is valid
    $wgUser = User::loadFromSession(); 
    if ($wgUser->isLoggedIn()) {
      return;
    }

    // Do little if user already exists
    //  (set the _REQUEST variable so that Login knows we're authenticated)
    $username = $_SERVER['REMOTE_USER' ];  
    $u = User::newFromName( $username );
    if (is_null($u)) {
      # Invalid username or some other error -- force login, just return
      return;
    }

    $wgUser = $u; 
    if ($u->getId() != 0) {
      $_REQUEST['wpName'] = $username;
      # also return, but user is know. set Cookies, et al
      // $wgUser->setCookies();
      // $wgUser->saveSettings();
      
      return;
    }


    // Ok, now we need to create a user.

    include 'includes/SpecialUserlogin.php';
    $form = new LoginForm( $wgRequest );
    $form->initUser( $wgUser );
    $wgUser->saveSettings();

    // if it worked: refer to login page, otherwise, exit
    header( "Location: http" . 
	(isset($_SERVER['HTTPS']) 
	  && $_SERVER['HTTPS'] == "on" ? "s" : "") . 
	"://" .  $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . 
	( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI']
        : "/" .  
	  ( isset($_SERVER['URL']) ? $_SERVER['PATH_INFO']  . 
	    ( $_SERVER['QUERY_STRING'] ? "?" . $_SERVER['QUERY_STRING'] : "" )
	  : "" )
        ) 
    );

    // Now redirect to referred page
    // print("Done");
    return;
}

class Auth_remoteuser extends AuthPlugin {

  function Auth_remoteuser() {

    if ( strlen($_SERVER['REMOTE_USER']) ) {
      global $wgExtensionFunctions;
      if (!isset($wgExtensionFunctions)) {
	$wgExtensionFunctions = array();
      }
      else if (!is_array($wgExtensionFunctions)) {
	$wgExtensionFunctions = array( $wgExtensionFunctions );
      }
      array_push($wgExtensionFunctions, 'Auth_remote_user_hook');
    }
    return;
  }

  // disallow password change
  function allowPasswordChange() {
        return false;
  }

  //return whether $username is a valid username
  function userExists($username) {
    // in media wiki 1.5.5 this should always be true for autocreate() to work right
    return true; 
  }

  //whether the given username and password authenticate
  function authenticate($username, $password) {
    if ($username != $_SERVER['REMOTE_USER']) {
      return false;
    }
    return isset($_SERVER['REMOTE_USER']);
  }

  //The authorization is external, so autocreate accounts as necessary
  function autoCreate() {
    return true;
  }

  //tell MediaWiki to not look in its database for user authentication and 
  // that our authentication method is all that counts
  function strict() {
    return true;
  }

  //this function gets called when the user is created
  //$user is an instance of the User class (see includes/User.php)
  function initUser(&$user) {
    //unless you want the person to be nameless, you should probably populate 
    // info about this user here
    //  $user->setRealName("John Smith");
    global $_SERVER;
    $user->setEmail($_SERVER['REMOTE_USER'] . "@dps.uibk.ac.at");

    $user->mEmailAuthenticated = wfTimestampNow();
    $user->setToken();

    //turn on e-mail notifications by default
    $user->setOption('enotifwatchlistpages', 1);
    $user->setOption('enotifusertalkpages', 1);
    $user->setOption('enotifminoredits', 1);
    $user->setOption('enotifrevealaddr', 1);
  }

  //if using MediaWiki 1.5, we have a new function to modify the UI template!
  function modifyUITemplate(&$template) {
    //disable the mail new password box
    $template->set("useemail", false);
    //disable 'remember me' box
    $template->set("remember", false);
    $template->set("create", false);
    $template->set("domain", false);
  }
}
?>


Discussion[edit]

swarren@wwwdotorg.org says: On my system (Fedora Core 3, Apache 2.0.53, PHP 4.3.1, MediaWiki 1.5.5), I had to substitute the following block of code for the post-user-creation redirect:

    // if it worked: refer to login page, otherwise, exit
    header( "Location: http" .
        (isset($_SERVER['HTTPS'])
          && $_SERVER['HTTPS'] == "on" ? "s" : "") .
        "://" .  $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] .
        ( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI']  .
          ( $_SERVER['QUERY_STRING'] ? "?" . $_SERVER['QUERY_STRING'] : "" )
        : "" )
    );

zGas: On my system (IIS 5, PHP 5.1.2, MediaWiki 1.5.6) i had to change the following block: note: this also worked for me using Apache 2, PHP 4, MediaWiki 1.5.7 with https

This also worked for me using IIS 6.0/Php5.1.2/MediaWiki 1.6.3 Hopespoppa 13:05, 27 April 2006 (UTC)
    // if it worked: refer to login page, otherwise, exit
    header( "Location: http" . 
        (isset($_SERVER['HTTPS_KEYSIZE']) 
          && $_SERVER['HTTPS_KEYSIZE'] > 0 ? "s" : "") . 
        "://" .  $_SERVER['SERVER_NAME'] . 
        "/" .  
        ( isset($_SERVER['ORIG_PATH_INFO']) ? $_SERVER['ORIG_PATH_INFO']  . 
          ( $_SERVER['QUERY_STRING'] ? "?" . $_SERVER['QUERY_STRING'] : "" )
        : "" ) 
    );

    // Now redirect to referred page
    // print("Done");
    return;

mrhenroi: Can't figure out why but my wiki was doing a BEGIN TRANSACTION when new users were getting created. I had to add the following before the header(...) described above to get it to work.

$db = & wfGetDB( DB_MASTER );
$db->commit();

robert.risse: I think the coockies will work if you add the $_SERVER global, something that you have forgotten.

function Auth_remote_user_hook() {
    global $wgUser;
    global $wgRequest;
    global $_REQUEST;
    global $_SERVER;

...

    if ($u->getId() != 0) {
      $_REQUEST['wpName'] = $username;
      # also return, but user is know. set Cookies, et al
      $wgUser->setCookies();
      $wgUser->saveSettings();
      
      return;
    }

swarren@wwwdotorg.org says: I added a few more features. First, I have my web-server set two environment variables AUTHENTICATE_CN and AUTHENTICATE_EMAIL, in addition to REMOTE_USER. Then, when a new user gets created, I populate the users's exact name and email into the user object. Also, whenever a valid user is authenticated, I check if the CN/EMAIL changed, and if so, I update the user object and save it, so that the new information is automatically propagated into MediaWiki.

Here's how I configured the LDAP query in apache 2 to set these variables:

AuthLDAPURL ldap://localhost/dc=wwwdotorg,dc=org?uid,mail,cn?

Any attribute names in the query string are converted to upper case, have "AUTHENTICATE_" prefixed, and their value is set in the environment for MediaWiki to pick up (mod_auth_ldap does all this for me).

Here are the relevant modified sections of code. First, the modifications to Auth_remote_user_hook for the user-already-exists case:

    $wgUser = $u;
    if ($u->getId() != 0) {
      $_REQUEST['wpName'] = $username;

      $need_save = 0;
      $auth_email = $_SERVER['AUTHENTICATE_MAIL'];
      if ($wgUser->getEmail() != $auth_email) {
        $wgUser->setEmail($auth_email);
        $need_save = 1;
      }
      $auth_cn = $_SERVER['AUTHENTICATE_CN'];
      if ($wgUser->getRealName() != $auth_cn) {
        $wgUser->setRealName($auth_cn);
        $need_save = 1;
      }

      # also return, but user is know. set Cookies, et al
      $wgUser->setCookies();

      if ($need_save == 1) {
        $wgUser->saveSettings();
      }

      return;
    }

and now the new initUser:

  //this function gets called when the user is created
  //$user is an instance of the User class (see includes/User.php)
  function initUser(&$user) {
    //unless you want the person to be nameless, you should probably populate
    // info about this user here
    global $_SERVER;
    $user->setEmail($_SERVER['AUTHENTICATE_MAIL']);
    $user->setRealName($_SERVER['AUTHENTICATE_CN']);

    $user->mEmailAuthenticated = wfTimestampNow();

    //turn on e-mail notifications by default
    $user->setOption('enotifwatchlistpages', 1);
    $user->setOption('enotifusertalkpages', 1);
    $user->setOption('enotifminoredits', 1);
    $user->setOption('enotifrevealaddr', 1);
  }

vic@eng.iastate.edu says: I had to change the last exit(); to a return; for the code to work on my Apache 2.0.52 / PHP 4.3.9 machine:

    // Now redirect to referred page
    // print("Done");
    return;
}

Otherwise it kept redirecting infinitely and ticking off Firefox. I use mod_auth_kerb to authenticate my users against our Kerberos server and REMOTE_USER is returned in a format I don't quite like so I did some string processing on that as well. robert.risse, $_SERVER and $_REQUEST are superglobals. They are available in every scope and do not need to be invoked using 'global'. Source: php.net

swarren says: The above code doesn't setup the PHP session (which usually occurs within wfSpecialUserlogin(), which gets run any time the user hits Special:Userlogin). To fix this, it's easiest just to add this code:

    if ( !$wgCommandLineMode && !isset( $_COOKIE[session_name()] ) ) {
      User::SetupSession();
    }

Right before "// Do little if user already exists".

A better solution would probably be to forcably redirect any user without a PHP session key to Special:Userlogin, so as to re-use all the code there rather than the maintenance nightmare that the above cut/paste fix is. Looking at SpecialUserlogin.php, there are things you can pass it to tell it where to redirect back to. If you make this change, also check if the current page is Special:Userlogin & display an error instead of a redirect in this case, to avoid infinite redirects.

2006-04-27, ch@lathspell.de: I can confirm the first two comments from swarren@wwwdotorg.org and zGas regarding the Loop when accessing a https site with an unknown user. Their patches work. My config: Apache 1.3.33, Mediawiki 1.5.6 Please update the source!

Logout to spoof security fix[edit]

The authenticate function should be updated as seen below. This is a security fix. If logout is choosen, the user can put any username in the login box with any password and login as an arbitrary user. Checking to make sure that the usernames match prevents logging in as an arbitrary user.

  function authenticate($username, $password) {
    if ($username != $_SERVER['REMOTE_USER']) {
      return false;
    }
    return isset($_SERVER['REMOTE_USER']);
  }

I have noticed this happening but I can't reliably duplicate this behavior so I can't test to fix. -Hopespoppa 18:54, 5 May 2006 (UTC)



First Save problem[edit]

2006-04-28, rkimsey I noticed the first time you try to save an edit you will not be able to unless you have done a "Show preview" first. Once you have done a "Show preview", then you may save edits. It appears the reason behind this, is you need to add a call to SetupSession(). Once the session is setup, "Save" works as it should. Also, in the code below, I removed the commented lines to setCookie() and saveSettings().

This also affects saving preferences (and not just first-time things -- for me it *never* setup a session or gave me cookies...at all...ever.) --65.208.210.97 17:29, 19 June 2006 (UTC) w:User:Stradenko



    $wgUser = $u;
    if ($u->getId() != 0) {
      $_REQUEST['wpName'] = $username;

      // Make call to load session name, otherwise can't save
      if( !isset($wgCommandLineMode) && !isset( $_COOKIE[session_name()] )  ) {
            User::SetupSession();
      }
      return;

This fixed my mediawiki website (running on Fedora Core 4, MySQL5, PHP5, AD). This could be integrated into the Auth_remoteuser.php code ?

I am using the code from source and I am having the following issue:

'session_fail_preview' => 'Sorry! We could not process your edit due to a loss of session data. Please try again. If it still doesn\'t work, try logging out and logging back in.'

The error is presented whenever an edit is made. I am Running MediaWiki 1.9.3 on Apache, slackware over SSL.

The source *apparently* has the fix applied:

 if(!isset($wgCommandLineMode) && !isset($_COOKIE[session_name()])) {
   wfSetupSession();

Problem is it hasn't fixed the issue. Any pointers appreciated --82.71.124.182 11:28, 5 September 2007 (UTC)

Language independency[edit]

Xypron 05:34, 4 August 2006 (UTC)

The following line depends on the wiki default language being set to english

if ($title == 'Special:Userlogout' || $title == 'Special:Userlogin') {

because the use of Special as namespace is language dependent.

Please replace with function calls like

Title::makeName(NS_SPECIAL, 'Userlogin')


Other Authentication than server[edit]

I used a php page which was part of a different script to set the $_SERVER['REMOTE_USER'], with some of the modifications from above I got it to work but I needed to change:

 $wgUser = $u; 
   if ($u->getId() != 0) {
     $_REQUEST['wpName'] = $username;
     # also return, but user is know. set Cookies, et al
     // $wgUser->setCookies();
     // $wgUser->saveSettings();
     
     return;
   }

to:

 $wgUser = $u; 
   if ($u->getId() != 0) {
     $_REQUEST['wpName'] = $username;
     # also return, but user is know. set Cookies, et al
     $wgUser->setCookies();
     $wgUser->saveSettings();
     
     return;
   }

otherwise I got logged out after the first page. I'm not too sure why that was commented out to begin with?