On Friday, 2 December 2011 01:40:22 Nick Urbanik wrote: > Dear Chris, > > On 01/12/11 07:12 +0000, Chris Ridd wrote: > >On 30 Nov 2011, at 22:21, Nick Urbanik wrote: > >> Dear Folks, > >> > >> I am writing Perl software to manage our OpenLDAP cluster using the > >> slapo.ppolicy overlay to manage password policy. I'm hoping to get > >> this into production very soon.
I haven't had time to do all I wanted with this, but I developed a simple Catalyst app for managing users for an open-source project, covering registration, some user self-service, some minimal user/group management. I wanted to add some features regarding password expiry, locked out users etc., but the password change / reset feature does use/support ppolicy. I should probably also revert some of the theming changes and host it in a more neutral place, but you can find it here: http://svnweb.mageia.org/soft/identity/CatDap/trunk/ The 'stable' instance runs here: https://identity.mageia.org/ I can probably hook up a demo one somewhere if you don't want to register on this one to see what it looks like after login ... I hope to be able to spend some time on it this month to address needs of my $dayjob. There is a long list of issues that still need to be addressed: https://wiki.mageia.org/en/Web_Identity but it is useful in its current form, and I did most development just to get us to the point where we could centralise all user accessible apps (such as the wiki where this page is) in one place. I would be interested to know what you are doing/using and whether it is open source or not. > >> > >> My problem: how to efficiently search for users who have expired? > >> > >> I have seen the code written by Buchan Milne at > >> http://staff.telkomsa.net/~bgmilne/ldap/, but he is munging with the > >> operational attributes directly. That statement is a bit unfair. The script to search for users whose passwords have expired does search on the ppolicy attributes directly (there seems to be no other way), but the password changing script available from the same location uses only Net::LDAP::Control::PasswordPolicy (and doesn't rely on any specific attributes). At present I don't believe there is any way to use the ppolicy overlay to list users with expired passwords. I believe there was talk of having a compare operation with PasswordPolicy control attached be useful for determining if a specific user's password had expired, but this would still require one operation per user, whereas one search per policy is sufficient if we use the ppolicy attributes. > >> The code I've written so far has > >> mostly avoided working with these directly; I've been using > >> Net::LDAP::Control::PasswordPolicy so far, and am trying to understand > >> how to construct a suitable filter using this control. > >> > >> Can anyone point me in the right direction? > > > >The PP control is very much meant for telling a user about *his* > >password status. If you combined it with proxy auth, you could > >imagine writing something that iterated through every user, and did > >some kind of operation (something simple like whoami perhaps) > >proxying as that user and requesting PP status back. > > > >That may give you what you want. It may even work! :-) > > Well, I have thousands of users, and simply want to create a filter > that will fetch the unexpired users who match various other criteria. > > >I suspect Buchan's approach is better as it doesn't involve iterating > >through each user and the attributes it is using are defined in the > >same draft as the PP control, i.e. reasonably standardised. > > The problem is that I cannot construct one filter that works for > everyone, because I have multiple policies; I have code like this: > > sub _read_ppolicy_times { > my ( $self ) = @_; > my $ldap = _ldapopen $self or return; > my $search = _search $self, $ldap, $POLICY_DN, > '(objectclass=pwdPolicy)', 'sub', [ qw( cn pwdMaxAge pwdExpireWarning > pwdGraceAuthnLimit ) ] or return; > if ( $search->code() or $search->count() == 0 ) { > _log_trans_fail $self, $ERR_LDAP_SEARCH_FAIL, 'cannot find ANY > ppolicy'; return; > } > my %expires; > foreach my $entry ( $search->entries() ) { > my $pwd_max_age = $entry->get_value( 'pwdMaxAge' ) or next; > > $expires{ $entry->get_value( 'cn' ) } > = strftime '%Y%m%d%H%M%SZ', gmtime( time - $pwd_max_age ); > } > return \%expires; > } > > And then I conduct a search, You don't show what search you are doing, but you should construct a search filter based on the current date, the expiry/warn time of your policy. This is what my code does (granted, a lot of the code in this script is ugly, but I think the approach is correct). It has something like this: mesg = $ldap->search(base => $config{'basedn'}, filter => '(objectclass=pwdPolicy)', attrs => ['cn','pwdMaxAge','pwdExpireWarning','pwdGraceAuthnLimit'], ); $mesg->code && carp $mesg->error; foreach my $entry ($mesg->entries) [...] if ( $policydn eq $config{'defaultpolicy'} ) { $filter = "(&(|(!(pwdPolicySubEntry=*)) (pwdPolicySubEntry=$config{'defaultpolicy'}))(pwdChangedTime<=$filterwarn) (pwdChangedTime>=$filterexpire))"; } else { $filter = "(&(pwdPolicySubEntry=$policydn) (pwdChangedTime<=$filterwarn)(pwdChangedTime>=$filterexpire))" } $mesg = $ldap->search(base => $config{'basedn'}, filter => $filter, attrs => ['cn', 'uid', 'mail', 'pwdChangedTime', 'pwdGraceUseTime'], ); > and loop through the entries removing > those that are expired, like this: > > foreach my $entry ( $search->entries() ) { > my $ppolicy = _dns_to_cns $entry->get_value( 'pwdPolicySubentry' > ); my $last_changed = $entry->get_value( 'pwdChangedTime' ); > > if ( exists $expires->{$ppolicy} ) { > next if $no_expired and $expires->{$ppolicy} > $last_changed; > } > else { > $self->log( "Cannot find '$ppolicy' in " . Dumper( $expires ) > ) } > push @entries, $entry; > } > > This is ugly, especially when I want to have other code to fetch only > users who *have* expired. Do I really need to do this sort of thing? > The server knows which ones have expired; Not really. It has an overlay that intercepts specific operations (bind, passmod, modify) and evaluates the password policy only for these operations. It doesn't maintain a list of which DNs have expired or similar. > it would be great if I had a > way of asking it to filter them out for me. Ah, but you can to some degree ... although you do need to search once per policy (most of my environments have two to 5 policies). Regards, Buchan