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