Merge 'better_model_integration' into 'trunk'
[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
57e643d2 54BEGIN { __PACKAGE__->mk_accessors(qw/user store _ldap_connection/) }
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.
150 $self->_ldap_connection( sub { $self->store->ldap_bind( undef, $self->ldap_entry->dn, $password ) } );
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
235=head2 AUTOLOADed methods
236
237We automatically map the attributes of the underlying L<Net::LDAP::Entry>
238object to read-only accessor methods. So, if you have an entry that looks
239like this one:
240
241 dn: cn=adam,ou=users,dc=yourcompany,dc=com
242 cn: adam
243 loginShell: /bin/zsh
244 homeDirectory: /home/adam
245 gecos: Adam Jacob
246 gidNumber: 100
247 uidNumber: 1053
248 mail: adam@yourcompany.com
249 uid: adam
250 givenName: Adam
251 sn: Jacob
252 objectClass: inetOrgPerson
253 objectClass: organizationalPerson
254 objectClass: Person
255 objectClass: Top
256 objectClass: posixAccount
257
258You can call:
259
260 $c->user->homedirectory
261
262And you'll get the value of the "homeDirectory" attribute. Note that
263all the AUTOLOADed methods are automatically lower-cased.
264
265=head2 Special Keywords
266
267The highly useful and common method "username" will map to the configured
268value of user_field (uid by default.)
269
270 $c->user->username == $c->user->uid
271
272=cut
273
274sub AUTOLOAD {
275 my $self = shift;
276
277 ( my $method ) = ( our $AUTOLOAD =~ /([^:]+)$/ );
278
279 if ( $method eq "DESTROY" ) {
280 return;
281 }
282 if ( exists( $self->user->{'attributes'}->{$method} ) ) {
283 return $self->user->{'attributes'}->{$method};
284 }
285 elsif ( $method eq "username" ) {
286 my $userfield = $self->store->user_field;
287 my $username = $self->has_attribute($userfield);
288 if ($username) {
289 return $username;
290 }
291 else {
292 Catalyst::Exception->throw( "User is missing the "
293 . $userfield
294 . " attribute, which should not be possible!" );
295 }
296 }
297 else {
298 Catalyst::Exception->throw(
299 "No attribute $method for User " . $self->stringify );
300 }
301}
302
3031;
304
305__END__
306
307=head1 AUTHORS
308
309Adam Jacob <holoway@cpan.org>
310
311Some parts stolen shamelessly and entirely from
312L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
313
314Currently maintained by Peter Karman <karman@cpan.org>.
315
316=head1 THANKS
317
318To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :)
319
320=head1 SEE ALSO
321
322L<Catalyst::Authentication::Store::LDAP>, L<Catalyst::Authentication::Store::LDAP::Backend>, L<Catalyst::Plugin::Authentication>, L<Net::LDAP>
323
324=head1 COPYRIGHT & LICENSE
325
326Copyright (c) 2005 the aforementioned authors. All rights
327reserved. This program is free software; you can redistribute
328it and/or modify it under the same terms as Perl itself.
329
330=cut
331