Fix bug that results in you getting the user back as your object
[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;
51
d94851da 52our $VERSION = '0.1005';
f66d606b 53
da0d62e4 54BEGIN { __PACKAGE__->mk_accessors(qw/user store _ldap_connection_password/) }
f66d606b 55
56use overload '""' => sub { shift->stringify }, fallback => 1;
57
58=head1 METHODS
59
52a972a4 60=head2 new($store, $user, $c)
f66d606b 61
62Takes a L<Catalyst::Authentication::Store::LDAP::Backend> object
63as $store, and the data structure returned by that class's "get_user"
52a972a4 64method as $user. The final argument is an instance of your application,
65which is passed along for those wanting to subclass User and perhaps use
66models for fetching data.
f66d606b 67
68Returns a L<Catalyst::Authentication::Store::LDAP::User> object.
69
70=cut
71
72sub new {
52a972a4 73 my ( $class, $store, $user, $c ) = @_;
f66d606b 74
75 return unless $user;
76
77 bless { store => $store, user => $user, }, $class;
78}
79
80=head2 id
81
82Returns the results of the "stringify" method.
83
84=cut
85
86sub id {
87 my $self = shift;
88 return $self->stringify;
89}
90
91=head2 stringify
92
93Uses the "user_field" configuration option to determine what the "username"
94of this object is, and returns it.
95
96If you use the special value "dn" for user_field, it will return the DN
97of the L<Net::LDAP::Entry> object.
98
99=cut
100
101sub stringify {
102 my ($self) = @_;
103 my $userfield = $self->store->user_field;
104 $userfield = $$userfield[0] if ref $userfield eq 'ARRAY';
105 if ( $userfield eq "dn" ) {
106 my ($string) = $self->user->ldap_entry->dn;
107 return $string;
108 }
109 else {
110 my ($string) = $self->$userfield;
111 return $string;
112 }
113}
114
115=head2 supported_features
116
117Returns hashref of features that this Authentication::User subclass supports.
118
119=cut
120
121sub supported_features {
122 return {
123 password => { self_check => 1, },
124 session => 1,
125 roles => { self_check => 0, },
126 };
127}
128
129=head2 check_password($password)
130
131Bind's to the directory as the DN of the internal L<Net::LDAP::Entry> object,
132using the bind password supplied in $password. Returns 1 on a successful
133bind, 0 on failure.
134
135=cut
136
137sub check_password {
138 my ( $self, $password ) = @_;
139 my $ldap
140 = $self->store->ldap_bind( undef, $self->ldap_entry->dn, $password,
141 'forauth' );
142 if ( defined($ldap) ) {
405489b5 143 if ($self->store->role_search_as_user) {
57e643d2 144 # FIXME - This can be removed and made to use the code below..
405489b5 145 # Have to do the role lookup _now_, as this is the only time
146 # that we have the user's password/ldap bind..
147 $self->roles($ldap);
148 }
57e643d2 149 # Stash a closure which can be used to retrieve the connection in the users context later.
da0d62e4 150 $self->_ldap_connection_password( sub { $password } ); # Close over
151 # password to try to ensure it doesn't come out in debug dumps
152 # or get serialized into sessions etc..
f66d606b 153 return 1;
154 }
155 else {
156 return 0;
157 }
158}
159
160=head2 roles
161
162Returns the results of L<Catalyst::Authentication::Store::LDAP::Backend>'s "lookup_roles" method, an array of roles that are valid for this user.
163
164=cut
165
166sub roles {
167 my $self = shift;
405489b5 168 my $ldap = shift;
169 $self->{_roles} ||= [$self->store->lookup_roles($self, $ldap)];
170 return @{$self->{_roles}};
f66d606b 171}
172
173=head2 for_session
174
175Returns the User object, stringified.
176
177=cut
178
179sub for_session {
180 my $self = shift;
181 return $self->stringify;
182}
183
184=head2 ldap_entry
185
186Returns the raw ldap_entry.
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.
216The safest way to get at an attribute.
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 }
229 elsif ( exists( $self->user->{'attributes'}->{$attribute} ) ) {
230 return $self->user->{'attributes'}->{$attribute};
231 }
232 else {
233 return undef;
234 }
235}
236
da0d62e4 237=head2 ldap_connection
238
239Re-binds to the auth store with the credentials of the user you logged in
240as, and returns a L<Net::LDAP> object which you can use to do further queries.
241
242=cut
243
244sub ldap_connection {
245 my $self = shift;
89ab2886 246 $self->store->ldap_bind( undef, $self->ldap_entry->dn,
da0d62e4 247 $self->_ldap_connection_password->() );
da0d62e4 248}
249
f66d606b 250=head2 AUTOLOADed methods
251
252We automatically map the attributes of the underlying L<Net::LDAP::Entry>
253object to read-only accessor methods. So, if you have an entry that looks
254like this one:
255
256 dn: cn=adam,ou=users,dc=yourcompany,dc=com
257 cn: adam
258 loginShell: /bin/zsh
259 homeDirectory: /home/adam
260 gecos: Adam Jacob
261 gidNumber: 100
262 uidNumber: 1053
263 mail: adam@yourcompany.com
264 uid: adam
265 givenName: Adam
266 sn: Jacob
267 objectClass: inetOrgPerson
268 objectClass: organizationalPerson
269 objectClass: Person
270 objectClass: Top
271 objectClass: posixAccount
272
273You can call:
274
275 $c->user->homedirectory
276
277And you'll get the value of the "homeDirectory" attribute. Note that
278all the AUTOLOADed methods are automatically lower-cased.
279
280=head2 Special Keywords
281
282The highly useful and common method "username" will map to the configured
283value of user_field (uid by default.)
284
285 $c->user->username == $c->user->uid
286
287=cut
288
289sub AUTOLOAD {
290 my $self = shift;
291
292 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
293
294 if ( $method eq "DESTROY" ) {
295 return;
296 }
297 if ( exists( $self->user->{'attributes'}->{$method} ) ) {
298 return $self->user->{'attributes'}->{$method};
299 }
300 elsif ( $method eq "username" ) {
301 my $userfield = $self->store->user_field;
302 my $username = $self->has_attribute($userfield);
303 if ($username) {
304 return $username;
305 }
306 else {
307 Catalyst::Exception->throw( "User is missing the "
308 . $userfield
309 . " attribute, which should not be possible!" );
310 }
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