Extension talk:LDAP Authentication/LQT Archive 1

= Archive =

Due to limitations of MediaWiki structures and unidentified editor's limited patience, someone archived an earlier version of this discussion/talk page, as "it was getting way too long" - accuracy, continuity, and helpfulness be damned! Some archive contents of this discussion/talk page from ~2007-05-12 and earlier can be found (and contributed to!) here:

Extension_talk:LDAP_Authentication/archive1 -- Peter Blaise peterblaise 10:02, 23 May 2007 (UTC)

= Requirements =

Php
Could the statement "PHP must be compiled with LDAP support for any functionality at all" be explained further? I'm not a developer and simply downloaded php5 from php.net and followed config instructions to get mediawiki running. I never compiled php. According to php.net I would need some development tools to compile php? What is needed to change the default version of php5.1.2 windows package to be 'compiled' for LDAP? Can I just configure some extension from php.ini? my specific situation is Windows2003/IIS/php/mysql.


 * There is quite a bit of documentation on how to get LDAP working with PHP, and specifically with windows. I believe someone even posted some info on the content page of this article. I believe this is probably beyond the scope of this documentation. --Ryan Lane

= I can't find the code! =

Quick Answer!
Answer: See Extension:LDAP_Authentication

= Compatibility =

Problem with autocreated users
I had a problem with automatically creating users that existed in the LDAP directory (Novell eDirectory in my case), but not in the MediaWiki DB. The following was occurring:
 * If user exists in directory (LDAP)
 * If user exists in MediaWiki DB
 * If user had a password set in MediaWiki DB
 * If user entered correct LDAP password
 * User authenticated
 * Else (user entered incorrect LDAP password or correct MediaWiki DB password)
 * Wrong password text displayed; user not authenticated
 * Else (user exists in LDAP and MediaWiki DB, but with empty password in MediaWiki DB)
 * Wrong password text displayed; user not authenticated
 * Else (user exists in LDAP, but not MediaWiki DB)
 * User was created in MediaWiki DB with empty password. Authentication would follow, result according to flow above: Wrong password text displayed; user not authenticated
 * Else (user doesn't exist in LDAP)
 * User doesn't exist text displayed; user not authenticated

This was caused by some conflicting checks that were done in the MediaWiki code. I made 2 changes to correct this.

In includes/SpecialUserlogin.php, line 367: if (!$u->checkPassword( $this->$mPassword )) { changed to: if (!$u->checkPassword( $this->$mPassword ) && !($wgAuth->authenticate( $u->getName, $this->mPassword ))) {

In extensions/LdapAuthentication.php, line 604: if ($updateLDAP || $mailPassword ) { changed to: if ($updateLDAP == false || $mailPassword == false) {

This last change changes the logic from what I believe was intended, but it works for me. After this, everything works as it should (LDAP user is locally created with empty password, and is able to authenticate using only the LDAP password).

Versions used: MediaWiki 1.10 PHP 5.1.2 Apache 2.2.0 MySQL 5.0.18 LdapAuthentication 1.1d SLES 10 Novell eDirectory 8.7.3.9

LdapAuthentication.php up to 1.1c (>=1.1d can skip this)
I've added a bug into MediaWiki's bugzilla to get part of this fixed. One part of the workaround is in my code (which will be fixed and released soon), and the other is in MediaWiki's code. So, to make it work, please change the following in LdapAuthentication.php in the initUser function (if using 1.1c or below):

$user->setPassword( '' );

to:

$user->mPassword = '' ;

and add the following function to LdapAuthentication.php:

/**        * Can the wiki change passwords in LDAP? * Return true if yes. *        * @return bool * @access public */           function allowPasswordChange { global $wgLDAPUpdateLDAP, $wgLDAPMailPassword;

if ( isset($wgLDAPUpdateLDAP[$_SESSION['wsDomain']]) ) { $updateLDAP = $wgLDAPUpdateLDAP[$_SESSION['wsDomain']]; }               if ( isset($wgLDAPMailPassword[$_SESSION['wsDomain']]) ) { $mailPassword = $wgLDAPMailPassword[$_SESSION['wsDomain']]; }               if ( $updateLDAP || $mailPassword ) { return true; } else { return false; }              }

SpecialUserlogin.php (all Versions MediaWiki 1.9.x)
And in includes/SpecialUserlogin.php you can use the following patch (you probably want to patch by hand since this patch is against SVN):

--- SpecialUserlogin.php       (revision 19677) +++ SpecialUserlogin.php       (working copy) @@ -307,13 +307,18 @@        * @private */       function initUser( $u ) { +              global $wgAuth; +               $u->addToDatabase; -              $u->setPassword( $this->mPassword ); + +              if ( $wgAuth->allowPasswordChange ) { +                      $u->setPassword( $this->mPassword ); +              } +                $u->setEmail( $this->mEmail ); $u->setRealName( $this->mRealName ); $u->setToken; -              global $wgAuth; $wgAuth->initUser( $u ); $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );

Please let me know if this fixes the problem
I don't currently have the ability to test MediaWiki 1.9 w/ LDAP authentication, please let me know if this fixes your problem. --Ryan Lane 18:16, 13 February 2007 (UTC)

With a config very similar to Robert, above, and applying you fixes everything seems to be working fine for me. -- sterling 144.92.220.30 20:30, 15 February 2007 (UTC)

The above config changes together with LDAPAuthentication 1.1d fixing the problem with MediaWiki 1.9.2 and LDAP authentication against Novell eDirectory. Thanx a lot! -- Günther Rasch 07:35, 22 February 2007 (UTC)

Shit yeah! I've been trying to make this work for 2 days straight and I missed this last step. Works like a charm. :) -- Jonathan Puddle

This is exactly what i was searching for. Now every LDAP-user ist automaticaly registered while logging in at the first time, and authenticates with LDAP-password. Thanx a lot! -- Jens Vieler, 21 March 2007

This worked for me. Against both AD and Linux LDAP. Thanks much! -- John Harris, 23 March 2007 (Virginia Tech, ECE)

This fix stopped me to sweat over the ldap issue I have been having. Thanks!!!! - Mutuk March 27 2007.

PHP errors in allowPasswordChange
This fix produces PHP errors because your local variables $updateLDAP and $mailPassword are not properly scoped. (Enable onscreen PHP errors to see them.) You have defined these variables inside the IF statements, but your final test is outside. So these error messages are produced for the if ( $updateLDAP || $mailPassword ) test:

Notice: Undefined variable: updateLDAP in ....\extensions\LdapAuthentication.php on line 596

Notice: Undefined variable: mailPassword in ....\extensions\LdapAuthentication.php on line 596

To fix, just set both variables to the empty string just below the "global" line. Maiden taiwan 02:29, 1 March 2007 (UTC)


 * Well, they aren't errors, just warnings. Another user has pointed this out (I'm too lazy to find it now), and it'll be fixed in the next release. Ryan Lane 14:30, 1 March 2007 (UTC)

allowPasswordChange doesn't check for $wgLDAPUseLocal
This fix causes another bug. If $wgLDAPUseLocal is set to true and no LDAP user with write permission is specified, then when someone tries to create a new account and the wiki attempts to add the user's password to the database, it first checks with $wgAuth->allowPasswordChange which returns false. This causes the user to be added to the database fine but with no password. The user is logged in, but once they log out their account has no password so they can never get back in. --Christian 15:57, 17 April 2007 (UTC)

if ( 'local' == $_SESSION['wsDomain'] ) { return true; }


 * You can fix this by adding the above to the beginning of the function allowPasswordChange --Christian 16:13, 17 April 2007 (UTC)


 * Thanks for the report and fix; this will be in the next release. Ryan lane 02:45, 18 April 2007 (UTC)


 * This is fixed in svn right now if it is causing you any issues. Ryan lane 02:45, 18 April 2007 (UTC)


 * After overwriting 1.1d with 1.1e on 1.9.3, my password is no longer accepted. I'm using AD 2003.  Are there any additional tweaks that need to be done?  163.252.39.78 14:56, 18 April 2007 (UTC)


 * 1.1d to 1.1e was essentially just bug fixes. It shouldn't have broken anything. I'll need debugging info from you, and your configuration with anything sensitive snipped out. --Ryan lane 15:04, 18 April 2007 (UTC)


 * I just tried to apply this fix to the top of allowPasswordChange but it had no effect. I had to simply put "return true;" at the top of the function for my logins to work.  This was with $wgLDAPUseLocal set to 'true'.  (Setting it to 'false' caused the same password-change-forbidden error before I applied my hack, though).  This is on mw 1.9.3 and plugin 1.1e.  --138.26.64.50 15:35, 24 April 2007 (UTC)

= How do I install this thing? =

Quick Answer!
Drop the LdapAuthentication.php into the includes or extensions directory and follow the configuration information.

= How do I configure it? =

Configuration examples are here:

Configuration Examples

Explanation of the plugin is in the content page.

= Support =

Please ask support question here, posting new questions at the bottom.

password min length limit curiosity
It's over my head, but the authors may be interested in this user's findings:


 * http://mail.wikipedia.org/pipermail/mediawiki-l/2005-September/006985.html
 * http://mail.wikipedia.org/pipermail/mediawiki-l/2005-September/006997.html

-- Sy / (talk) 12:30, 15 September 2005 (UTC)


 * Ah, good to know. I'm probably not checking the password limit when creating users. This will have to be an outstanding bug for a while. I'm currently evacuated from new orleans, and have no ability to work on the patch. -- Ryan Lane


 * This bug is unfortunately in the core code, and not in my plugin. I have submitted a bug (and a patch correcting the problem) into the bugzilla at: http://bugzilla.wikipedia.org/show_bug.cgi?id=4081
 * --Ryan Lane

Nested Groups option not working
Hello All,

I know this isn't a default plugin for MediaWiki, but I am sure that others are using LDAP authentication.

I am using the Ldap plugin on the mediawiki site and I am unable to get nested groups working. I can authenticate when a user is part of a specific group, but when a group is added to the required group it does not work.

Below is my nested groups array in my Localsettings.php file.

$wgLDAPGroupSearchNestedGroups = array( "domain.com"=>true );

I am running mediawiki 1.9.3, php 5.2.0, apache 2.2.3 and Version 1.1c 12/04/2006 of this module.

Can anyone help me out with this?

What can I do to fix this or get further assistance?

Thanks :)

DEBUG: Entering validDomain User is using a valid domain. Setting domain as: domain.com Entering getCanonicalName Username isn't empty. Munged username: User Entering authenticate Entering Connect Using TLS or not using encryption. Using servers: ldap://dc2.domain.com ldap://dc4.domain.com Using TLS Connected successfully Entering getSearchString Doing a proxy or anonymous bind Entering getUserDN Doing a proxy bind Created a regular filter: (sAMAccountName=User) Using base: dc=domain,dc=com Fetched username is not a string (check your hook code...). userdn is: CN=User,OU=Operations&NOC,OU=Domain Staff,DC=domain,DC=com Binding as the user Binded successfully Checking for (new style) group membership Entering isMemberOfRequiredLdapGroup Required groups:cn=nca se wiki users,ou=groups,dc=domain,dc=com Entering getUserGroups Entering getGroups Search string: (&(member=CN=User,OU=Operations&NOC,OU=Domain Staff,DC=domain,DC=com)(objectclass=group)) Binding as the proxyagentDN Returned groups:cn=operations,ou=groups,dc=domain,dc=com,cn=nca all,ou=groups,dc=domain,dc=com,cn=nca vpn users,ou=groups,dc=domain,dc=com,cn=nca altiris,ou=groups,dc=domain,dc=com,cn=systems engineering,ou=groups,dc=domain,dc=com,cn=nca   outage,ou=groups,dc=domain,dc=com,cn=altiris helpdesk workers,ou=groups,dc=domain,dc=com,cn=nc tripwire,ou=groups,dc=domain,dc=com,cn=linuxadmins,ou=groups,dc=domain,dc=com,cn=linuxdns,ou=groups,dc=domain,dc=com,cn=nca gw wiki users,ou=groups,dc=domain,dc=com,cn=nca wiki core users,ou=groups,dc=domain,dc=com Returned groups:,,,,,,,,,,, Entering searchNestedGroups Checking groups:cn=operations,ou=groups,dc=domain,dc=com,cn=nca all,ou=groups,dc=domain,dc=com,cn=nca vpn users,ou=groups,dc=domain,dc=com,cn=nca altiris,ou=groups,dc=domain,dc=com,cn=systems engineering,ou=groups,dc=domain,dc=com,cn=nca   outage,ou=groups,dc=domain,dc=com,cn=altiris helpdesk workers,ou=groups,dc=domain,dc=com,cn=nc tripwire,ou=groups,dc=domain,dc=com,cn=linuxadmins,ou=groups,dc=domain,dc=com,cn=linuxdns,ou=groups,dc=domain,dc=com,cn=nca gw wiki users,ou=groups,dc=domain,dc=com,cn=nca wiki core users,ou=groups,dc=domain,dc=com

Entering getUserGroups Checking membership for: cn=operations,ou=groups,dc=domain,dc=com Checking membership for: cn=nca all,ou=groups,dc=domain,dc=com Checking membership for: cn=nca vpn users,ou=groups,dc=domain,dc=com Checking membership for: cn=nca altiris,ou=groups,dc=domain,dc=com Checking membership for: cn=systems engineering,ou=groups,dc=domain,dc=com Checking membership for: cn=nca   outage,ou=groups,dc=domain,dc=com Checking membership for: cn=altiris helpdesk workers,ou=groups,dc=domain,dc=com Checking membership for: cn=nc tripwire,ou=groups,dc=domain,dc=com Checking membership for: cn=linuxadmins,ou=groups,dc=domain,dc=com Checking membership for: cn=linuxdns,ou=groups,dc=domain,dc=com Checking membership for: cn=nca gw wiki users,ou=groups,dc=domain,dc=com Checking membership for: cn=nca wiki core users,ou=groups,dc=domain,dc=com (Loops about 5 times or so)

Entering searchNestedGroups Checking groups:cn=operations,ou=groups,dc=domain,dc=com,cn=nca all,ou=groups,dc=domain,dc=com,cn=nca vpn users,ou=groups,dc=domain,dc=com,cn=nca altiris,ou=groups,dc=domain,dc=com,cn=systems engineering,ou=groups,dc=domain,dc=com,cn=nca   outage,ou=groups,dc=domain,dc=com,cn=altiris helpdesk workers,ou=groups,dc=domain,dc=com,cn=nc tripwire,ou=groups,dc=domain,dc=com,cn=linuxadmins,ou=groups,dc=domain,dc=com,cn=linuxdns,ou=groups,dc=domain,dc=com,cn=nca gw wiki users,ou=groups,dc=domain,dc=com,cn=nca wiki core users,ou=groups,dc=domain,dc=com (Repeats about 10 times or so)

Entering getUserGroups Checking membership for: cn=operations,ou=groups,dc=domain,dc=com Checking membership for: cn=nca all,ou=groups,dc=domain,dc=com Checking membership for: cn=nca vpn users,ou=groups,dc=domain,dc=com Checking membership for: cn=nca altiris,ou=groups,dc=domain,dc=com Checking membership for: cn=systems engineering,ou=groups,dc=domain,dc=com Checking membership for: cn=nca   outage,ou=groups,dc=domain,dc=com Checking membership for: cn=altiris helpdesk workers,ou=groups,dc=domain,dc=com Checking membership for: cn=nc tripwire,ou=groups,dc=domain,dc=com Checking membership for: cn=linuxadmins,ou=groups,dc=domain,dc=com Checking membership for: cn=linuxdns,ou=groups,dc=domain,dc=com Checking membership for: cn=nca gw wiki users,ou=groups,dc=domain,dc=com Checking membership for: cn=nca wiki core users,ou=groups,dc=domain,dc=com wiki users,ou=groups,dc=domain,dc=com Checking membership for: cn=nca wiki core users,ou=groups,dc=domain,dc=com (Loops 137 times)

Entering searchNestedGroups Couldn't find user in any nested groups. Couldn't find the user in any groups (2). Entering strict. Returning true in strict. Entering modifyUITemplate

Configuration require_once( 'LdapAuthentication.php' ); $wgAuth = new LdapAuthenticationPlugin; $wgLDAPProxyAgent = array( "domain.com"=>"CN=ldapuser,CN=Users,DC=domain,DC=com" ); $wgLDAPSearchAttributes = array( "domain.com"=>"sAMAccountName" ); $wgLDAPProxyAgentPassword = array( "domain.com"=>"password" ); $wgLDAPDomainNames = array( "domain.com" ); $wgLDAPServerNames = array( "domain.com"=>"dc2.domain.com dc3.domain.com" ); $wgLDAPUseLocal = false; $wgLDAPAddLDAPUsers = false; $wgLDAPUpdateLDAP = false; $wgLDAPMailPassword = false; $wgLDAPRetrievePrefs = true; $wgMinimalPasswordLength = 1; $wgLDAPGroupLowerCaseUsername = false; $wgLDAPRequiredGroups = array( "domain.com"=>array("cn=nca se wiki users,ou=groups,dc=domain,dc=com") ); $wgLDAPGroupUseFullDN = array( "domain.com"=>true ); $wgLDAPGroupObjectclass = array( "domain.com"=>"group" ); $wgLDAPGroupAttribute = array( "domain.com"=>"member" ); $wgLDAPGroupSearchNestedGroups = array( "domain.com"=>true ); $wgLDAPBaseDNs = array( "ncaustin.com"=>"dc=domain,dc=com" ); $wgLDAPDebug = 4; //for debugging LDAP $wgShowExceptionDetails = true; //for debugging MediaWiki $wgLDAPEncryptionType = array("domain.com"=>"tls");

The group "systems engineering" is a member of nca se wiki users. But I still can't login... I hope this is enough information.


 * This looks like it may be a bug. The part about "Returned groups" looks completely wrong. It shows the same group a bunch of times. All the looping is pretty strange too. Can you post your configuration with sensitive stuff snipped out? --Ryan lane 13:30, 27 March 2007 (UTC)

Was this issue ever resolved? I am having the same problem. The only difference is I am doing a straight bind. Any ideas? Thanks.


 * I just noticed that "$wgLDAPGroupNameAttribute" isn't defined here... it should be defined as $wgLDAPGroupNameAttribute = array ("domain.com"=>"cn"); --Ryan lane 22:48, 30 June 2007 (UTC)

I still can't get it to work. Same errors. Just to clarify, should that be $wgLDAPGroupNameAttribute = array ("domain.com"=>"cn"); or should I substitute cn for my master group name that contains the nested groups that need to be searched? I tried it both ways, but I wanted to make sure my syntax was right. MIS is the master group that contains a bunch of other nested groups. So here is what I used with all the sensitive information removed.

require_once( 'LdapAuthentication.php' ); $wgAuth = new LdapAuthenticationPlugin; $wgLDAPDomainNames = array( "domain.com" ); $wgLDAPServerNames = array( "domain.com"=>"server1.domain.com server2.domain.com" ); $wgLDAPSearchStrings = array( "domain.com"=>"USER-NAME@domain.com" ); $wgLDAPEncryptionType = array( "domain.com"=>"clear" ); $wgLDAPUseLocal = true; $wgMinimalPasswordLength = 1;

$wgGroupPermissions['*']['edit'] = false;
 * 1) Disable anonymous editing

$wgGroupPermissions['*']['createpage'] = false;
 * 1) Anonymous users can't create pages

$wgGroupPermissions['*']['read'] = false;
 * 1) Disable reading by anonymous users

$wgStrictFileExtensions = false; $wgCheckFileExtensions = false; $wgMimeDetectorCommand= "file -bi"; require_once("extensions/SpecialPdf.php");

$wgLDAPRequiredGroups = array( "domain.com"=>array("cn=mis,ou=groups,ou=site1,ou=sites,dc=domain,dc=com") ); $wgLDAPGroupUseFullDN = array( "domain.com"=>true ); $wgLDAPGroupObjectclass = array( "domain.com"=>"group" ); $wgLDAPGroupAttribute = array( "domain.com"=>"member" ); $wgLDAPGroupSearchNestedGroups = array( "domain.com"=>true ); $wgLDAPBaseDNs = array( "domain.com"=>"ou=administrators,dc=domain,dc=com" ); $wgLDAPSearchAttributes = array( "domain.com"=>"sAMAccountName" ); $wgLDAPGroupNameAttribute = array( "domain.com"=>"mis" );
 * 1) DNs in $wgLDAPRequiredGroups must be lowercase, as search result attribute values are...
 * 2) $wgLDAPRequiredGroups = array( "domain.com"=>array("cn=mis-tech-2,ou=groups,ou=administrators,dc=domain,dc=com", "cn=mis-tech-1,ou=groups,ou=administrators,dc=domain,dc=com") );
 * 1) $wgLDAPGroupSearchNestedGroups = array( "domain.com"=>false );
 * 1) $wgLDAPBaseDNs = array( "domain.com"=>"ou=groups,ou=administrators,dc=domain,dc=com" );

$wgLDAPDebug = 3;

It should be this exactly (copy, paste): $wgLDAPGroupNameAttribute = array( "domain.com"=>"cn" );

Once you make this change and authenticate, you should notice this line Returned groups:,,,,,,,,,,, Change to Returned groups:mis-tech-2,mis-tech-1,etc Basically, it will return the actual groups instead of ,,,,,,,,,. Once it returns the groups, does the Nested Groups work? It's still not working for me. I'm going to make a post to the mailing list. 2007-07-02

I made the change and it did return the proper groups instead of just the commas, but the login still isn't working.

In my case, I can log in if the user is in one of the $wgLDAPRequiredGroups, however, I CANNOT log in when my user is in a group, that is in one of the $wgLDAPRequiredGroups (ie, Nested). In the case of a nested group, I get what you are getting, my debug log shows massive looping when "Entering searchNestedGroups" and "Entering getUserGroups". I'm assuming this has something to do with our GroupNameAttribute, but my PHP is limited. Here is my post to the mailing list. I haven't received a reply yet: http://lists.wikimedia.org/pipermail/mediawiki-l/2007-July/021463.html


 * Ignore the last thing I wrote (now erased). I'm reading my own function very poorly :) (stupid recursion). $wgLDAPGroupNameAttribute should definately be set as "cn", as that seems to be the naming attribute of your group. The massive looping may be a bug, as I've noticed it happening to a few people. It also may just be poor debug logging on my part. I'm going to change around the logging some to be less spammy, and more informative. Hopefully it'll help us track this problem down. --Ryan lane 19:55, 6 July 2007 (UTC)

Ryan, If you need help recreating the error, or testing, I'm available M-F 9-6 PST. You can get my email from the post http://lists.wikimedia.org/pipermail/mediawiki-l/2007-July/021463.html

initUser bug is fixed in current mediawiki svn
I setup MediaWiki 1.9.3 and LdapAuthentication.php but got errors about "password-change-forbidden" when logging in as a user in LDAP but not yet in the MW DB. I found that this bug is already fixed in mediawiki svn, just not yet in a released version. If you encounter this, apply this small change:

http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/SpecialUserlogin.php?r1=20158&r2=20300

--Mindless 15:58, 30 March 2007 (UTC)

Problems with underlines in username (AD)
Hi I've got the Problem that users with underlines in their usernames can't login. Read in another article that I have to change $this->mTextform = str_replace( '_', ' ', $dbkey ); to $this->mTextform = str_replace( '_', '_', $dbkey ); int the Title.php. But this didn't make any difference. The Login still doesn't work with users withe underlines in their loginnames. Is there anybody who can help me? ..Thanks...


 * I honestly don't know how to fix this. The username is passed to the plugin after it is munged by MediaWiki. I'm sure there is somewhere I can hack the core code to fix this, but I haven't looked around for it yet. --Ryan lane 22:50, 30 June 2007 (UTC)

Internal server error on login
Ok, so i've setup the wiki before the weekend, and it was working all just fine with LDAP-authentication. Now on monday, when logging in with a user that already exists in the database it comes up with the following Internal server error: Database error A database query syntax error has occurred. This may indicate a bug in the software. The last attempted database query was: (SQL query hidden) from within function "User::addToDatabase". MySQL returned error "1062: Duplicate entry 'ldap-username' for key 2 (localhost)". Where ldap-username is ofcourse the ldap-user. When I delete the ldap-user from the database, logging in works just fine, but after logging out and in again it comes up with the same error message. It's strange because nothing has changed on the server over the weekend as logging on and off was just working fine last week. With debugging on the authentication has passed. Somehow it is trying to add the user again, while instead it has to update it if I am correct? Anybody got any ideas? -Martin
 * BTW, I am using the following setup: MediaWiki: 1.9.3, PHP: 5.2.1, MySQL: 5.0.37, LDAP plugin 1.1d. -Martin
 * Ok, so I fixed the problem, by making a dump and dropping the database. After this importing the database fixed the problem. Somehow there is some caching issue here, but I cannot seem to know which. Also I didn't use database caching upon installation. Maybe this helps somebody out or maybe Ryan can take a look at it in furter development. -Martin


 * Did you mess around with your database manually? I don't remember any entry called ldap-username... The plugin doesn't ever touch the database directly. --Ryan lane 21:34, 1 May 2007 (UTC)

Making work with Pywikipediabot
Has anyone had any experience getting pywikipediabot to work with LDAP? I tried it and it seems to not work because it can't successfully select the domain name. Any thoughts?

HotMonkeyAC 18:25, 27 April 2007 (UTC)


 * See: Extension talk:LDAP Authentication. This is a similar issue. the domain stuff is a valid part of the software, and bot frameworks should be providing that info on login. The pywikipediabot developer needs to fix that. --Ryan lane 20:10, 27 April 2007 (UTC)


 * one possible fix is to add the line
 * "wpDomain": self.domain,


 * to the predata array under def getCookie in login.py


 * you'll have to set self.domain somewhere else. mine is hardcoded in login.py as
 * self.domain = 'mydomain'
 * but this is very nasty. it should be set somewhere else like mywiki_family.py
 * let me know if this works for you
 * Jono Brooker 13:27, 20 June 2007 (UTC)


 * Jono, that worked perfectly.  Thank you!!!   -HotMonkeyAC 14:48, 26 June 2007 (UTC)

Import LDAP user data to wiki
Hi! I've got a strange problem. I've read a lot, but couldn't find anything, that was helping me. The LDAP-Authentication works well (after some starting troubles) ... when a user logs in, then the user is created in the wiki-database as well, that's good.

1. But is there a way, to import ALL LDAP users to the wiki? Thus, I can see on the userlist site in the wiki all LDAP users, which are able to log in, even those who haven't been logged in yet? Maybe there is a way in combination with another extension?

2. And is there a way, to automatically import the basic LDAP data of a user to his usersite, like Realname and email? Therefore the usersites of the users, who haven't logged in yet, wouldn't be that empty.

I already thought about writing a php script, which inserts the users in die usertable in the wiki DB ... but I'm afraid of destroying the consistency of the DB.

I hope you can give me some hints to achieve that.

thanks -- widi


 * Unfortunately, that is probably the only way to do it. If you really want to do this, I'd right a php script that uses MediaWiki to add the users, don't manually mess with the database. The SSL auto-authentication hook is probably a good starting ground for figuring out how to do it; you can ignore everything except for the part that actually creates the users. --Ryan lane 22:44, 30 June 2007 (UTC)

Please archive this page
Too long. 202.54.176.51 10:14, 7 May 2007 (UTC)


 * Is this better? Enjoy --Ryan lane 03:55, 13 May 2007 (UTC)

Another preferences problem
Everything works great for me except the preference pulling, i think (via the $wgLDAPdebug option) i have narrowed it down to it doesn't pull preferences. i have the following line at the end of the LDAP options in localsettings.php: $wgLDAPRetrievePrefs = true; but for some reason this is still returning false: $this->printDebug("Retrieving preferences",1); $entry = @ldap_read($ldapconn, $userdn, "objectclass=*"); $info = @ldap_get_entries($ldapconn, $entry); $this->email = $info[0]["mail"][0]; $this->lang = $info[0]["preferredlanguage"][0]; $this->nickname = $info[0]["displayname"][0]; $this->realname = $info[0]["cn"][0]; $this->printDebug("Retrieved: $this->email, $this->lang, $this->nickname, $this->realname",2); #} I'm not very patient so after posting this i continued to try to fix it. i found that this was also returning false: #if ( isset($wgLDAPRetrievePrefs[$_SESSION['wsDomain']]) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) { $this->printDebug("Setting user preferences.",1); if ('' != $this->lang) { $user->setOption('language',$this->lang); } 		if ('' != $this->nickname) { $user->setOption('nickname',$this->nickname); } 		if ('' != $this->realname) { $user->setRealName($this->realname); } 		if ('' != $this->email) { $user->setEmail($this->email); } 		$saveSettings = true; #} i commented out the check blocks and preferences are reported as pulled and after my second edit, they are being populated in wiki, tho preferred language is coming back empty as such: Retrieved: eberlec@ .com,, Christopher Eberle, Christopher Eberle Setup is mediawiki 1.9.3, IIS6, mysql 4.1, PHP 5.2.2, windows 2003 standard I have changed the specialuserlogin.php as suggested and here are the settigns from my localsettings.php (incase you need them):
 * 1) if ( isset($wgLDAPRetrievePrefs[$_SESSION['wsDomain']]) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) {

$wgGroupPermissions['ITUser']['read'] = true; $wgGroupPermissions['ITUser']['edit'] = true; $wgGroupPermissions['ITUser']['createpage'] = true; $wgGroupPermissions['ITUser']['createtalk'] = true; $wgGroupPermissions['ITUser']['upload'] = true; $wgGroupPermissions['*']['edit'] = false; $wgGroupPermissions['user']['edit'] = false; $wgGroupPermissions['*']['createaccount'] = false; $wgWhitelistRead = array( "Main Page", "Special:Userlogin", "Special:Userlogout", "-", "MediaWiki:Monobook.css" ); $wgGroupPermissions['*']['read']= false; $wgGroupPermissions['user']['read']=false; $wgShowIPinHeader = false; require_once( 'LdapAuthentication.php' ); $wgAuth = new LdapAuthenticationPlugin; $wgLDAPDomainNames = array(" "," "); $wgLDAPServerNames = array(" "=>"naddc1. .com"); $wgLDAPSearchStrings = array(" "=>" \\USER-NAME"); $wgLDAPEncryptionType = array(" "=>"clear"); $wgLDAPUseLocal = false; $wgMinimalPasswordLength = 7; $wgLDAPRetrievePrefs = true; $wgLDAPDebug=4; $wgLDAPBaseDNs = array( " "=>"dc= ,dc=com" ); $wgLDAPSearchAttributes = array( " "=>"sAMAccountName" ); $wgShowExceptionDetails = true; $wgLogo = "http://mercury/ITWiki/images/c/c9/Logo.png"
 * 1) Create IT User group
 * 1) Only logged in ITUsers can edit
 * 1) Nobody is allowed to create accounts
 * 1) Define pages people not logged in can see
 * 1) LDAP Auth Integration info##
 * 2) These lines should work in conjunction with ldapauthentication.php in the includes dir.
 * 1) These lines should work in conjunction with ldapauthentication.php in the includes dir.
 * 1) These lines should work in conjunction with ldapauthentication.php in the includes dir.
 * 1) Setup for LDAP Auth

And here is my output:

Entering validDomain User is using a valid domain. Setting domain as: Entering getCanonicalName Username isn't empty. Munged username: Eberlec Entering authenticate Entering Connect Using TLS or not using encryption. Using servers: ldap://naddc1. .com Connected successfully Entering getSearchString Doing a straight bind userdn is: \Eberlec Binding as the user Binded successfully Entering getUserDN Created a regular filter: (sAMAccountName=Eberlec) Using base: dc= ,dc=com Fetched username is not a string (check your hook code...). Pulled the user's DN: CN=Christopher Eberle,OU=Local Admins,OU=Groups,OU=NAA,DC= ,DC=com Retrieving preferences Retrieved: eberlec@ .com,, Christopher Eberle, Christopher Eberle Authentication passed Entering updateUser

(What is the deal with the "Fetched username is not a string"? Seems to be in everyones debug...)

Any help would be appreciated (after the second edit, apparently its just the checks for the variable $wgLDAPRetrievePrefs isn't being read or stored properly. Commenting out the "if" checks makes the whole thing work perfect. Any idea why the if checks aren't working?). eberlec at nicholsal (ignore this parented part) dot com


 * /me pokes you in the - you are using the wrong syntax for this option, and the option is documented at Extension:LDAP_Authentication. This option, like most options, are defined on a per domain basis. A good way to ensure your options are correct is to never use external documentation for how to configure this plugin. External documentation almost always seems to be completely incorrect. Usually the external docs were written for an older version of the plugin. Options have changed since then.


 * BTW, the "Fetched username is not a string" part is something most people don't configure. It isn't a bug; I guess I should make the message a little clearer. --Ryan lane 03:35, 13 May 2007 (UTC)

Username / Real Name question
I'm tackling LDAP authentication for the first time. I've managed to connect and authenticate, logging in with my LDAP username. Ideally, I'd like to login to an account named after LDAP real name, eg. having authenticated with username "johns", the registered account name is "John Smith" instead of "johns". Is this possible? I'm using the following configuration fragment (version 1.1d).

require_once( 'LdapAuthentication.php' ); $wgAuth = new LdapAuthenticationPlugin; $wgLDAPDomainNames = array( " " ); $wgLDAPServerNames = array( " "=>" "  ); $wgLDAPSearchAttributes = array( " "=>"uid" ); $wgLDAPBaseDNs = array( " "=>"o=Gla" ); $wgLDAPEncryptionType = array( " "=>"false" ); $wgLDAPDebug = 7; $wgMinimalPasswordLength = 1;


 * Well, if you have an attribute in LDAP that is the same as the real name, then yes, it is possible. For instance, if the entry's cn value is "John Smith", you can change:


 * $wgLDAPSearchAttributes = array( " "=>"uid" );


 * to:


 * $wgLDAPSearchAttributes = array( " "=>"cn" );


 * This way, the user can log in with "John Smith" instead of "johns". Also, btw, if you don't want to use encryption, set:


 * $wgLDAPEncryptionType = array( " "=>"false" );


 * to:


 * $wgLDAPEncryptionType = array( " "=>"clear" );


 * The plugin defaults to TLS encryption if the setting isn't defined, or if it is set to an incorrect value (for protection purposes). However, if this is working for you, set that to "tls", or remove the entry, since TLS is much better than clear. --Ryan lane 22:41, 30 June 2007 (UTC)

LDAPAuth for 1.10?
I try to use LDAPAuth for MediaWiki 1.10... it works with

$wgLDAPEncryptionType = array("D Name"=>"clear");

but not with

$wgLDAPEncryptionType = array("D Name"=>"ssl");

I get Failed to bind as xxusernamexx when I use ssl... - any idea?


 * Ok - it works fine ;) I forgot the LDAP-Cert :-/ -- 07:57, 24 May 2007

PHP warnings
With the latest version on MW 1.10 getting the following PHP warnings: [Mon May 28 21:59:59 2007] [error] [client ...] PHP Notice: Undefined index:  wsDomain in ..../extensions/LdapAuthentication/LdapAuthentication.php on line 669, referer: http://.../w/Special:Preferences [Mon May 28 21:59:59 2007] [error] [client ...] PHP Notice: Undefined index:  wsDomain in .../extensions/LdapAuthentication/LdapAuthentication.php on line 673, referer: http://.../w/Special:Preferences [Mon May 28 21:59:59 2007] [error] [client ...] PHP Notice: Undefined index:  wsDomain in .../extensions/LdapAuthentication/LdapAuthentication.php on line 677, referer: http://.../w/Special:Preferences This comes from a failure to check for isset($_SESSION['wsdomain']) in the relevant code (3 spots I've found).

I just finished setting up LdapAuthentication.php version 1.1e with MediaWiki 1.93. After getting the login to work with TLS & SSL to work and the debug info turned on, I see these warnings after successful login:

... Notice: Undefined index: mail in /var/www/wiki/mediawiki-1.9.3/extensions/LdapAuthentication.php on line 354 Notice: Undefined index: preferredlanguage in /var/www/wiki/mediawiki-1.9.3/extensions/LdapAuthentication.php on line 355 Notice: Undefined index: displayname in /var/www/wiki/mediawiki-1.9.3/extensions/LdapAuthentication.php on line 356 Notice: Undefined index: cn in /var/www/wiki/mediawiki-1.9.3/extensions/LdapAuthentication.php on line 357 Retrieved:, , , ...

Lines 354-357 contain: $this->email = $info[0]["mail"][0]; $this->lang = $info[0]["preferredlanguage"][0]; $this->nickname = $info[0]["displayname"][0]; $this->realname = $info[0]["cn"][0];

The warnings go away if I remove the setting for $wgLDAPRetrievePrefs.

Real AD Group Synch
I needed a real Group Sync to get the LDAP Extension work with the "Group_Based_Access_Control" Extension. I wrote this method and add it to the Class. I call in in the "authenticate" method. It does a real syncronize between AD Group Membership and the Wiki UserGroups. (Note: The Group Name size is limited by wiki database)

04.06.07 by Punisher: [mailto:punisher@e-workstation.de E-Mail]

function syncGroups($username){ $aLDAPGroups = $this->userLDAPGroups; $userID = User::idFromName($username);

if(($userID == 0)||(count($aLDAPGroups)== 0)) return; $u = User::newFromName($username); $aOldGroups = $u->getGroups;

// Adding new Groups for($i=0;$iaddGroup($aLDAPGroups[$i]); }           // Remove old Groups for($i=0;$iremoveGroup($aOldGroups[$i]); }           }	    $u->saveSettings; return; }


 * Huh? I've tested the plugin against active directory, and it seems to sync groups just fine. Is there something specific not working in the plugin? --Ryan lane 22:32, 30 June 2007 (UTC)

Creating a sysop user after configuring LDAP
My WikiSysop user account seems to be locked out now that I've set up MediaWiki to use LDAP authentication and I can't create a WikiSysop user in our LDAP directory. Can I make my own LDAP username a sysop user somehow? The instructions under Adding WikiSysop seem to be for doing so before setting up LDAP; however, I've already gotten LDAP configured.


 * Nevermind, I found the instructions on the Help:Setting user rights in MediaWiki page and that helped me figure it out.

HTTP Basic Authentication
I modified the extension to use HTTP Basic Authentication, if available. I tried to make it be an AutoAuth plugin, like SSL Auth is in this extension now, but I'm willing to bet it's not 100% right. You'll find a diff against SVN trunk below.

Index: LdapAuthentication.php

=
====================================================== --- LdapAuthentication.php     (revision 22916) +++ LdapAuthentication.php     (working copy) @@ -56,7 +56,7 @@

function LdapAuthenticationPlugin { } - +       /**         * Check whether there exists a user account with the given name. * The name will be normalized to MediaWiki's requirements, so @@ -1587,6 +1587,11 @@ $wgHooks['PersonalUrls'][] = 'NoLogout'; /* Disallow logout link */ }                       break; +              case "http": +                      $wgAuth->printDebug( "Allowing http basic authentication.", 1 ); +                       $wgHooks['AutoAuthenticate'][] = 'HttpAuth'; /* Hook for magical authN */ +                       $wgHooks['PersonalUrls'][] = 'NoLogout'; /* Disallow logout link */ +                      break; default: $wgAuth->printDebug( "Not using any AutoAuthentication methods.", 1 ); } @@ -1597,6 +1602,98 @@       $personal_urls['logout'] = null; }

+function HttpAuth(&$user){ +      global $wgUser; +      global $wgAuth; +      global $wgLDAPHttpDomain; +      $wgAuth->printDebug( "Entering HttpAuth.", 1 ); +      $wgAuth->printDebug( "PHP_AUTH_USER = " . $_SERVER['PHP_AUTH_USER'], 1 ); +      $wgAuth->printDebug( "PHP_AUTH_PW = " . $_SERVER['PHP_AUTH_PW'], 1 ); +      $wgAuth->printDebug( "wgLDAPHttpDomain = " . $wgLDAPHttpDomain ,1 ); +      $wgAuth->setDomain($wgLDAPHttpDomain); + +      //Give us a user, see if we're around +      $tmpuser = User::newFromSession; + +      //They already with us? If so, quit this function. +      if( $tmpuser->isLoggedIn ) { +              $wgAuth->printDebug( "User is already logged in.", 1 ); +              return; +      } + +        //Let regular authentication plugins configure themselves for auto +       //authentication chaining +       //$wgAuth->autoAuthSetup; + +      $authenticated = $wgAuth->authenticate( $_SERVER['PHP_AUTH_USER' ], $_SERVER['PHP_AUTH_PW'] ); +      if ( !$authenticated ) { +                      //If the user doesn't exist in LDAP, there isn't much reason to +                       //go any further. +                      $wgAuth->printDebug("User wasn't found in LDAP, exiting.", 1 ); +                      return; +              } +       if ( $tmpuser == null ) { +              $wgAuth->printDebug( "Username is not a valid MediaWiki username.", 1 ); +              return; +      } + +       //We need the username that MediaWiki will always use, *not* the one we +       //get from LDAP. +      $mungedUsername = $wgAuth->getCanonicalName( $_SERVER['PHP_AUTH_USER' ] ); + +      $wgAuth->printDebug( "User exists in LDAP; finding the user by name in MediaWiki.", 1 ); + +      //Is the user already in the database? +      $tmpuser = User::newFromName( $mungedUsername ); + +      if ( $tmpuser == null ) { +              $wgAuth->printDebug( "Username is not a valid MediaWiki username.", 1 ); +              return; +      } + +       //If exists, log them in +       if( $tmpuser->getID != 0 ) { +              $wgAuth->printDebug( "User exists in local database, logging in.", 1 ); +              $wgUser = &$tmpuser; +              $wgAuth->updateUser( $wgUser ); +              $wgUser->setCookies; +              $wgUser->setupSession; +              return; +      } +       $wgAuth->printDebug( "User does not exist in local database; creating.", 1 ); + +      //Require SpecialUserlogin so that we can get a loginForm +      require_once( 'SpecialUserlogin.php' ); + +      //This section contains a silly hack for MW +       global $wgLang; +      global $wgContLang; +      global $wgRequest; +      if( !isset( $wgLang ) ) +      { +               $wgLang = $wgContLang; +              $wgLangUnset = true; +      } + +       $wgAuth->printDebug( "Creating LoginForm.", 1 ); + +      //This creates our form that'll let us create a new user in the database +      $lf = new LoginForm( $wgRequest ); + +      //The user we'll be creating... +      $wgUser = &$tmpuser; +      $wgUser->setName( $wgContLang->ucfirst( $mungedUsername ) ); + +      $wgAuth->printDebug( "Creating User.", 1 ); + + +      //Create the user +      $lf->initUser( $wgUser ); + +      //Initialize the user +      $wgUser->setupSession; +      $wgUser->setCookies; +} + /** * Does the SSL authentication piece of the LDAP plugin. *

Problems authenticating usernames with underscore character(s)
I installed the LDAP Auth extension and have it working in MediaWiki 1.7.1 and Windows 2003 Server. The problem I have is that authentication does not work with usernames that include an underscore ("_"). After googling around, it looks like it is normal for MediaWiki to convert underscores ("_") to spaces (" "). Is there any workaround for this? Or, will I have to make sure any AD user that needs Wiki access has a username without any underscores? :( Please see my included debug output below... Anyone else have this issue :)? Your help is appreciated! Entering validDomain User is using a valid domain. Setting domain as: exampledomain Entering getCanonicalName Username isn't empty. Munged username: Wiki user Entering authenticate Entering Connect Using TLS or not using encryption. Using servers: ldap://ad0.exampledomain.org Connected successfully Entering getSearchString Doing a straight bind userdn is: exampledomain\Wiki user Binding as the user Failed to bind as exampledomain\Wiki user Entering strict. Returning true in strict. Entering modifyUITemplate

Reeboblue 20:32, 13 June 2007 (UTC) [mailto:reeboblue@yahoo.com E-Mail]


 * Well, the plugin doesn't currently support this. You may be able to add support for this into the authentication method, similar to how the plugin allows users to lowercase the username. Thing is, no matter what, you can either have users that have underscores, or spaces, not a mix of the two, because the wiki is always going to change underscores to spaces. The plugin unfortunately has no way of changing that. Sorry I couldn't be more help. --Ryan lane 22:30, 30 June 2007 (UTC)

Returned Groups not returning my Returned groups :)
I'm having a similar problem as the "Nested Groups option not working" guy. Ldapauthentication.php works fine prior to adding group based restrictions. When I add the "#group based auth" section to my config, AD users in the allowed group can no longer authenticate. However, the users that logged in prior to group based restriction were added to the users mySQL table and they can still successfully authenticate although they are no longer in the allowed group.

cat LdapAuthentication.php | grep "'version'" 'version' => '1.1f (alpha)',

Here is my conf: require_once( "includes/LdapAuthentication.php" ); $wgAuth = new LdapAuthenticationPlugin; $wgLDAPDomainNames = array( "DOMAIN" ); $wgLDAPServerNames = array( "DOMAIN"=>"frodo.domain.com legolas.domain.com" ); //this place is run by true geeks $wgLDAPSearchStrings = array( "DOMAIN"=>"DOMAIN\\USER-NAME" ); $wgLDAPUseSSL = false; //not recommended but OK for testing $wgLDAPEncryptionType = array( "DOMAIN"=>'clear' ); // this is needed in >= 1.1c $wgLDAPUseLocal = true; //allows mysql db driven auth (default Root user) $wgMinimalPasswordLength = 1; $wgLDAPRetrievePrefs = true; $wgLDAPUpdateLDAP = array( "DOMAIN"=>"false" ); //disables mediawiki from updating LDAP $wgLDAPDebug = 3; //debugging $wgLDAPBaseDNs = array( "DOMAIN"=>"dc=domain,dc=com" ); $wgLDAPRequiredGroups = array( "DOMAIN"=>array("cn=wiki-sysops,dc=group,dc=domain,dc=com") ); $wgLDAPGroupUseFullDN = array( "DOMAIN"=>true ); $wgLDAPGroupObjectclass = array( "DOMAIN"=>"group" ); $wgLDAPGroupAttribute = array( "DOMAIN"=>"member" ); $wgLDAPGroupSearchNestedGroups = array( "DOMAIN"=>false ); $wgLDAPSearchAttributes = array( "DOMAIN"=>"sAMAccountName" );
 * 1) LDAP FUN!!!
 * 1) $wgLDAPRetrievePrefs = array( "DOMAIN"=>false ); // this is needed in >= 1.1c
 * 1) group based auth

Here is the debug log:

Entering validDomain User is using a valid domain. Setting domain as: SMP-INC Entering getCanonicalName Username isn't empty. Munged username: Wiki-user Entering userExists Entering authenticate Entering Connect Using TLS or not using encryption. Using servers: ldap://frodo.smp-inc.com ldap://legolas.smp-inc.com Connected successfully Entering getSearchString Doing a straight bind userdn is: SMP-INC\Wiki-user Binding as the user Binded successfully Entering getUserDN Created a regular filter: (sAMAccountName=Wiki-user) Using base: dc=smp-inc,dc=com Fetched username is not a string (check your hook code...). Pulled the user's DN: CN=wiki-user,CN=Users,DC=smp-inc,DC=com Checking for (new style) group membership Entering isMemberOfRequiredLdapGroup Required groups:cn=wiki-sysops,dc=group,dc=smp-inc,dc=com Entering getUserGroups Entering getGroups Search string: (&(member=CN=wiki-user,CN=Users,DC=smp-inc,DC=com)(objectclass=group)) Returned groups:cn=wiki-sysops,cn=users,dc=smp-inc,dc=com Returned groups: Couldn't find the user in any groups (2). Entering modifyUITemplate Allowing the local domain, adding it to the list.

Look at my 2 lines that start with "Returned groups:". It appears that the search string is returning results, however it's not listing the groups. When there are multiple groups, the second Returned Groups: line has a bunch of commas, ie:

Returned group:,,,,,,,,

When I turn on nested groups, it also cycles the same groups multiple times. I've read this has happened to other users as well. 6/26/07 15:45 PST

EDIT-i had the wrong OU defined. this has been resolved.

Allow certain department numbers
How would I do a search for department number ("departmentnumber") or a group of them in LDAP and restrict the login using those numbers? Thanks


 * If you want to limit login by an attribute like department number, you can use search based options, like so:

$wgLDAPRequireAuthAttribute = array( "mydomain"=>true ); $wgLDAPAuthAttribute = array( "mydomain"=>"departmentnumber=331" );


 * Say for instance, you wanted to have multiple department numbers, you could use the following syntax:

$wgLDAPRequireAuthAttribute = array( "mydomain"=>true ); $wgLDAPAuthAttribute = array( "mydomain"=>"|(departmentnumber=331)(departmentnumber=332)(departmentnumber=333)" );


 * Pretty much any valid LDAP filter works; notice however, that the plugin wraps around the filter (I'll try to fix this in the future in a backwards-compatible way), so make sure you take that into account.


 * As for groups, you can define groups however you like in LDAP, and then use group based login restrictions to limit users by group.


 * Btw, I didn't notice your post earlier because you stuck the question at the top. Please stick all new support requests at the bottom of the page. --Ryan lane 20:42, 9 July 2007 (UTC)

you do iz besz
Hi Looks good! Very useful, good stuff. Good resources here. Thanks much! Bye

Thanks much!
Hello Looks good! Very useful, good stuff. Good resources here. Thanks much! G'night

Thanks much!
Hi Looks good! Very useful, good stuff. Good resources here. Thanks much! G'night