skip correct number of tests
[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
2d7fcdde 53our $VERSION = '1.011';
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 }
a4427384 231 elsif ( $attribute eq "username" ) {
232 return $self->user->{'attributes'}->{$self->store->user_field};
233 }
f66d606b 234 elsif ( exists( $self->user->{'attributes'}->{$attribute} ) ) {
235 return $self->user->{'attributes'}->{$attribute};
236 }
237 else {
238 return undef;
239 }
240}
241
da0d62e4 242=head2 ldap_connection
243
244Re-binds to the auth store with the credentials of the user you logged in
245as, and returns a L<Net::LDAP> object which you can use to do further queries.
246
247=cut
248
249sub ldap_connection {
250 my $self = shift;
89ab2886 251 $self->store->ldap_bind( undef, $self->ldap_entry->dn,
8fe890e6 252 $_ldap_connection_passwords{refaddr($self)} );
da0d62e4 253}
254
f66d606b 255=head2 AUTOLOADed methods
256
257We automatically map the attributes of the underlying L<Net::LDAP::Entry>
258object to read-only accessor methods. So, if you have an entry that looks
259like this one:
260
261 dn: cn=adam,ou=users,dc=yourcompany,dc=com
262 cn: adam
263 loginShell: /bin/zsh
264 homeDirectory: /home/adam
265 gecos: Adam Jacob
266 gidNumber: 100
267 uidNumber: 1053
268 mail: adam@yourcompany.com
269 uid: adam
270 givenName: Adam
271 sn: Jacob
272 objectClass: inetOrgPerson
273 objectClass: organizationalPerson
274 objectClass: Person
275 objectClass: Top
276 objectClass: posixAccount
277
278You can call:
279
280 $c->user->homedirectory
281
282And you'll get the value of the "homeDirectory" attribute. Note that
283all the AUTOLOADed methods are automatically lower-cased.
284
285=head2 Special Keywords
286
287The highly useful and common method "username" will map to the configured
288value of user_field (uid by default.)
289
290 $c->user->username == $c->user->uid
291
292=cut
293
8fe890e6 294sub DESTROY {
295 my $self = shift;
296 # Don't leak passwords..
297 delete $_ldap_connection_passwords{refaddr($self)};
298}
299
f66d606b 300sub AUTOLOAD {
301 my $self = shift;
302
303 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
304
305 if ( $method eq "DESTROY" ) {
306 return;
307 }
a4427384 308
309 if ( my $attribute = $self->has_attribute($method) ) {
310 return $attribute;
f66d606b 311 }
312 else {
313 Catalyst::Exception->throw(
314 "No attribute $method for User " . $self->stringify );
315 }
316}
317
3181;
319
320__END__
321
322=head1 AUTHORS
323
324Adam Jacob <holoway@cpan.org>
325
326Some parts stolen shamelessly and entirely from
327L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
328
329Currently maintained by Peter Karman <karman@cpan.org>.
330
331=head1 THANKS
332
333To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :)
334
335=head1 SEE ALSO
336
337L<Catalyst::Authentication::Store::LDAP>, L<Catalyst::Authentication::Store::LDAP::Backend>, L<Catalyst::Plugin::Authentication>, L<Net::LDAP>
338
339=head1 COPYRIGHT & LICENSE
340
341Copyright (c) 2005 the aforementioned authors. All rights
342reserved. This program is free software; you can redistribute
343it and/or modify it under the same terms as Perl itself.
344
345=cut
346