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