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