setting up mediawiki with active directory

The problem is this: How do you get MediaWiki which runs on top of XAMPP to be secure using Active Directory?

We wanted to secure our MediaWiki site so that users can log in using their Active Directory account. In addition we wanted only some users to be able to read the Wiki pages and a smaller subset of those users to be able to edit pages.

MediaWiki does have an LDAP Authentication extension but it is not obvious from the extension web page how to set it up. I came across a lot of web pages to get this final solution, but this this one was especially helpful. So without further adieu..

how to set up the extension

1. First, you need to enable the LDAP module in PHP (recent versions of PHP have this built in). To do this, you need to find the php.ini file that your installation is using. I found mine in xampp\apache\bin\php.ini. If you’re not sure which one to change you can do the following:

    Create a file called phpinfo.php and put this code in it:

    <?php  phpinfo();  ?>

    Run it from your browser (http://server/directory/phpinfo.php) and the page that comes up will show you the php.ini path.

2. Edit the php.ini file and remove the semicolon at the head of this line:

;extension=php_ldap.dll

3. Next, copy the LdapAuthentication.php file to your wiki includes directory. In my case this directory is xampp\htdocs\wiki\includes.

4. Add the following lines to your LocalSettings.php, replacing the bold items with actual values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
   #### LDAP settings 
   require_once( '$IP/includesLdapAuthentication.php' );
   $wgAuth = new LdapAuthenticationPlugin();
   #### Uncomment this line to see debug messages:
   #$wgLDAPDebug = 10;
   $wgLDAPDomainNames = array('<strong>DOMAINNAME</strong>',);
   $wgLDAPServerNames = array('<strong>DOMAINNAME</strong>' => '<strong>DOMAINCONTROLLERNAME</strong>',);
   $wgLDAPEncryptionType = array('<strong>DOMAINNAME</strong>' => 'clear',);
   $wgLDAPBaseDNs = array('<strong>DOMAINNAME</strong>' => '<strong>BASEDN</strong>');
   $wgLDAPSearchAttributes = array('<strong>DOMAINNAME</strong>' => 'sAMAccountName');
   $wgLDAPProxyAgent = array("<strong>DOMAINNAME</strong>"=>"<strong>PROXYUSERDN</strong>");
   $wgLDAPProxyAgentPassword = array("<strong>DOMAINNAME</strong>"=>"<strong>PROXYUSERPASS</strong>");
   $wgLDAPUpdateLDAP = array("<strong>DOMAINNAME</strong>"=>false);
   $wgLDAPAddLDAPUsers = array("<strong>DOMAINNAME</strong>"=>false);
 
   $wgLDAPGroupUseFullDN = array("<strong>DOMAINNAME</strong>"=>true);
   $wgLDAPGroupObjectclass = array( "<strong>DOMAINNAME</strong>"=&gt;"group" );
   $wgLDAPGroupAttribute = array( "<strong>DOMAINNAME</strong>"=&gt;"member" );
   $wgLDAPGroupMemberOfAttribute = array( "<strong>DOMAINNAME</strong>"=>"memberof" );
   $wgLDAPGroupSearchNestedGroups = array( "<strong>DOMAINNAME</strong>"=>true );
   $wgLDAPGroupNameAttribute = array( "<strong>DOMAINNAME</strong>"=>"cn" );
   $wgLDAPUseLDAPGroups = array( "<strong>DOMAINNAME</strong>"=>true);
 
   #### restrict to logged in users only$wgGroupPermissions['*']['edit'] = false;
   $wgGroupPermissions['user']['edit'] = false;
   $wgGroupPermissions['<strong>EDITGROUPNAME</strong>']['edit'] = true;
   $wgGroupPermissions['*']['read'] = false;
   $wgGroupPermissions['user']['read'] = false;
   $wgGroupPermissions['<strong>READONLYGROUPNAME</strong>']['read'] = true;
   $wgGroupPermissions['<strong>EDITGROUPNAME</strong>']['read'] = true;
   $wgGroupPermissions['*']['createaccount'] = false;
 
   #### make sure login and logout pages are visible
   $wgWhitelistRead = array( "Special:Userlogout", "Special:Userlogin" );

nested groups

The above will set up MediaWiki to connect to your Active Directory. However, the default implementation of the extension does not support nested groups when it comes to determining the groups that a user is in. If you want your user to be a member of a group which is a member of another group, and use the parent group in your wiki settings, then you need to make this modification. In order to do this I had to modify the LDAP Authentication extension. This requires changing the LdapAuthentication.php file:

1. Add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function mygetGroups( $ldapconn, $dn ) {
        global $wgLDAPGroupObjectclass, $wgLDAPGroupAttribute, $wgLDAPGroupNameAttribute, $wgLDAPGroupMemberOfAttribute;
        global $wgLDAPProxyAgent, $wgLDAPProxyAgentPassword;
        global $wgUser;
 
        //$this-&gt;printDebug( "Entering getGroups", NONSENSITIVE );
 
        $nameattribute = $wgLDAPGroupNameAttribute[$_SESSION['wsDomain']];
        $memberofattr = $wgLDAPGroupMemberOfAttribute[$_SESSION['wsDomain']];
 
        $groups = array();
        $shortnamegroups = array();
        $entry = @ldap_read( $ldapconn, $dn, "objectclass=*" );
        $infos = @ldap_get_entries( $ldapconn, $entry );
        if (isset($infos[0][$memberofattr])) {
            //We need to shift because the first entry will be a count
            array_shift( $infos[0][$memberofattr] );
            foreach ($infos[0][$memberofattr] as $memberof)
            {
                $entryName = @ldap_read( $ldapconn, $memberof, "objectclass=*" );
                $infosName = @ldap_get_entries( $ldapconn, $entryName );
                if (isset($infosName[0][$nameattribute])) {
                            $this->printDebug( "memberof=" .  strtolower($infosName[0][$nameattribute][0]), SENSITIVE);
                    array_push( $groups, $memberof );
                    array_push( $shortnamegroups, strtolower($infosName[0][$nameattribute][0]) );
                }
            }
        }
        $both_groups = array();
        array_push( $both_groups, $groups );
        array_push( $both_groups, $shortnamegroups );
 
        //$this->printDebug( "Returned groups:" . implode( ",", $groups ) . "", SENSITIVE );
        $this->printDebug( "Returned groups:" . implode( ", ", $shortnamegroups ) . "", SENSITIVE );
 
        return $both_groups;
    }
    function mysearchNestedGroups( $ldapconn, $groups, $groupsshort, &amp;$checkedgroups, &amp;$checkedgroupsshort ) {
        global $wgLDAPRequiredGroups;
 
        $this->printDebug( "Entering searchNestedGroups for : " . implode( " // ", $groups ), NONSENSITIVE );
 
        //base case, no more groups left to check
        if ( !$groups ) {
            $this->printDebug( "Couldn't find user in any nested groups.", NONSENSITIVE );
            return false;
        }
 
        $this->printDebug( "Checking groups:" . implode( ",", $groups ) . "", SENSITIVE );
 
        $reqgroups = $wgLDAPRequiredGroups[$_SESSION['wsDomain']];
        for ( $i = 0; $i &lt; count( $reqgroups ); $i++ ) {
            $reqgroups[$i] = strtolower( $reqgroups[$i] );
        }
 
        $checkedgroups = array_unique( array_merge( $groups, $checkedgroups ) );
        $checkedgroupsshort = array_unique( array_merge( $groupsshort, $checkedgroupsshort ) );
        $groupstocheck = array();
        $groupstocheckshort = array();
        foreach ( $groups as $group ) {
            list( $returnedgroups, $returnedgroupsshort ) = $this->mygetGroups( $ldapconn, $group );
            $this->printDebug( "*************** Group $group is in the following groups:" . implode( " // ", $returnedgroups ) . "", SENSITIVE );
            $i = 0;
            foreach ( $returnedgroups as $checkme ) {
                if ( in_array( $checkme, $checkedgroups ) ) {
                    //We already checked this, move on
                    continue;
                }
                $this->printDebug( "Checking membership for: $checkme", SENSITIVE );
                array_push( $groupstocheck, $checkme );
                                array_push( $groupstocheckshort, $returnedgroupsshort[i] );
                $i++;
            }
            $checkedgroups = array_unique( array_merge( $returnedgroups, $checkedgroups ) );
            $checkedgroupsshort = array_unique( array_merge( $returnedgroupsshort, $checkedgroupsshort ) );
        }        
 
        //Mmmmmm. Tail recursion. Tasty.
        return $this->mysearchNestedGroups( $ldapconn, $groupstocheck, $groupstocheckshort, $checkedgroups, $checkedgroupsshort );
     }

2. Add code to the authenticate function (new code is in bold):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Synch LDAP groups with MediaWiki groups 
if ( isset( $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) {
     $this->printDebug( "Retrieving LDAP group membership", NONSENSITIVE );
 
     //Let's get the user's LDAP groups
     if ( isset( $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) && $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) {
         $this-&gt;userLDAPGroups = $this->getUserGroups( $ldapconn, $userdn, true );
         <strong>$userLDAPGroupsFull = $this-&gt;getUserGroups( $ldapconn, $userdn, false );</strong>
       } else {
         if ( ( isset( $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']] ) && $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']] )
             && $this->LDAPUsername != '' ) {
 
             $this->userLDAPGroups = $this->getUserGroups( $ldapconn, $this->LDAPUsername, true );
             <strong>$userLDAPGroupsFull = $this->getUserGroups( $ldapconn, $this-&gt;LDAPUsername, false );</strong>
           } else {
             $this-&gt;userLDAPGroups = $this->getUserGroups( $ldapconn, $username, true );
             <strong>$userLDAPGroupsFull = $this-&gt;getUserGroups( $ldapconn, $username, false );  </strong>
         }
     }
 
     <strong>global $wgLDAPGroupSearchNestedGroups;
     $searchnested = $wgLDAPGroupSearchNestedGroups[$_SESSION['wsDomain']];
     if ( $searchnested ) {
         $this-&gt;printDebug("Searching nested..", SENSITIVE);
         $foundgroups = array();
         $foundgroupsshort = array();
         $this->mysearchNestedGroups( $ldapconn, $userLDAPGroupsFull, $this-&gt;userLDAPGroups, $foundgroups, $foundgroupsshort );
         $this->printDebug("Found groups: " . implode( ",", $foundgroupsshort ) . "", SENSITIVE );
         $this-&gt;userLDAPGroups = $foundgroupsshort;
     }</strong>