X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FAuthentication%2FStore%2FLDAP%2FBackend.pm;h=bd2276e3365e9ab41859c3184e6a4d445aea19e9;hb=71e3a4f6f35a40145bc66918b912f0661d27cffc;hp=8cca1d3996d4b3aa2f44464c16f217c5df38bd2a;hpb=bc77ff45fe40b52d43d7c2c3a5c80b061efdfbbb;p=catagits%2FCatalyst-Authentication-Store-LDAP.git diff --git a/lib/Catalyst/Authentication/Store/LDAP/Backend.pm b/lib/Catalyst/Authentication/Store/LDAP/Backend.pm index 8cca1d3..bd2276e 100644 --- a/lib/Catalyst/Authentication/Store/LDAP/Backend.pm +++ b/lib/Catalyst/Authentication/Store/LDAP/Backend.pm @@ -3,7 +3,7 @@ =head1 NAME -Catalyst::Authentication::Store::LDAP::Backend +Catalyst::Authentication::Store::LDAP::Backend - LDAP authentication storage backend. =head1 SYNOPSIS @@ -31,10 +31,11 @@ Catalyst::Authentication::Store::LDAP::Backend }, 'user_basedn' => 'ou=people,dc=yourcompany,dc=com', 'user_filter' => '(&(objectClass=posixAccount)(uid=%s))', - 'user_scope' => 'one', + 'user_scope' => 'one', # or 'sub' for Active Directory 'user_field' => 'uid', 'user_search_options' => { 'deref' => 'always', + 'attrs' => [qw( distinguishedname name mail )], }, 'user_results_filter' => sub { return shift->pop_entry }, 'entry_class' => 'MyApp::LDAP::Entry', @@ -49,8 +50,9 @@ Catalyst::Authentication::Store::LDAP::Backend 'deref' => 'always', }, 'role_search_as_user' => 0, + 'persist_in_session' => 'all', ); - + our $users = Catalyst::Authentication::Store::LDAP::Backend->new(\%config); =head1 DESCRIPTION @@ -72,11 +74,12 @@ use base qw( Class::Accessor::Fast ); use strict; use warnings; -our $VERSION = '1.010'; +our $VERSION = '1.016'; use Catalyst::Authentication::Store::LDAP::User; use Net::LDAP; use Catalyst::Utils (); +use Catalyst::Exception; BEGIN { __PACKAGE__->mk_accessors( @@ -87,6 +90,7 @@ BEGIN { role_filter role_scope role_field role_value role_search_options start_tls start_tls_options user_results_filter user_class role_search_as_user + persist_in_session ) ); } @@ -117,13 +121,18 @@ sub new { $config_hash{'role_filter'} ||= '(memberUid=%s)'; $config_hash{'role_scope'} ||= 'sub'; $config_hash{'role_field'} ||= 'cn'; - $config_hash{'use_roles'} ||= '1'; + $config_hash{'use_roles'} = '1' + unless exists $config_hash{use_roles}; $config_hash{'start_tls'} ||= '0'; $config_hash{'entry_class'} ||= 'Catalyst::Model::LDAP::Entry'; - $config_hash{'user_class'} ||= 'Catalyst::Authentication::Store::LDAP::User'; + $config_hash{'user_class'} + ||= 'Catalyst::Authentication::Store::LDAP::User'; $config_hash{'role_search_as_user'} ||= 0; + $config_hash{'persist_in_session'} ||= 'username'; + Catalyst::Exception->throw('persist_in_session must be either username or all') + unless $config_hash{'persist_in_session'} =~ /\A(?:username|all)\z/; - Catalyst::Utils::ensure_class_loaded($config_hash{'user_class'}); + Catalyst::Utils::ensure_class_loaded( $config_hash{'user_class'} ); my $self = \%config_hash; bless( $self, $class ); return $self; @@ -132,7 +141,7 @@ sub new { =head2 find_user( I, $c ) Creates a L object -for the given User ID. This is the preferred mechanism for getting a +for the given User ID. This is the preferred mechanism for getting a given User out of the Store. I should be a hashref with a key of either C or @@ -157,8 +166,7 @@ This is the preferred mechanism for getting a given User out of the Store. sub get_user { my ( $self, $id, $c ) = @_; - my $user = $self->user_class->new( $self, - $self->lookup_user($id), $c ); + my $user = $self->user_class->new( $self, $self->lookup_user($id), $c ); return $user; } @@ -208,14 +216,19 @@ If $binddn is "anonymous", an anonymous bind will be performed. =cut sub ldap_bind { - my ( $self, $ldap, $binddn, $bindpw, $forauth ) = @_; - $forauth ||= 0; - $ldap ||= $self->ldap_connect; + my ( $self, $ldap, $binddn, $bindpw ) = @_; + $ldap ||= $self->ldap_connect; if ( !defined($ldap) ) { Catalyst::Exception->throw("LDAP Server undefined!"); } - $binddn ||= $self->binddn; - $bindpw ||= $self->bindpw; + + # if username is present, make sure password is present too. + # see https://rt.cpan.org/Ticket/Display.html?id=81908 + if ( !defined $binddn ) { + $binddn = $self->binddn; + $bindpw = $self->bindpw; + } + if ( $binddn eq "anonymous" ) { $self->_ldap_bind_anon($ldap); } @@ -223,33 +236,43 @@ sub ldap_bind { if ($bindpw) { my $mesg = $ldap->bind( $binddn, 'password' => $bindpw ); if ( $mesg->is_error ) { - - # If we're not checking this bind for authentication purposes - # Go ahead an blow up if we fail. - if ( $forauth ne 'forauth' ) { - Catalyst::Exception->throw( - "Error on Initial Bind: " . $mesg->error ); - } - else { - return undef; - } + Catalyst::Exception->throw( + "Error on Initial Bind: " . $mesg->error ); } } else { - $self->_ldap_bind_anon($ldap, $binddn); + $self->_ldap_bind_anon( $ldap, $binddn ); } } return $ldap; } sub _ldap_bind_anon { - my ($self, $ldap, $dn) = @_; + my ( $self, $ldap, $dn ) = @_; my $mesg = $ldap->bind($dn); if ( $mesg->is_error ) { Catalyst::Exception->throw( "Error on Bind: " . $mesg->error ); } } +=head2 ldap_auth( $binddn, $bindpw ) + +Connect to the LDAP server and do an authenticated bind against the +directory. Throws an exception if connecting to the LDAP server fails. +Returns 1 if binding succeeds, 0 if it fails. + +=cut + +sub ldap_auth { + my ( $self, $binddn, $bindpw ) = @_; + my $ldap = $self->ldap_connect; + if ( !defined $ldap ) { + Catalyst::Exception->throw("LDAP server undefined!"); + } + my $mesg = $ldap->bind( $binddn, password => $bindpw ); + return $mesg->is_error ? 0 : 1; +} + =head2 lookup_user($id) Given a User ID, this method will: @@ -257,7 +280,7 @@ Given a User ID, this method will: A) Bind to the directory using the configured binddn and bindpw B) Perform a search for the User Object in the directory, using user_basedn, user_filter, and user_scope. - C) Assuming we found the object, we will walk it's attributes + C) Assuming we found the object, we will walk its attributes using L's get_value method. We store the results in a hashref. If we do not find the object, then undef is returned. @@ -275,10 +298,6 @@ This method is usually only called by find_user(). sub lookup_user { my ( $self, $id ) = @_; - # No sneaking in wildcards! - if ( $id =~ /\*/ ) { - Catalyst::Exception->throw("ID $id contains wildcards!"); - } # Trim trailing space or we confuse ourselves $id =~ s/\s+$//; my $ldap = $self->ldap_bind; @@ -345,8 +364,8 @@ sub lookup_user { $attrhash->{ lc($attr) } = \@attrvalues; } } - - eval { Catalyst::Utils::ensure_class_loaded($self->entry_class) }; + + eval { Catalyst::Utils::ensure_class_loaded( $self->entry_class ) }; if ( !$@ ) { bless( $userentry, $self->entry_class ); $userentry->{_use_unicode}++; @@ -360,22 +379,23 @@ sub lookup_user { =head2 lookup_roles($userobj, [$ldap]) -This method looks up the roles for a given user. It takes a +This method looks up the roles for a given user. It takes a L object -as it's first argument, and can optionally take a I object which +as its first argument, and can optionally take a I object which is used rather than the default binding if supplied. It returns an array containing the role_field attribute from all the -objects that match it's criteria. +objects that match its criteria. =cut sub lookup_roles { my ( $self, $userobj, $ldap ) = @_; if ( $self->use_roles == 0 || $self->use_roles =~ /^false$/i ) { - return undef; + return (); } - $ldap ||= $self->ldap_bind; + $ldap ||= $self->role_search_as_user + ? $userobj->ldap_connection : $self->ldap_bind; my @searchopts; if ( defined( $self->role_basedn ) ) { push( @searchopts, 'base' => $self->role_basedn ); @@ -390,7 +410,7 @@ sub lookup_roles { . $userobj->username . " has no " . $self->role_value - . " attribute, so I can't look up it's roles!" ); + . " attribute, so I can't look up its roles!" ); } my $filter = $self->_replace_filter( $self->role_filter, $filter_value ); push( @searchopts, 'filter' => $filter ); @@ -411,13 +431,14 @@ sub _replace_filter { my $self = shift; my $filter = shift; my $replace = shift; + $replace =~ s/([*()\\\x{0}])/sprintf '\\%02x', ord($1)/ge; $filter =~ s/\%s/$replace/g; return $filter; } =head2 user_supports -Returns the value of +Returns the value of Catalyst::Authentication::Store::LDAP::User->supports(@_). =cut @@ -429,15 +450,28 @@ sub user_supports { Catalyst::Authentication::Store::LDAP::User->supports(@_); } -=head2 from_session( I, I<$c> ) +=head2 from_session( I, I<$c>, $frozenuser ) -Returns get_user() for I. +Revives a serialized user from storage in the session. + +Supports users stored with a different persist_in_session setting. =cut sub from_session { - my ( $self, $c, $id ) = @_; - $self->get_user($id, $c); + my ( $self, $c, $frozenuser ) = @_; + + # we need to restore the user depending on the current storage of the + # user in the session store which might differ from what + # persist_in_session is set to now + if ( ref $frozenuser eq 'HASH' ) { + # we can rely on the existance of this key if the user is a hashref + if ( $frozenuser->{persist_in_session} eq 'all' ) { + return $self->user_class->new( $self, $frozenuser->{user}, $c, $frozenuser->{_roles} ); + } + } + + return $self->get_user( $frozenuser, $c ); } 1;