Date in changelog, regenerate README, fix Makefile.PL
[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
5faab354 53our $VERSION = '1.009';
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 ) = @_;
143 my $ldap
144 = $self->store->ldap_bind( undef, $self->ldap_entry->dn, $password,
145 'forauth' );
146 if ( defined($ldap) ) {
405489b5 147 if ($self->store->role_search_as_user) {
57e643d2 148 # FIXME - This can be removed and made to use the code below..
405489b5 149 # Have to do the role lookup _now_, as this is the only time
150 # that we have the user's password/ldap bind..
151 $self->roles($ldap);
152 }
57e643d2 153 # Stash a closure which can be used to retrieve the connection in the users context later.
8fe890e6 154 $_ldap_connection_passwords{refaddr($self)} = $password;
f66d606b 155 return 1;
156 }
157 else {
158 return 0;
159 }
160}
161
162=head2 roles
163
164Returns the results of L<Catalyst::Authentication::Store::LDAP::Backend>'s "lookup_roles" method, an array of roles that are valid for this user.
165
166=cut
167
168sub roles {
169 my $self = shift;
405489b5 170 my $ldap = shift;
171 $self->{_roles} ||= [$self->store->lookup_roles($self, $ldap)];
172 return @{$self->{_roles}};
f66d606b 173}
174
175=head2 for_session
176
177Returns the User object, stringified.
178
179=cut
180
181sub for_session {
182 my $self = shift;
183 return $self->stringify;
184}
185
186=head2 ldap_entry
187
188Returns the raw ldap_entry.
189
190=cut
191
192sub ldap_entry {
193 my $self = shift;
194 return $self->user->{'ldap_entry'};
195}
196
197=head2 attributes($type)
198
199Returns an array of attributes present for this user. If $type is "ashash",
200it will return a hash with the attribute names as keys. (And the values of
201those attributes as, well, the values of the hash)
202
203=cut
204
205sub attributes {
206 my ( $self, $type ) = @_;
207 if ( $type eq "ashash" ) {
208 return $self->user->{'attributes'};
209 }
210 else {
211 return keys( %{ $self->user->{'attributes'} } );
212 }
213}
214
215=head2 has_attribute
216
217Returns the values for an attribute, or undef if that attribute is not present.
218The safest way to get at an attribute.
219
220=cut
221
222sub has_attribute {
223 my ( $self, $attribute ) = @_;
224 if ( !defined($attribute) ) {
225 Catalyst::Exception->throw(
226 "You must provide an attribute to has_attribute!");
227 }
228 if ( $attribute eq "dn" ) {
229 return $self->ldap_entry->dn;
230 }
231 elsif ( exists( $self->user->{'attributes'}->{$attribute} ) ) {
232 return $self->user->{'attributes'}->{$attribute};
233 }
234 else {
235 return undef;
236 }
237}
238
da0d62e4 239=head2 ldap_connection
240
241Re-binds to the auth store with the credentials of the user you logged in
242as, and returns a L<Net::LDAP> object which you can use to do further queries.
243
244=cut
245
246sub ldap_connection {
247 my $self = shift;
89ab2886 248 $self->store->ldap_bind( undef, $self->ldap_entry->dn,
8fe890e6 249 $_ldap_connection_passwords{refaddr($self)} );
da0d62e4 250}
251
f66d606b 252=head2 AUTOLOADed methods
253
254We automatically map the attributes of the underlying L<Net::LDAP::Entry>
255object to read-only accessor methods. So, if you have an entry that looks
256like this one:
257
258 dn: cn=adam,ou=users,dc=yourcompany,dc=com
259 cn: adam
260 loginShell: /bin/zsh
261 homeDirectory: /home/adam
262 gecos: Adam Jacob
263 gidNumber: 100
264 uidNumber: 1053
265 mail: adam@yourcompany.com
266 uid: adam
267 givenName: Adam
268 sn: Jacob
269 objectClass: inetOrgPerson
270 objectClass: organizationalPerson
271 objectClass: Person
272 objectClass: Top
273 objectClass: posixAccount
274
275You can call:
276
277 $c->user->homedirectory
278
279And you'll get the value of the "homeDirectory" attribute. Note that
280all the AUTOLOADed methods are automatically lower-cased.
281
282=head2 Special Keywords
283
284The highly useful and common method "username" will map to the configured
285value of user_field (uid by default.)
286
287 $c->user->username == $c->user->uid
288
289=cut
290
8fe890e6 291sub DESTROY {
292 my $self = shift;
293 # Don't leak passwords..
294 delete $_ldap_connection_passwords{refaddr($self)};
295}
296
f66d606b 297sub AUTOLOAD {
298 my $self = shift;
299
300 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
301
302 if ( $method eq "DESTROY" ) {
303 return;
304 }
305 if ( exists( $self->user->{'attributes'}->{$method} ) ) {
306 return $self->user->{'attributes'}->{$method};
307 }
308 elsif ( $method eq "username" ) {
309 my $userfield = $self->store->user_field;
310 my $username = $self->has_attribute($userfield);
311 if ($username) {
312 return $username;
313 }
314 else {
315 Catalyst::Exception->throw( "User is missing the "
316 . $userfield
317 . " attribute, which should not be possible!" );
318 }
319 }
320 else {
321 Catalyst::Exception->throw(
322 "No attribute $method for User " . $self->stringify );
323 }
324}
325
3261;
327
328__END__
329
330=head1 AUTHORS
331
332Adam Jacob <holoway@cpan.org>
333
334Some parts stolen shamelessly and entirely from
335L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
336
337Currently maintained by Peter Karman <karman@cpan.org>.
338
339=head1 THANKS
340
341To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :)
342
343=head1 SEE ALSO
344
345L<Catalyst::Authentication::Store::LDAP>, L<Catalyst::Authentication::Store::LDAP::Backend>, L<Catalyst::Plugin::Authentication>, L<Net::LDAP>
346
347=head1 COPYRIGHT & LICENSE
348
349Copyright (c) 2005 the aforementioned authors. All rights
350reserved. This program is free software; you can redistribute
351it and/or modify it under the same terms as Perl itself.
352
353=cut
354