File Coverage

blib/lib/Catalyst/Authentication/Store/LDAP/User.pm
Criterion Covered Total %
statement 40 73 54.7
branch 10 24 41.6
condition 1 5 20.0
subroutine 14 24 58.3
pod 13 14 92.8
total 78 140 55.7


line stmt bran cond sub pod time code
1              
2             =pod
3              
4             =head1 NAME
5              
6             Catalyst::Authentication::Store::LDAP::User
7             - A User object representing an LDAP object.
8              
9             =head1 SYNOPSIS
10              
11             You should be creating these objects through L<Catalyst::Authentication::Store::LDAP::Backend>'s "get_user" method, or just letting $c->authenticate do
12             it for you.
13              
14             sub action : Local {
15             my ( $self, $c ) = @_;
16             $c->authenticate({
17             id => $c->req->param(username),
18             password => $c->req->param(password)
19             );
20             $c->log->debug($c->user->username . "is really neat!");
21             }
22              
23             If you access just $c->user in a scalar context, it will return the current
24             username.
25              
26             =head1 DESCRIPTION
27              
28             This wraps up an LDAP object and presents a simplified interface to it's
29             contents. It uses some AUTOLOAD magic to pass method calls it doesn't
30             understand through as simple read only accessors for the LDAP entries
31             various attributes.
32              
33             It gets grumpy if you ask for an attribute via the AUTOLOAD mechanism
34             that it doesn't know about. Avoid that with using "has_attribute",
35             discussed in more detail below.
36              
37             You can skip all that and just go straight to the L<Net::LDAP::Entry>
38             object through the "ldap_entry" method:
39              
40             my $entry = $c->user->ldap_entry;
41              
42             It also has support for Roles.
43              
44             =cut
45              
46             package Catalyst::Authentication::Store::LDAP::User;
47 4     4   22625 use base qw( Catalyst::Authentication::User Class::Accessor::Fast );
  4         8  
  4         2541  
48              
49 4     4   718166 use strict;
  4         10  
  4         144  
50 4     4   19 use warnings;
  4         7  
  4         144  
51 4     4   20 use Scalar::Util qw/refaddr/;
  4         5  
  4         411  
52              
53             our $VERSION = '1.015';
54              
55 4     4   43 BEGIN { __PACKAGE__->mk_accessors(qw/user store/) }
56              
57 4     4   18501 use overload '""' => sub { shift->stringify }, fallback => 1;
  4     1   9  
  4         57  
  1         180  
58              
59             my %_ldap_connection_passwords; # Store inside-out so that they don't show up
60             # in dumps..
61              
62             =head1 METHODS
63              
64             =head2 new($store, $user, $c)
65              
66             Takes a L<Catalyst::Authentication::Store::LDAP::Backend> object
67             as $store, and the data structure returned by that class's "get_user"
68             method as $user. The final argument is an instance of your application,
69             which is passed along for those wanting to subclass User and perhaps use
70             models for fetching data.
71              
72             Returns a L<Catalyst::Authentication::Store::LDAP::User> object.
73              
74             =cut
75              
76             sub new {
77 4     4 1 173 my ( $class, $store, $user, $c ) = @_;
78              
79 4 50       18 return unless $user;
80              
81 4         32 bless { store => $store, user => $user, }, $class;
82             }
83              
84             =head2 id
85              
86             Returns the results of the "stringify" method.
87              
88             =cut
89              
90             sub id {
91 0     0 1 0 my $self = shift;
92 0         0 return $self->stringify;
93             }
94              
95             =head2 stringify
96              
97             Uses the "user_field" configuration option to determine what the "username"
98             of this object is, and returns it.
99              
100             If you use the special value "dn" for user_field, it will return the DN
101             of the L<Net::LDAP::Entry> object.
102              
103             =cut
104              
105             sub stringify {
106 1     1 1 3 my ($self) = @_;
107 1         10 my $userfield = $self->store->user_field;
108 1 50       545 $userfield = $$userfield[0] if ref $userfield eq 'ARRAY';
109 1 50       5 if ( $userfield eq "dn" ) {
110 0         0 my ($string) = $self->user->ldap_entry->dn;
111 0         0 return $string;
112             }
113             else {
114 1         16 my $val = $self->$userfield;
115 1 50       6 return ref($val) eq 'ARRAY' ? $val->[0] : $val;
116             }
117             }
118              
119             =head2 supported_features
120              
121             Returns hashref of features that this Authentication::User subclass supports.
122              
123             =cut
124              
125             sub supported_features {
126             return {
127 0     0 1 0 password => { self_check => 1, },
128             session => 1,
129             roles => { self_check => 0, },
130             };
131             }
132              
133             =head2 check_password($password)
134              
135             Bind's to the directory as the DN of the internal L<Net::LDAP::Entry> object,
136             using the bind password supplied in $password. Returns 1 on a successful
137             bind, 0 on failure.
138              
139             =cut
140              
141             sub check_password {
142 0     0 1 0 my ( $self, $password ) = @_;
143 0 0       0 if ( $self->store->ldap_auth($self->ldap_entry->dn, $password) ) {
144             # Stash a closure which can be used to retrieve the connection in the users context later.
145 0         0 $_ldap_connection_passwords{refaddr($self)} = $password;
146 0         0 return 1;
147             }
148             else {
149 0         0 return 0;
150             }
151             }
152              
153             =head2 roles
154              
155             Returns the results of L<Catalyst::Authentication::Store::LDAP::Backend>'s "lookup_roles" method, an array of roles that are valid for this user.
156              
157             =cut
158              
159             sub roles {
160 0     0 1 0 my $self = shift;
161 0   0     0 $self->{_roles} ||= [$self->store->lookup_roles($self)];
162 0         0 return @{$self->{_roles}};
  0         0  
163             }
164              
165             =head2 for_session
166              
167             Returns the User object, stringified.
168              
169             =cut
170              
171             sub for_session {
172 0     0 1 0 my $self = shift;
173 0         0 return $self->stringify;
174             }
175              
176             =head2 ldap_entry
177              
178             Returns the raw ldap_entry.
179              
180             =cut
181              
182             sub ldap_entry {
183 0     0 1 0 my $self = shift;
184 0         0 return $self->user->{'ldap_entry'};
185             }
186              
187             =head2 attributes($type)
188              
189             Returns an array of attributes present for this user. If $type is "ashash",
190             it will return a hash with the attribute names as keys. (And the values of
191             those attributes as, well, the values of the hash)
192              
193             =cut
194              
195             sub attributes {
196 0     0 1 0 my ( $self, $type ) = @_;
197 0 0       0 if ( $type eq "ashash" ) {
198 0         0 return $self->user->{'attributes'};
199             }
200             else {
201 0         0 return keys( %{ $self->user->{'attributes'} } );
  0         0  
202             }
203             }
204              
205             =head2 has_attribute
206              
207             Returns the values for an attribute, or undef if that attribute is not present.
208             The safest way to get at an attribute.
209              
210             =cut
211              
212             sub has_attribute {
213 4     4 1 9 my ( $self, $attribute ) = @_;
214 4 50       14 if ( !defined($attribute) ) {
215 0         0 Catalyst::Exception->throw(
216             "You must provide an attribute to has_attribute!");
217             }
218 4 50       45 if ( $attribute eq "dn" ) {
    50          
    50          
219 0         0 return $self->ldap_entry->dn;
220             }
221             elsif ( $attribute eq "username" ) {
222 0         0 return $self->user->{'attributes'}->{$self->store->user_field};
223             }
224             elsif ( exists( $self->user->{'attributes'}->{$attribute} ) ) {
225 4         1429 return $self->user->{'attributes'}->{$attribute};
226             }
227             else {
228 0         0 return undef;
229             }
230             }
231              
232             =head2 get
233              
234             A simple wrapper around has_attribute() to satisfy the Catalyst::Authentication::User API.
235              
236             =cut
237              
238 0     0 1 0 sub get { return shift->has_attribute(@_) }
239              
240             =head2 get_object
241              
242             Satisfies the Catalyst::Authentication::User API and returns the contents of the user()
243             attribute.
244              
245             =cut
246              
247 0     0 1 0 sub get_object { return shift->user }
248              
249             =head2 ldap_connection
250              
251             Re-binds to the auth store with the credentials of the user you logged in
252             as, and returns a L<Net::LDAP> object which you can use to do further queries.
253              
254             =cut
255              
256             sub ldap_connection {
257 0     0 1 0 my $self = shift;
258 0         0 $self->store->ldap_bind( undef, $self->ldap_entry->dn,
259             $_ldap_connection_passwords{refaddr($self)} );
260             }
261              
262             =head2 AUTOLOADed methods
263              
264             We automatically map the attributes of the underlying L<Net::LDAP::Entry>
265             object to read-only accessor methods. So, if you have an entry that looks
266             like this one:
267              
268             dn: cn=adam,ou=users,dc=yourcompany,dc=com
269             cn: adam
270             loginShell: /bin/zsh
271             homeDirectory: /home/adam
272             gecos: Adam Jacob
273             gidNumber: 100
274             uidNumber: 1053
275             mail: adam@yourcompany.com
276             uid: adam
277             givenName: Adam
278             sn: Jacob
279             objectClass: inetOrgPerson
280             objectClass: organizationalPerson
281             objectClass: Person
282             objectClass: Top
283             objectClass: posixAccount
284              
285             You can call:
286              
287             $c->user->homedirectory
288              
289             And you'll get the value of the "homeDirectory" attribute. Note that
290             all the AUTOLOADed methods are automatically lower-cased.
291              
292             =head2 Special Keywords
293              
294             The highly useful and common method "username" will map to the configured
295             value of user_field (uid by default.)
296              
297             $c->user->username == $c->user->uid
298              
299             =cut
300              
301             sub DESTROY {
302 5     5   2938 my $self = shift;
303             # Don't leak passwords..
304 5         216 delete $_ldap_connection_passwords{refaddr($self)};
305             }
306              
307             sub can {
308 1     1 0 4 my ($self, $method) = @_;
309              
310 1   33     19 return $self->SUPER::can($method) || do {
311             return unless $self->has_attribute($method);
312 1     1   5 return sub { $_[0]->has_attribute($method) };
313             };
314             }
315              
316             sub AUTOLOAD {
317 5     5   3128 my $self = shift;
318              
319 5         70 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
320              
321 5 50       25 if ( $method eq "DESTROY" ) {
322 0         0 return;
323             }
324              
325 5 50       24 if ( my $attribute = $self->has_attribute($method) ) {
326 5         619 return $attribute;
327             }
328             else {
329 0           Catalyst::Exception->throw(
330             "No attribute $method for User " . $self->stringify );
331             }
332             }
333              
334             1;
335              
336             __END__
337              
338             =head1 AUTHORS
339              
340             Adam Jacob <holoway@cpan.org>
341              
342             Some parts stolen shamelessly and entirely from
343             L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
344              
345             Currently maintained by Peter Karman <karman@cpan.org>.
346              
347             =head1 THANKS
348              
349             To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :)
350              
351             =head1 SEE ALSO
352              
353             L<Catalyst::Authentication::Store::LDAP>, L<Catalyst::Authentication::Store::LDAP::Backend>, L<Catalyst::Plugin::Authentication>, L<Net::LDAP>
354              
355             =head1 COPYRIGHT & LICENSE
356              
357             Copyright (c) 2005 the aforementioned authors. All rights
358             reserved. This program is free software; you can redistribute
359             it and/or modify it under the same terms as Perl itself.
360              
361             =cut
362