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