LDAP Authentication is currently enabled, and manually created users with auth set to LDAP can authenticate correctly.
My ldap contexts is:ou=Campus Accounts,dc=some_ad,dc=some_school,dc=edu
This is where all the accounts created for campus reside, there's +50K accounts here.
Can moodle automatically create moodle user accounts for people when they first login?
How can I limit this access to members of a central group? Like; cn=moodleAccounts,ou=myOU,dc=some_ad,dc=some_school,dc=edu
I attempted to use the auth_ldap_sync_user script but I'm getting bonged on exceeding the sizelimit of returned results - which appears to be set on the AD to 1000 (I can't change this). I attempted to set the ldar_create_context to a group like the one above but wasn;t getting anywhere. If I need to use this how can I restrict moodle to a single group for user creation?
Any help would be appreciated!
oops - running Moodle 1.5.2, on SLES8 w/ Apache 2.0.54, mysql 4.0.x.
Mike
Re: Populating Moodle users via Active Directory (LDAP)
Moodle _will_ create the accounts when people login for the first time, no problem with that. It can limit to users on a particular leaf (your LDAP context), but not to members of a group. You may be able to hack that in.
Re: Populating Moodle users via Active Directory (LDAP)
thanks,
Mike
Re: Populating Moodle users via Active Directory (LDAP)
ok for some weird reason my moodle has decided not to create accounts for people who can authenticate correctly
I'm keen on debugging this one through. If you can set debug on, and watch the apache logs...
Re: Populating Moodle users via Active Directory (LDAP)
Username= mwedward
AUTH= ldap
Creating mwedward
Create_user_record
auth_get_userinfo exists
Username=mwedward in LDAP Auth_get_userinfo
User_DN: CN=mwedward,OU=Campus Accounts,DC=some_ad,DC=some_school,DC=edu
user_info_result= Resource id #31
Key= Michael Value= givenname
Key= Edwards Value= sn
Key= mwedward@some_sc.edu Value= mail
Key= mwedward Value= sAMAccountName
Before LDAP_Close
LDAP_Connection= Resource id #27
#################Dies here - no LDAP close
Username= admin
AUTH= manual
#################Logged in as admin and created my accoutn manually
Username= mwedward
AUTH= ldap
Username=mwedward in LDAP Auth_get_userinfo
User_DN: CN=mwedward,OU=Campus Accounts,DC=some_ad,DC=some_school,DC=edu
user_info_result= Resource id #44
Key= Michael Value= givenname
Key= Edwards Value= sn
Key= mwedward@some_school.edu Value= mail
Key= mwedward Value= sAMAccountName
Before LDAP_Close
LDAP_Connection= Resource id #40
After LDAP_Close
Username= mwedward
AUTH= ldap
Username=mwedward in LDAP Auth_get_userinfo
User_DN: CN=mwedward,OU=Campus Accounts,DC=some_ad,DC=some_school,DC=edu
user_info_result= Resource id #43
Key= Michael Value= givenname
Key= Edwards Value= sn
Key= mwedward@some_school.edu Value= mail
Key= mwedward Value= sAMAccountName
Before LDAP_Close
LDAP_Connection= Resource id #39
After LDAP_Close
Why is it different when comming from an account creation path vs a straight authentication path?
Mike
Re: Populating Moodle users via Active Directory (LDAP)
Not surewhy it is different. What happens if you comment out the ldap_disconnect?
Re: Populating Moodle users via Active Directory (LDAP)
time passes and I scratch my head.
Ok I know what made it work. I added a single line to dump out info to my logfile from the create_user_record function. I added a line to print the $newinfo variable out to a file before the IF statement where it is set to auth_get_userinfo($username). For some reason if I pull out everything else I have and add this line in it still won't work.
This is totally random. I'm attaching a diff -u from the stock 1.5.2 moodlelib.php and my modified one.
Mike
Re: Populating Moodle users via Active Directory (LDAP)
You can change that limit as shown here: http://www.openldap.org/lists/openldap-software/200206/msg00627.html
Or you could change the script to use paged results -see RFC 2696- (I think you need openldap 2.2.* client libraries for this at least).
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
I did look into paged results a while ago, mainly to avoid swamping memory, but the PHP LDAP libraries don't support them
Re: Populating Moodle users via Active Directory (LDAP)
I have just looked at it (3+ hours of googling and source code reading of both openldap libraries and PHP-4.3.10 ldap extension ;) and it seems it should be posible (theoretically from PHP 4.0.3 onwards, provided local openldap libraries support it, which you can get for sure from 2.2.5 onwards -even my Debian Sarge's 2.1.30 version has support for it- ).
The trick seems to be using PHP's ldap_set_option() with option LDAP_OPT_SERVER_CONTROLS, using the Paged Results OID (1.2.840.113556.1.4.319) and the desired number of results as the value (using code similar to example 2 at http://www.php.net/manual/en/function.ldap-set-option.php).
I haven't tested anything of this at all, but the code reading suggests it should work.
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
Paged results require that we have both $offset and $limit, so we can fetch the 1st page, and then the 2nd, 3rd, etc.
Is there an ldap_set_option() option to say 'offset 100'? Can you post a code snippet that grabs 100 entries at a time, and can go like that through a large resultset?
Re: Populating Moodle users via Active Directory (LDAP)
The way paged results work is described in RFC 2696. You send a special control request along with your query. That control request includes a "size" (an integer value) and a "cookie" (an octect string, initially sent empty from the client). This is what we set using ldap_set_option() with LDAP_OPT_SERVER_CONTROLS.
The server includes a similar control request in its reply with the cookie value filled in. You have to read the cookie to see if it's empty or not. If the cookie is empty, there are no more results (this was the last page of results). Otherwise, you process these results and send the exact same query with the cookie value you have just read (using a control request just like the first time). The server uses the cookie to track "continuation requests", and sends you the next page of results instead of the first ones.
The problem is we can't get the value of the cookie (or at least I can't see how, since ldap_get_option() doesn't support getting LDAP_OPT_SERVER_CONTROLS neither in PHP 4.3.11 nor 5.0.4).
So I think we are back to square number 1...
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
> can't see how, since ldap_get_option() doesn't support getting
> LDAP_OPT_SERVER_CONTROLS neither in PHP 4.3.11 nor 5.0.4).
I have digged a little bit more (even if I sound like an LDAP programming expert, this is the first time I do LDAP programming at all , and ldap_get_option() is not the way to go. The (response) control requests the server sends back are not stored in our LDAP_OPT_SERVER_CONTROLS data structure (i.e., they don't update it).
These control requests are attached to the responses the server sends back. So we really need to get them from the result response. There is a LDAP function called ldap_parse_result() [see RFC 1823] that does exactly this. PHP's ldap_parse_result() is a wrapper around this function but, I don't know why, the last parameter (serverctrlsp, ¡which is what we really need!) is not available via PHP.
So I have developped and tested a small patch (attached to this message) against PHP 4.3.10 (Debian Sarge's version) LDAP module that provides this value (serversctls) as an array of controls, using the same format we need to use PHP's ldap_set_option() later. If anyone wants to try it out, I can make patches against newer versions of PHP.
But this isn't the end of the story. These controls extensions (Paged Results) have to specify the page size and cookie value as a BER-encoded ASN.1 sequence (the elements of the sequence are described in RFC2696 and ASN.1 BER encondig is described in ITU's X.690 standard). So all we need is a couple of functions to BER-encode/decode those values to/from the control extensions (ideally PHP people should make wrapper functions around OpenLDAP libraries' existing routines).
In order to test my patch (without having to code the BER functions), I have hand-coded the values, looked at what I get back in the control extension from a W2003 AD server, "patched" the returned control extension (to reuse all the BER encoding, without having to do it again manually) and sent the query again with the patched control extension. Guess what? I just works
This is the php code I have used to test it all this (really a pice of crap, specially crafted for W2003 AD responses, but I can't do it better without the BER functions):
<?phpSo you "only" have to:
define ('PAGE_SIZE', 100);
define ('LDAP_HOST', 'somehost.mydomain.com');
define ('LDAP_USER', 'CN=bind-user,OU=my-users,DC=mydomain,DC=com');
define ('LDAP_PWD', 'bind-user-password');
// CAUTION:
//
// All error checking skipped. This is just sample code
// specially crafted for W2000/W2003 A.D. servers
//
$l = ldap_connect(LDAP_HOST);
$PageControl = array(
array(
'oid' => '1.2.840.113556.1.4.319',
'iscritical' => true,
'value' => sprintf ("%c%c%c%c%c%c%c",
48, 5, 2, 1, PAGE_SIZE, 4, 0)
)
);
ldap_set_option($l,LDAP_OPT_PROTOCOL_VERSION,3);
ldap_bind($l,LDAP_USER,LDAP_PWD);
$continue = true;
while ($continue) {
ldap_set_option($l, LDAP_OPT_SERVER_CONTROLS,$PageControl);
$sr = ldap_search($l, 'OU=some-ou,DC=mydomain,DC=com',
'cn=*', array('sAMAccountName'),
null, null, null, null);
ldap_parse_result ($l, $sr, &$errcode, &$matcheddn,
&$errmsg, &$referrals, &$serverctrls);
if (isset($serverctrls)) {
foreach ($serverctrls as $i) {
if ($i["oid"] == '1.2.840.113556.1.4.319') {
$i["value"]{8} = chr(PAGE_SIZE);
$i["iscritical"] = true;
$PageControl = array();
$PageControl[] = $i;
break;
}
}
}
$info = ldap_get_entries($l, $sr);
print "Data for " . $info["count"] . " items returned:\n";
if ($info["count"] < PAGE_SIZE) {
$continue = false;
}
for ($entry = ldap_first_entry ($l,$sr); $entry != false;
$entry = ldap_next_entry($l,$entry)) {
$dn = ldap_get_dn ($l,$entry);
print "$dn \n";
}
print "\n---------------------------------------\n";
}
?>
- apply my patch and rebuild PHP's ldap module and
- code the BER-encode/decode functions.
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
this is really cool stuff! Molt bon treball! I suspect the PHP guys will be interested in your patch. One catch though: most of the LDAP data passed around is BER-encoded - and the OpenLDAP client libraries include it. Check out 'man lber-decode'.
No need to do it on the PHP side
Re: Populating Moodle users via Active Directory (LDAP)
I have opened a bug in PHP (http://bugs.php.net/bug.php?id=34492) with the patch. In case anyone is interested in this going into PHP anytime soon, they better vote for it
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
Do you think you can get it to do the BER-decoding before the data gets to the PHP layer? If so, I'll be more than happy to build Debian packages for Sarge (i368, amd64, powerpc) of the modified PHP so we can start using it...
Saludos!
Re: Populating Moodle users via Active Directory (LDAP)
I think we should do all the BER encoding/decoding at the PHP layer. Given that there are several control extensions available [1] and that every control extension uses a different set of values in the controlValue field [2], doing this below the PHP layer (inside the LDAP module) means we need to know in advance which control extensions we are going to "recognize" in order to decode the right set of values.
On the other hand, if we do this at the PHP layer, the application knows exactly what it expects and the format of the associated data. It can ignore the control extensions it doesn't "recognize" (or isn't interested in), and just proccess the "interesting" ones.
I think we only need to make wrappers around ber_print and ber_scanf (and hide all the dirty details -like BER elements allocation and such- inside the ldap module) to make this happen at the PHP layer.
Saludos. Iñaki.
[1] There are several available now, but nothing in the standard prevents the creation of new ones.
[2] In fact RFC 2251, section 4.1.12, says this field's format (number of values and their type) is defined by each control extension.
Re: Populating Moodle users via Active Directory (LDAP)
Re: Populating Moodle users via Active Directory (LDAP)
Re: Populating Moodle users via Active Directory (LDAP)
But I have been able to work a little bit with the code and here is attached a patch against Debian Sarges PHP4 package (php4-4.3.10). It's beta quality code so don't relay on it for production by now
I have made several successful tests against my W2K3 AD with different page sizes, but haven't had the chance to test it with an OpenLDAP server yet.
I think there are no obvious bugs in the code (i.e., pointers going astray, memory leaks, etc.) but this is the first time I use the Zend API, so I could be missing something.
Finally the documentation for the two new functions [ldap_ber_printf(), ldap_ber_scanf()] is missing, but it should be fairly obvious how to use them looking at the ber_scanf()/ber_printf() manpages.
There are a couple of encondings that are not supported so far ('v' and 'V') and two OpenLDAP extensions to the standard draft that are not implemented. The reason is a) we don't need it for paged results at all, b) they are a little bit trickier than the rest, and I wanted to push the code out ASAP for you to test it.
Here is sample PHP code to use the new functions to get paged results working:
=========================================================
Any feedback is welcome.
<?php
define ('PAGE_SIZE', 500);
define ('LDAP_HOST', 'myserver.mydomain.com' );
define ('LDAP_USER', 'cn=myuser,ou=myusers,dc=mydomain,dc=com' );
define ('LDAP_PWD', 'mypassword' );
$cookie = "";
$l = ldap_connect(LDAP_HOST);
ldap_set_option($l,LDAP_OPT_PROTOCOL_VERSION,3);
ldap_bind($l,LDAP_USER,LDAP_PWD);
$continue = true;
while ($continue) {
$PageControl = array(
array(
'oid' =>'1.2.840.113556.1.4.319',
'iscritical' => true,
'value' => ldap_ber_printf("{iO}",
PAGE_SIZE, $cookie)
)
);
ldap_set_option($l, LDAP_OPT_SERVER_CONTROLS, $PageControl);
$sr = ldap_search($l,'ou=myusers,dc=mydomain,dc=com',
'cn=*', array('sAMAccountName'),
null, null, null, null);
ldap_parse_result ($l, $sr, &$errcode, &$matcheddn,
&$errmsg, &$referrals, &$serverctrls);
if (isset($serverctrls)) {
foreach ($serverctrls as $i) {
if ($i["oid"] == '1.2.840.113556.1.4.319') {
ldap_ber_scanf($i["value"], "{iO}",
&$pagesize, &$cookie);
break;
}
}
}
$info = ldap_get_entries($l, $sr);
print "Data for " . $info["count"] . " items returned:\n";
for ($entry = ldap_first_entry ($l,$sr); $entry != false;
$entry = ldap_next_entry($l,$entry)) {
$dn = ldap_get_dn ($l,$entry);
print "$dn \n";
}
print "\n---------------------------------------\n\n";
if ($cookie == "") {
$continue = false;
}
}
?>
=========================================================
Saludos. Iñaki.
Re: Populating Moodle users via Active Directory (LDAP)
http://www.eteo.mondragon.edu/descargas/php-ldap/
in case anybody is interested in testing it.
Saludos. Iñaki.
I am running moodle 1.9.6 on php 5.2.12
As regards user synchronisation, I am experiencing the AD limit problem when running ..../moodle/auth/ldap/auth_ldap_sync_users.php.
As per the notes above, I tried the following
- updated php 5.2.12 with the path from the web site above and
- changed ..../moodle/auth/ldap/auth.php based upon the PHP paging code above, which uses ldap_ber_printf().
This did not work so I tried the following
- changed ..../moodle/auth/ldap/auth.php based upon the older PHP paging code above, which does not uses ldap_ber_printf().
This has more success in that it successfully retrieves more than 1000 entries from AD if required by retrieving a 1000 at a time, in each page.
However, later in the processing, it will connect to AD again to get more detailed information on each entry and fails to get information including the username. I am guessing the paging code that has been inserted into auth.php has an adverse ripple effect on the retrieval of the detailed information.
The changed auth.php is attached.
Log
Connecting to ldap...
+ 1000 users
entry_count >1000<
+ 1000 users
entry_count >1000<
+ 231 users
entry_count >231<
Got 2231 records from LDAP
No updates to be done
No updates to be done
User entries to add: 2231
Notice: Undefined property: stdClass::$username in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 913
Notice: Undefined property: stdClass::$username in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 914
Inserted user id 30964
Notice: Trying to get property of non-object in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 904
Notice: Trying to get property of non-object in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 904
Notice: Undefined property: stdClass::$username in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 913
Notice: Undefined property: stdClass::$username in /wwwroot/htdocs/moodle/auth/ldap/auth.php on line 914
<div class="notifytiny" style="text-align:center">Duplicate entry '1-' for key 2<br /><br />INSERT INTO mdl_user ( AUTH, CONFIRMED, MNETHOSTID, USERNAME, LANG ) VALUES ( 'ldap', 1, 1, '', 'en_utf8' )<ul style="text-align:left"><li>line 1554 of lib/dmllib.php: call to debugging()</li><li>line 922 of auth/ldap/auth.php: call to insert_record()</li><li>line 44 of auth/ldap/auth_ldap_sync_users.php: call to auth_plugin_ldap->sync_users()</li></ul></div>
Error inserting user
Any help - much appreciated? Does anyone have an auth.php which successfully retrieves more than 1000 entries from AD and inserts them into mySQL?
Thanks
Cheers,
Muyi
I am trying to do a similar thing, but I am running into slightly different problem. Your code has so far made it so I can page query an OU for its DNs, but with ActiveDirectory, at least the way we have it, the members of a group are listed as a multi-attribute called member in the group object. Wow - that was clear, anyway, I can do an ldapsearch on a debian box, that queries a W2K3 DC, that looks sort of like this -
ldapsearch -x -b ou=mygroups,ou=myusers,dc=mydomain,dc=edu -D "cn=myquery-acct,ou=myusers,dc=mydomain,dc=edu" -w mypassword -h w2k3-pdc.mydomain.edu "(&(objectCategory=group)(name=students))" member
and it spits out the first page of a query:
--------------------------------------------------------------------------------------------
# extended LDIF
#
# LDAPv3
# base <ou=mygroups,ou=myusers,dc=alfredstate,dc=edu> with scope sub
# filter: (&(objectCategory=group)(name=mystudents))
# requesting: member
#
# student, mygroups, myusers, mydomain.edu
dn: CN=mystudents,OU=mygroups,OU=myusers,DC=mydomain,DC=edu
member;range=0-1499: CN=Riviera Doris D,OU=mystudents,OU=myusers,DC=mydomain,DC=edu
member;range=0-1499: CN=Rhudel Willhem K,OU=mystudents,OU=myusers,DC=mydomain,DC=edu
.member;range=0-1499: CN=Rundel Alan J,OU=mystudents,OU=myusers,DC=mydomain,DC=edu
...
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
--------------------------------------------------------------------------------------------
the dn being searched is dn: CN=mystudents,OU=mygroups,OU=myusers,DC=mydomain,DC=edu
and the multi-attribute is member. So, using your code, I change the line from
$sr = ldap_search($l,'ou=mygroups,ou=myusers,dc=mydomain,dc=edu',
'cn=*', array('sAMAccountName'),
null, null, null, null);
to:
$sr = ldap_search($l,'ou=mygroups,ou=myusers,dc=mydomain,dc=edu',
'(&(objectCategory=group) (name=mystudents))', array('member'),
null, null, null, null);
and the webpage puts out:
Data for 1 item returned: CN=mystudents,OU=mygroups,OU=myusers,DC=mydomain,DC=edu
--------------------------------------------------
I would have expected a bunch of member DNs instead. Do you know how
I would search for the multi-attribute using your code? (Some special LBER or something?) Thanks for any ideas!
Dale B
Sorry for the delay
In fact, what you are getting is exactly what you asked for: the entry for the group. As you rightly say, members are attributes of that entry. So once you get the entry from LDAP, you just need to process that attribute. You can use ldap_get_values() to extract the values of a given attribute. Just add something like this after the line that prints the $dn:
echo "<br />";
$members = ldap_get_values ($l, $entry, 'member');
for ($i = 0; $i < $members['count']; $i++) {
echo "member: " . $members[$i] . "<br />";
}
Saludos. Iñaki.
That's some good info though. I might convert the script to something else and see if I can get it to work...
Mike
Re: Populating Moodle users via Active Directory (LDAP)
Re: Populating Moodle users via Active Directory (LDAP)
> log in"? Is this possible?
This is better done _outside_ of Moodle, by a separate script (of the kind of accessory scripts we put in cvs:/contrib ) . I'll probably put together a Perl (or PHP) script that reads mdl_user and outputs an LDIF that you can feed to AD or OpenLDAP.
Actually, it's likely to happen before the end of the year, I have some Moodle setups that need an LDAP backend.
Re: Populating Moodle users via Active Directory (LDAP)
We are populating the AD with separate scripts from our student management systems, but the users are seen in Moodle only after they have logged in once.
We'd like to be able to put them on courses et cetera even before they have logged in Moodle. Now I did some small bits of code to convert LDIF to a CSV file that Moodle eats, and pre-filled the accounts that way. Running in to the size limit also, though, so I'm running a lot of LDIF files out of the AD.
Re: Populating Moodle users via Active Directory (LDAP)
> before the user logs in Moodle".
That's already possible. There is a script inside auth/ldap and another inside enrol/ldap. We run both of them around midnight. No messing around w CSV files.
Re: Populating Moodle users via Active Directory (LDAP)
You use an LDAP with no (or larger) max query size than 1000? We have AD and some contexts with more than 1000 users so something else still must be hacked in. Maybe some other parameter to iterate to than the contexts, to break it to pieces smaller than 1000...
Re: Populating Moodle users via Active Directory (LDAP)
I've been trying to get the auth_ldap_sync_users.php script to work and every time I try and run it in a browser I get a blank screen. I saw something saying that it should be run from a command line but it doesn't seem to do anything. I'm running moodle on server 2k3 with php5. Does anyone know what the correct command line sytax is to run auth_ldap_sync_users.php? Thanks.
How can I limit this access to members of a central group? Like; cn=moodleAccounts,ou=myOU,dc=some_ad,dc=some_school,dc=edu
If I understand your question correctly, it is easy. In the Moodle >> Administration >> Users >> Authentication section, only add those groups to the
ldap_contexts section. The authentication module should then only look in those OU's for users, and if it doesn't find them, it will not authenticate, and not create a Moodle account.
Is that what you are asking?