Committed missed changes from 0.1082
[catagits/Catalyst-Authentication-Store-DBIx-Class.git] / lib / Catalyst / Authentication / Store / DBIx / Class / User.pm
1 package Catalyst::Authentication::Store::DBIx::Class::User;
2
3 use strict;
4 use warnings;
5 use base qw/Catalyst::Authentication::User/;
6 use base qw/Class::Accessor::Fast/;
7
8 BEGIN {
9     __PACKAGE__->mk_accessors(qw/config resultset _user _roles/);
10 }
11
12 sub new {
13     my ( $class, $config, $c) = @_;
14
15         if (!defined($config->{'user_model'})) {
16                 $config->{'user_model'} = $config->{'user_class'};
17         }
18
19     my $self = {
20         resultset => $c->model($config->{'user_model'}),
21         config => $config,
22         _roles => undef,
23         _user => undef
24     };
25     
26     bless $self, $class;
27
28     
29
30     if (not $self->{'resultset'}) {
31         Catalyst::Exception->throw("\$c->model('${ \$self->config->{user_model} }') did not return a resultset. Did you set user_model correctly?");
32     }
33
34     ## Note to self- add handling of multiple-column primary keys.
35     if (!exists($self->config->{'id_field'})) {
36         my @pks = $self->{'resultset'}->result_source->primary_columns;
37         if ($#pks == 0) {
38             $self->config->{'id_field'} = $pks[0];
39         } else {
40             Catalyst::Exception->throw("user table does not contain a single primary key column - please specify 'id_field' in config!");
41         }
42     }
43
44     if (!$self->{'resultset'}->result_source->has_column($self->config->{'id_field'})) {
45         Catalyst::Exception->throw("id_field set to " .  $self->config->{'id_field'} . " but user table has no column by that name!");
46     }
47     
48     ## if we have lazyloading turned on - we should not query the DB unless something gets read.
49     ## that's the idea anyway - still have to work out how to manage that - so for now we always force
50     ## lazyload to off.
51     $self->config->{lazyload} = 0;
52     
53 #    if (!$self->config->{lazyload}) {
54 #        return $self->load_user($authinfo, $c);
55 #    } else {
56 #        ## what do we do with a lazyload?
57 #        ## presumably this is coming out of session storage.  
58 #        ## use $authinfo to fill in the user in that case?
59 #    }
60
61     return $self;
62 }
63
64
65 sub load {
66     my ($self, $authinfo, $c) = @_;
67     
68     my $dbix_class_config = 0;
69     
70     if (exists($authinfo->{'dbix_class'})) {
71         $authinfo = $authinfo->{'dbix_class'};
72         $dbix_class_config = 1;
73     }
74     
75     ## User can provide an arrayref containing the arguments to search on the user class.
76     ## or even provide a prepared resultset, allowing maximum flexibility for user retreival.
77     ## these options are only available when using the dbix_class authinfo hash. 
78     if ($dbix_class_config && exists($authinfo->{'resultset'})) {
79         $self->_user($authinfo->{'resultset'}->first);
80     } elsif ($dbix_class_config && exists($authinfo->{'searchargs'})) {
81         $self->_user($self->resultset->search(@{$authinfo->{'searchargs'}})->first);    
82     } else {
83         ## merge the ignore fields array into a hash - so we can do an easy check while building the query
84         my %ignorefields = map { $_ => 1} @{$self->config->{'ignore_fields_in_find'}};                                    
85         my $searchargs = {};
86         
87         # now we walk all the fields passed in, and build up a search hash.
88         foreach my $key (grep {!$ignorefields{$_}} keys %{$authinfo}) {
89             if ($self->resultset->result_source->has_column($key)) {
90                 $searchargs->{$key} = $authinfo->{$key};
91             }
92         }
93         if (keys %{$searchargs}) {
94             $self->_user($self->resultset->search($searchargs)->first);
95         } else {
96             Catalyst::Exception->throw("Failed to load user data.  You passed [" . join(',', keys %{$authinfo}) . "] to authenticate() but your user source (" .  $self->config->{'user_model'} . ") only has these columns: [" . join( ",", $self->resultset->result_source->columns ) . "]   Check your authenticate() call.");
97         }
98     }
99
100     if ($self->get_object) {
101         return $self;
102     } else {
103         return undef;
104     }
105
106 }
107
108 sub supported_features {
109     my $self = shift;
110
111     return {
112         session         => 1,
113         roles           => 1,
114     };
115 }
116
117
118 sub roles {
119     my ( $self ) = shift;
120     ## this used to load @wantedroles - but that doesn't seem to be used by the roles plugin, so I dropped it.
121
122     ## shortcut if we have already retrieved them
123     if (ref $self->_roles eq 'ARRAY') {
124         return(@{$self->_roles});
125     }
126     
127     my @roles = ();
128     if (exists($self->config->{'role_column'})) {
129         my $role_data = $self->get($self->config->{'role_column'});
130         if ($role_data) { 
131             @roles = split /[\s,\|]+/, $self->get($self->config->{'role_column'});
132         }
133         $self->_roles(\@roles);
134     } elsif (exists($self->config->{'role_relation'})) {
135         my $relation = $self->config->{'role_relation'};
136         if ($self->_user->$relation->result_source->has_column($self->config->{'role_field'})) {
137             @roles = map { $_->get_column($self->config->{'role_field'}) } $self->_user->$relation->search(undef, { columns => [ $self->config->{'role_field'}]})->all();
138             $self->_roles(\@roles);
139         } else {
140             Catalyst::Exception->throw("role table does not have a column called " . $self->config->{'role_field'});
141         }
142     } else {
143         Catalyst::Exception->throw("user->roles accessed, but no role configuration found");
144     }
145
146     return @{$self->_roles};
147 }
148
149 sub for_session {
150     my $self = shift;
151     
152     #return $self->get($self->config->{'id_field'});
153     
154     #my $frozenuser = $self->_user->result_source->schema->freeze( $self->_user );
155     #return $frozenuser;
156     
157     my %userdata = $self->_user->get_columns();
158     return \%userdata;
159 }
160
161 sub from_session {
162     my ($self, $frozenuser, $c) = @_;
163     
164     #my $obj = $self->resultset->result_source->schema->thaw( $frozenuser );
165     #$self->_user($obj);
166     
167     #if (!exists($self->config->{'use_userdata_from_session'}) || $self->config->{'use_userdata_from_session'} == 0) {
168 #        $self->_user->discard_changes();
169 #    }
170 #    
171 #    return $self;
172 #    
173 ## if use_userdata_from_session is defined in the config, we fill in the user data from the session.
174     if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0)
175     {
176         my $obj = $self->resultset->new_result({ %$frozenuser });
177         $obj->in_storage(1);
178         $self->_user($obj);
179         return $self;
180     } else {
181         my $id;
182         if (ref($frozenuser) eq 'HASH') {
183             $id = $frozenuser->{$self->config->{'id_field'}};
184         } else {
185             $id = $frozenuser;
186         }
187         return $self->load( { $self->config->{'id_field'} => $id }, $c);
188     }
189 }
190
191 sub get {
192     my ($self, $field) = @_;
193     
194     if ($self->_user->can($field)) {
195         return $self->_user->$field;
196     } else {
197         return undef;
198     }
199 }
200
201 sub get_object {
202     my ($self, $force) = @_;
203     
204     if ($force) {
205         $self->_user->discard_changes;
206     }
207
208     return $self->_user;
209 }
210
211 sub obj {
212     my ($self, $force) = @_;
213     
214     return $self->get_object($force);
215 }
216
217 sub auto_create {
218     my $self = shift;
219     $self->_user( $self->resultset->auto_create( @_ ) );
220     return $self;
221 }
222
223 sub auto_update {
224     my $self = shift;
225     $self->_user->auto_update( @_ );
226 }
227
228 sub AUTOLOAD {
229     my $self = shift;
230     (my $method) = (our $AUTOLOAD =~ /([^:]+)$/);
231     return if $method eq "DESTROY";
232
233     $self->_user->$method(@_);
234 }
235
236 1;
237 __END__
238
239 =head1 NAME
240
241 Catalyst::Authentication::Store::DBIx::Class::User - The backing user
242 class for the Catalyst::Authentication::Store::DBIx::Class storage
243 module.
244
245 =head1 VERSION
246
247 This documentation refers to version 0.10.
248
249 =head1 SYNOPSIS
250
251 Internal - not used directly, please see
252 L<Catalyst::Authentication::Store::DBIx::Class> for details on how to
253 use this module. If you need more information than is present there, read the
254 source.
255
256                 
257
258 =head1 DESCRIPTION
259
260 The Catalyst::Authentication::Store::DBIx::Class::User class implements user storage
261 connected to an underlying DBIx::Class schema object.
262
263 =head1 SUBROUTINES / METHODS
264
265 =head2 new 
266
267 Constructor.
268
269 =head2 load ( $authinfo, $c ) 
270
271 Retrieves a user from storage using the information provided in $authinfo.
272
273 =head2 supported_features
274
275 Indicates the features supported by this class.  These are currently Roles and Session.
276
277 =head2 roles
278
279 Returns an array of roles associated with this user, if roles are configured for this user class.
280
281 =head2 for_session
282
283 Returns a serialized user for storage in the session.  
284
285 =head2 from_session
286
287 Revives a serialized user from storage in the session. 
288
289 =head2 get ( $fieldname )
290
291 Returns the value of $fieldname for the user in question.  Roughly translates to a call to 
292 the DBIx::Class::Row's get_column( $fieldname ) routine.
293
294 =head2 get_object 
295
296 Retrieves the DBIx::Class object that corresponds to this user
297
298 =head2 obj (method)
299
300 Synonym for get_object
301
302 =head2 auto_create
303
304 This is called when the auto_create_user option is turned on in 
305 Catalyst::Plugin::Authentication and a user matching the authinfo provided is not found. 
306 By default, this will call the C<auto_create()> method of the resultset associated
307 with this object. It is up to you to implement that method.
308
309 =head2 auto_update
310
311 This is called when the auto_update_user option is turned on in
312 Catalyst::Plugin::Authentication. Note that by default the DBIx::Class store
313 uses every field in the authinfo hash to match the user. This means any
314 information you provide with the intent to update must be ignored during the
315 user search process. Otherwise the information will most likely cause the user
316 record to not be found. To ignore fields in the search process, you
317 have to add the fields you wish to update to the 'ignore_fields_in_find'
318 authinfo element.  Alternately, you can use one of the advanced row retrieval
319 methods (searchargs or resultset).
320
321 By default, auto_update will call the C<auto_update()> method of the
322 DBIx::Class::Row object associated with the user. It is up to you to implement
323 that method (probably in your schema file)
324
325 =head1 BUGS AND LIMITATIONS
326
327 None known currently, please email the author if you find any.
328
329 =head1 AUTHOR
330
331 Jason Kuri (jayk@cpan.org)
332
333 =head1 LICENSE
334
335 Copyright (c) 2007 the aforementioned authors. All rights
336 reserved. This program is free software; you can redistribute
337 it and/or modify it under the same terms as Perl itself.
338
339 =cut