I don't actually need any of the extra lines, calling ->ldap_bind on the store I...
[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
405489b5 52our $VERSION = '0.1004';
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
60=head2 new($store, $user)
61
62Takes a L<Catalyst::Authentication::Store::LDAP::Backend> object
63as $store, and the data structure returned by that class's "get_user"
64method as $user.
65
66Returns a L<Catalyst::Authentication::Store::LDAP::User> object.
67
68=cut
69
70sub new {
71 my ( $class, $store, $user ) = @_;
72
73 return unless $user;
74
75 bless { store => $store, user => $user, }, $class;
76}
77
78=head2 id
79
80Returns the results of the "stringify" method.
81
82=cut
83
84sub id {
85 my $self = shift;
86 return $self->stringify;
87}
88
89=head2 stringify
90
91Uses the "user_field" configuration option to determine what the "username"
92of this object is, and returns it.
93
94If you use the special value "dn" for user_field, it will return the DN
95of the L<Net::LDAP::Entry> object.
96
97=cut
98
99sub stringify {
100 my ($self) = @_;
101 my $userfield = $self->store->user_field;
102 $userfield = $$userfield[0] if ref $userfield eq 'ARRAY';
103 if ( $userfield eq "dn" ) {
104 my ($string) = $self->user->ldap_entry->dn;
105 return $string;
106 }
107 else {
108 my ($string) = $self->$userfield;
109 return $string;
110 }
111}
112
113=head2 supported_features
114
115Returns hashref of features that this Authentication::User subclass supports.
116
117=cut
118
119sub supported_features {
120 return {
121 password => { self_check => 1, },
122 session => 1,
123 roles => { self_check => 0, },
124 };
125}
126
127=head2 check_password($password)
128
129Bind's to the directory as the DN of the internal L<Net::LDAP::Entry> object,
130using the bind password supplied in $password. Returns 1 on a successful
131bind, 0 on failure.
132
133=cut
134
135sub check_password {
136 my ( $self, $password ) = @_;
137 my $ldap
138 = $self->store->ldap_bind( undef, $self->ldap_entry->dn, $password,
139 'forauth' );
140 if ( defined($ldap) ) {
405489b5 141 if ($self->store->role_search_as_user) {
57e643d2 142 # FIXME - This can be removed and made to use the code below..
405489b5 143 # Have to do the role lookup _now_, as this is the only time
144 # that we have the user's password/ldap bind..
145 $self->roles($ldap);
146 }
57e643d2 147 # Stash a closure which can be used to retrieve the connection in the users context later.
da0d62e4 148 $self->_ldap_connection_password( sub { $password } ); # Close over
149 # password to try to ensure it doesn't come out in debug dumps
150 # or get serialized into sessions etc..
f66d606b 151 return 1;
152 }
153 else {
154 return 0;
155 }
156}
157
158=head2 roles
159
160Returns the results of L<Catalyst::Authentication::Store::LDAP::Backend>'s "lookup_roles" method, an array of roles that are valid for this user.
161
162=cut
163
164sub roles {
165 my $self = shift;
405489b5 166 my $ldap = shift;
167 $self->{_roles} ||= [$self->store->lookup_roles($self, $ldap)];
168 return @{$self->{_roles}};
f66d606b 169}
170
171=head2 for_session
172
173Returns the User object, stringified.
174
175=cut
176
177sub for_session {
178 my $self = shift;
179 return $self->stringify;
180}
181
182=head2 ldap_entry
183
184Returns the raw ldap_entry.
185
186=cut
187
188sub ldap_entry {
189 my $self = shift;
190 return $self->user->{'ldap_entry'};
191}
192
193=head2 attributes($type)
194
195Returns an array of attributes present for this user. If $type is "ashash",
196it will return a hash with the attribute names as keys. (And the values of
197those attributes as, well, the values of the hash)
198
199=cut
200
201sub attributes {
202 my ( $self, $type ) = @_;
203 if ( $type eq "ashash" ) {
204 return $self->user->{'attributes'};
205 }
206 else {
207 return keys( %{ $self->user->{'attributes'} } );
208 }
209}
210
211=head2 has_attribute
212
213Returns the values for an attribute, or undef if that attribute is not present.
214The safest way to get at an attribute.
215
216=cut
217
218sub has_attribute {
219 my ( $self, $attribute ) = @_;
220 if ( !defined($attribute) ) {
221 Catalyst::Exception->throw(
222 "You must provide an attribute to has_attribute!");
223 }
224 if ( $attribute eq "dn" ) {
225 return $self->ldap_entry->dn;
226 }
227 elsif ( exists( $self->user->{'attributes'}->{$attribute} ) ) {
228 return $self->user->{'attributes'}->{$attribute};
229 }
230 else {
231 return undef;
232 }
233}
234
da0d62e4 235=head2 ldap_connection
236
237Re-binds to the auth store with the credentials of the user you logged in
238as, and returns a L<Net::LDAP> object which you can use to do further queries.
239
240=cut
241
242sub ldap_connection {
243 my $self = shift;
89ab2886 244 $self->store->ldap_bind( undef, $self->ldap_entry->dn,
da0d62e4 245 $self->_ldap_connection_password->() );
da0d62e4 246}
247
f66d606b 248=head2 AUTOLOADed methods
249
250We automatically map the attributes of the underlying L<Net::LDAP::Entry>
251object to read-only accessor methods. So, if you have an entry that looks
252like this one:
253
254 dn: cn=adam,ou=users,dc=yourcompany,dc=com
255 cn: adam
256 loginShell: /bin/zsh
257 homeDirectory: /home/adam
258 gecos: Adam Jacob
259 gidNumber: 100
260 uidNumber: 1053
261 mail: adam@yourcompany.com
262 uid: adam
263 givenName: Adam
264 sn: Jacob
265 objectClass: inetOrgPerson
266 objectClass: organizationalPerson
267 objectClass: Person
268 objectClass: Top
269 objectClass: posixAccount
270
271You can call:
272
273 $c->user->homedirectory
274
275And you'll get the value of the "homeDirectory" attribute. Note that
276all the AUTOLOADed methods are automatically lower-cased.
277
278=head2 Special Keywords
279
280The highly useful and common method "username" will map to the configured
281value of user_field (uid by default.)
282
283 $c->user->username == $c->user->uid
284
285=cut
286
287sub AUTOLOAD {
288 my $self = shift;
289
290 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
291
292 if ( $method eq "DESTROY" ) {
293 return;
294 }
295 if ( exists( $self->user->{'attributes'}->{$method} ) ) {
296 return $self->user->{'attributes'}->{$method};
297 }
298 elsif ( $method eq "username" ) {
299 my $userfield = $self->store->user_field;
300 my $username = $self->has_attribute($userfield);
301 if ($username) {
302 return $username;
303 }
304 else {
305 Catalyst::Exception->throw( "User is missing the "
306 . $userfield
307 . " attribute, which should not be possible!" );
308 }
309 }
310 else {
311 Catalyst::Exception->throw(
312 "No attribute $method for User " . $self->stringify );
313 }
314}
315
3161;
317
318__END__
319
320=head1 AUTHORS
321
322Adam Jacob <holoway@cpan.org>
323
324Some parts stolen shamelessly and entirely from
325L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
326
327Currently maintained by Peter Karman <karman@cpan.org>.
328
329=head1 THANKS
330
331To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :)
332
333=head1 SEE ALSO
334
335L<Catalyst::Authentication::Store::LDAP>, L<Catalyst::Authentication::Store::LDAP::Backend>, L<Catalyst::Plugin::Authentication>, L<Net::LDAP>
336
337=head1 COPYRIGHT & LICENSE
338
339Copyright (c) 2005 the aforementioned authors. All rights
340reserved. This program is free software; you can redistribute
341it and/or modify it under the same terms as Perl itself.
342
343=cut
344