File Coverage

blib/lib/Catalyst/Authentication/Store/DBIx/Class/User.pm
Criterion Covered Total %
statement 24 126 19.0
branch 6 54 11.1
condition 1 18 5.5
subroutine 9 23 39.1
pod 12 12 100.0
total 52 233 22.3


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Store::DBIx::Class::User;
2              
3 1     1   24027 use Moose;
  1         491762  
  1         11  
4 1     1   9914 use namespace::autoclean;
  1         10406  
  1         5  
5             extends 'Catalyst::Authentication::User';
6              
7 1     1   83 use List::MoreUtils 'all';
  1         3  
  1         11  
8 1     1   488 use Try::Tiny;
  1         2  
  1         2698  
9              
10             has 'config' => (is => 'rw');
11             has 'resultset' => (is => 'rw');
12             has '_user' => (is => 'rw');
13             has '_roles' => (is => 'rw');
14              
15             sub new {
16 0     0 1 0 my ( $class, $config, $c) = @_;
17              
18             $config->{user_model} = $config->{user_class}
19 0 0       0 unless defined $config->{user_model};
20              
21             my $self = {
22 0         0 resultset => $c->model($config->{'user_model'}),
23             config => $config,
24             _roles => undef,
25             _user => undef
26             };
27              
28 0         0 bless $self, $class;
29              
30             Catalyst::Exception->throw(
31 0         0 "\$c->model('${ \$self->config->{user_model} }') did not return a resultset."
32             . " Did you set user_model correctly?"
33 0 0       0 ) unless $self->{resultset};
34              
35             $self->config->{'id_field'} = [$self->{'resultset'}->result_source->primary_columns]
36 0 0       0 unless exists $self->config->{'id_field'};
37              
38             $self->config->{'id_field'} = [$self->config->{'id_field'}]
39 0 0       0 unless ref $self->config->{'id_field'} eq 'ARRAY';
40              
41             Catalyst::Exception->throw(
42             "id_field set to "
43 0         0 . join(q{,} => @{ $self->config->{'id_field'} })
44             . " but user table has no column by that name!"
45 0 0   0   0 ) unless all { $self->{'resultset'}->result_source->has_column($_) } @{ $self->config->{'id_field'} };
  0         0  
  0         0  
46              
47             ## if we have lazyloading turned on - we should not query the DB unless something gets read.
48             ## that's the idea anyway - still have to work out how to manage that - so for now we always force
49             ## lazyload to off.
50 0         0 $self->config->{lazyload} = 0;
51              
52             # if (!$self->config->{lazyload}) {
53             # return $self->load_user($authinfo, $c);
54             # } else {
55             # ## what do we do with a lazyload?
56             # ## presumably this is coming out of session storage.
57             # ## use $authinfo to fill in the user in that case?
58             # }
59              
60 0         0 return $self;
61             }
62              
63              
64             sub load {
65 0     0 1 0 my ($self, $authinfo, $c) = @_;
66              
67 0         0 my $dbix_class_config = 0;
68              
69 0 0       0 if (exists($authinfo->{'dbix_class'})) {
70 0         0 $authinfo = $authinfo->{'dbix_class'};
71 0         0 $dbix_class_config = 1;
72             }
73              
74             ## User can provide an arrayref containing the arguments to search on the user class.
75             ## or even provide a prepared resultset, allowing maximum flexibility for user retrieval.
76             ## these options are only available when using the dbix_class authinfo hash.
77 0 0 0     0 if ($dbix_class_config && exists($authinfo->{'result'})) {
    0 0        
    0 0        
78 0         0 $self->_user($authinfo->{'result'});
79             } elsif ($dbix_class_config && exists($authinfo->{'resultset'})) {
80 0         0 $self->_user($authinfo->{'resultset'}->first);
81             } elsif ($dbix_class_config && exists($authinfo->{'searchargs'})) {
82 0         0 $self->_user($self->resultset->search(@{$authinfo->{'searchargs'}})->first);
  0         0  
83             } else {
84             ## merge the ignore fields array into a hash - so we can do an easy check while building the query
85 0         0 my %ignorefields = map { $_ => 1} @{$self->config->{'ignore_fields_in_find'}};
  0         0  
  0         0  
86 0         0 my $searchargs = {};
87              
88             # now we walk all the fields passed in, and build up a search hash.
89 0         0 foreach my $key (grep {!$ignorefields{$_}} keys %{$authinfo}) {
  0         0  
  0         0  
90 0 0       0 if ($self->resultset->result_source->has_column($key)) {
91 0         0 $searchargs->{$key} = $authinfo->{$key};
92             }
93             }
94 0 0       0 if (keys %{$searchargs}) {
  0         0  
95 0         0 $self->_user($self->resultset->search($searchargs)->first);
96             } else {
97             Catalyst::Exception->throw(
98 0         0 "Failed to load user data. You passed [" . join(',', keys %{$authinfo}) . "]"
99 0         0 . " to authenticate() but your user source (" . $self->config->{'user_model'} . ")"
100             . " only has these columns: [" . join( ",", $self->resultset->result_source->columns ) . "]"
101             . " Check your authenticate() call."
102             );
103             }
104             }
105              
106 0 0       0 if ($self->get_object) {
107 0         0 return $self;
108             } else {
109 0         0 return undef;
110             }
111              
112             }
113              
114             sub supported_features {
115 0     0 1 0 my $self = shift;
116              
117             return {
118 0         0 session => 1,
119             roles => 1,
120             };
121             }
122              
123              
124             sub roles {
125 0     0 1 0 my ( $self ) = shift;
126             ## this used to load @wantedroles - but that doesn't seem to be used by the roles plugin, so I dropped it.
127              
128             ## shortcut if we have already retrieved them
129 0 0       0 if (ref $self->_roles eq 'ARRAY') {
130 0         0 return(@{$self->_roles});
  0         0  
131             }
132              
133 0         0 my @roles = ();
134 0 0       0 if (exists($self->config->{'role_column'})) {
    0          
135 0         0 my $role_data = $self->get($self->config->{'role_column'});
136 0 0       0 if ($role_data) {
137 0         0 @roles = split /[\s,\|]+/, $self->get($self->config->{'role_column'});
138             }
139 0         0 $self->_roles(\@roles);
140             } elsif (exists($self->config->{'role_relation'})) {
141 0         0 my $relation = $self->config->{'role_relation'};
142 0 0       0 if ($self->_user->$relation->result_source->has_column($self->config->{'role_field'})) {
143             @roles = map {
144             $_->get_column($self->config->{role_field})
145 0         0 } $self->_user->$relation->search(undef, {
146 0         0 columns => [ $self->config->{role_field} ]
147             })->all;
148 0         0 $self->_roles(\@roles);
149             } else {
150 0         0 Catalyst::Exception->throw("role table does not have a column called " . $self->config->{'role_field'});
151             }
152             } else {
153 0         0 Catalyst::Exception->throw("user->roles accessed, but no role configuration found");
154             }
155              
156 0         0 return @{$self->_roles};
  0         0  
157             }
158              
159             sub for_session {
160 0     0 1 0 my $self = shift;
161              
162             #return $self->get($self->config->{'id_field'});
163              
164             #my $frozenuser = $self->_user->result_source->schema->freeze( $self->_user );
165             #return $frozenuser;
166              
167 0         0 my %userdata = $self->_user->get_columns();
168              
169             # If use_userdata_from_session is set, then store all of the columns of the user obj in the session
170 0 0 0     0 if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
171 0         0 return \%userdata;
172             } else { # Otherwise, we just need the PKs for load() to use.
173 0         0 my %pk_fields = map { ($_ => $userdata{$_}) } @{ $self->config->{id_field} };
  0         0  
  0         0  
174 0         0 return \%pk_fields;
175             }
176             }
177              
178             sub from_session {
179 0     0 1 0 my ($self, $frozenuser, $c) = @_;
180              
181             #my $obj = $self->resultset->result_source->schema->thaw( $frozenuser );
182             #$self->_user($obj);
183              
184             #if (!exists($self->config->{'use_userdata_from_session'}) || $self->config->{'use_userdata_from_session'} == 0) {
185             # $self->_user->discard_changes();
186             # }
187             #
188             # return $self;
189             #
190             ## if use_userdata_from_session is defined in the config, we fill in the user data from the session.
191 0 0 0     0 if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
192              
193             # We need to use inflate_result here since we -are- inflating a
194             # result object from cached data, not creating a fresh one.
195             # Components such as EncodedColumn wrap new() to ensure that a
196             # provided password is hashed on the way in, and re-running the
197             # hash function on data being restored is expensive and incorrect.
198              
199 0         0 my $class = $self->resultset->result_class;
200 0         0 my $source = $self->resultset->result_source;
201 0         0 my $obj = $class->inflate_result($source, { %$frozenuser });
202              
203 0         0 $obj->in_storage(1);
204 0         0 $self->_user($obj);
205 0         0 return $self;
206             }
207              
208 0 0       0 if (ref $frozenuser eq 'HASH') {
209             return $self->load({
210 0         0 map { ($_ => $frozenuser->{$_}) }
211 0         0 @{ $self->config->{id_field} }
  0         0  
212             }, $c);
213             }
214              
215 0         0 return $self->load( { $self->config->{'id_field'} => $frozenuser }, $c);
216             }
217              
218             sub get {
219 0     0 1 0 my ($self, $field) = @_;
220              
221 0 0       0 if (my $code = $self->_user->can($field)) {
    0          
222 0         0 return $self->_user->$code;
223             }
224             elsif (my $accessor =
225 0     0   0 try { $self->_user->result_source->column_info($field)->{accessor} }) {
226 0         0 return $self->_user->$accessor;
227             } else {
228             # XXX this should probably throw
229 0         0 return undef;
230             }
231             }
232              
233             sub get_object {
234 0     0 1 0 my ($self, $force) = @_;
235              
236 0 0       0 if ($force) {
237 0         0 $self->_user->discard_changes;
238             }
239              
240 0         0 return $self->_user;
241             }
242              
243             sub obj {
244 0     0 1 0 my ($self, $force) = @_;
245              
246 0         0 return $self->get_object($force);
247             }
248              
249             sub auto_create {
250 0     0 1 0 my $self = shift;
251 0         0 $self->_user( $self->resultset->auto_create( @_ ) );
252 0         0 return $self;
253             }
254              
255             sub auto_update {
256 0     0 1 0 my $self = shift;
257 0         0 $self->_user->auto_update( @_ );
258             }
259              
260             sub can {
261 3     3 1 1338 my $self = shift;
262 3   33     30 return $self->SUPER::can(@_) || do {
263             my ($method) = @_;
264             if (not ref $self) {
265             undef;
266             } elsif (not $self->_user) {
267             undef;
268             } elsif (my $code = $self->_user->can($method)) {
269 1     1   50 sub { shift->_user->$code(@_) }
270             } elsif (my $accessor =
271 1     1   74 try { $self->_user->result_source->column_info($method)->{accessor} }) {
272 0     0   0 sub { shift->_user->$accessor }
273             } else {
274             undef;
275             }
276             };
277             }
278              
279             sub AUTOLOAD {
280 3     3   430 my $self = shift;
281 3         27 (my $method) = (our $AUTOLOAD =~ /([^:]+)$/);
282 3 50       9 return if $method eq "DESTROY";
283              
284 3 100       10 return unless ref $self;
285              
286 2 100       138 if (my $code = $self->_user->can($method)) {
    50          
287 1         49 return $self->_user->$code(@_);
288             }
289             elsif (my $accessor =
290 1     1   64 try { $self->_user->result_source->column_info($method)->{accessor} }) {
291 0         0 return $self->_user->$accessor(@_);
292             } else {
293             # XXX this should also throw
294 1         14 return undef;
295             }
296             }
297              
298             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
299              
300             1;
301             __END__
302              
303             =head1 NAME
304              
305             Catalyst::Authentication::Store::DBIx::Class::User - The backing user
306             class for the Catalyst::Authentication::Store::DBIx::Class storage
307             module.
308              
309             =head1 VERSION
310              
311             This documentation refers to version 0.1506.
312              
313             =head1 SYNOPSIS
314              
315             Internal - not used directly, please see
316             L<Catalyst::Authentication::Store::DBIx::Class> for details on how to
317             use this module. If you need more information than is present there, read the
318             source.
319              
320              
321              
322             =head1 DESCRIPTION
323              
324             The Catalyst::Authentication::Store::DBIx::Class::User class implements user storage
325             connected to an underlying DBIx::Class schema object.
326              
327             =head1 SUBROUTINES / METHODS
328              
329             =head2 new
330              
331             Constructor.
332              
333             =head2 load ( $authinfo, $c )
334              
335             Retrieves a user from storage using the information provided in $authinfo.
336              
337             =head2 supported_features
338              
339             Indicates the features supported by this class. These are currently Roles and Session.
340              
341             =head2 roles
342              
343             Returns an array of roles associated with this user, if roles are configured for this user class.
344              
345             =head2 for_session
346              
347             Returns a serialized user for storage in the session.
348              
349             =head2 from_session
350              
351             Revives a serialized user from storage in the session.
352              
353             =head2 get ( $fieldname )
354              
355             Returns the value of $fieldname for the user in question. Roughly translates to a call to
356             the DBIx::Class::Row's get_column( $fieldname ) routine.
357              
358             =head2 get_object
359              
360             Retrieves the DBIx::Class object that corresponds to this user
361              
362             =head2 obj (method)
363              
364             Synonym for get_object
365              
366             =head2 auto_create
367              
368             This is called when the auto_create_user option is turned on in
369             Catalyst::Plugin::Authentication and a user matching the authinfo provided is not found.
370             By default, this will call the C<auto_create()> method of the resultset associated
371             with this object. It is up to you to implement that method.
372              
373             =head2 auto_update
374              
375             This is called when the auto_update_user option is turned on in
376             Catalyst::Plugin::Authentication. Note that by default the DBIx::Class store
377             uses every field in the authinfo hash to match the user. This means any
378             information you provide with the intent to update must be ignored during the
379             user search process. Otherwise the information will most likely cause the user
380             record to not be found. To ignore fields in the search process, you
381             have to add the fields you wish to update to the 'ignore_fields_in_find'
382             authinfo element. Alternately, you can use one of the advanced row retrieval
383             methods (searchargs or resultset).
384              
385             By default, auto_update will call the C<auto_update()> method of the
386             DBIx::Class::Row object associated with the user. It is up to you to implement
387             that method (probably in your schema file)
388              
389             =head2 AUTOLOAD
390              
391             Delegates method calls to the underlying user row.
392              
393             =head2 can
394              
395             Delegates handling of the C<< can >> method to the underlying user row.
396              
397             =head1 BUGS AND LIMITATIONS
398              
399             None known currently, please email the author if you find any.
400              
401             =head1 AUTHOR
402              
403             Jason Kuri (jayk@cpan.org)
404              
405             =head1 CONTRIBUTORS
406              
407             Matt S Trout (mst) <mst@shadowcat.co.uk>
408              
409             (fixes wrt can/AUTOLOAD sponsored by L<http://reask.com/>)
410              
411             =head1 LICENSE
412              
413             Copyright (c) 2007-2010 the aforementioned authors. All rights
414             reserved. This program is free software; you can redistribute
415             it and/or modify it under the same terms as Perl itself.
416              
417             =cut