1 package Catalyst::Authentication::Store::DBIx::Class::User;
4 use namespace::autoclean;
5 extends 'Catalyst::Authentication::User';
7 use List::MoreUtils 'all';
10 has 'config' => (is => 'rw');
11 has 'resultset' => (is => 'rw');
12 has '_user' => (is => 'rw');
13 has '_roles' => (is => 'rw');
16 my ( $class, $config, $c) = @_;
18 $config->{user_model} = $config->{user_class}
19 unless defined $config->{user_model};
22 resultset => $c->model($config->{'user_model'}),
30 Catalyst::Exception->throw(
31 "\$c->model('${ \$self->config->{user_model} }') did not return a resultset."
32 . " Did you set user_model correctly?"
33 ) unless $self->{resultset};
35 $self->config->{'id_field'} = [$self->{'resultset'}->result_source->primary_columns]
36 unless exists $self->config->{'id_field'};
38 $self->config->{'id_field'} = [$self->config->{'id_field'}]
39 unless ref $self->config->{'id_field'} eq 'ARRAY';
41 Catalyst::Exception->throw(
43 . join(q{,} => @{ $self->config->{'id_field'} })
44 . " but user table has no column by that name!"
45 ) unless all { $self->{'resultset'}->result_source->has_column($_) } @{ $self->config->{'id_field'} };
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
50 $self->config->{lazyload} = 0;
52 # if (!$self->config->{lazyload}) {
53 # return $self->load_user($authinfo, $c);
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?
65 my ($self, $authinfo, $c) = @_;
67 my $dbix_class_config = 0;
69 if (exists($authinfo->{'dbix_class'})) {
70 $authinfo = $authinfo->{'dbix_class'};
71 $dbix_class_config = 1;
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 if ($dbix_class_config && exists($authinfo->{'result'})) {
78 $self->_user($authinfo->{'result'});
79 } elsif ($dbix_class_config && exists($authinfo->{'resultset'})) {
80 $self->_user($authinfo->{'resultset'}->first);
81 } elsif ($dbix_class_config && exists($authinfo->{'searchargs'})) {
82 $self->_user($self->resultset->search(@{$authinfo->{'searchargs'}})->first);
84 ## merge the ignore fields array into a hash - so we can do an easy check while building the query
85 my %ignorefields = map { $_ => 1} @{$self->config->{'ignore_fields_in_find'}};
88 # now we walk all the fields passed in, and build up a search hash.
89 foreach my $key (grep {!$ignorefields{$_}} keys %{$authinfo}) {
90 if ($self->resultset->result_source->has_column($key)) {
91 $searchargs->{$key} = $authinfo->{$key};
94 if (keys %{$searchargs}) {
95 $self->_user($self->resultset->search($searchargs)->first);
97 Catalyst::Exception->throw(
98 "Failed to load user data. You passed [" . join(',', keys %{$authinfo}) . "]"
99 . " 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."
106 if ($self->get_object) {
114 sub supported_features {
120 self_check => $self->config->{check_roles} || 0,,
121 self_check_any => $self->config->{check_roles_any} || 0,
126 #will only be used if $config->{check_roles} is set
128 my ( $self, @wanted_roles ) = @_;
130 my @roles = $self->roles;
131 my $name = $self->config->{check_roles};
133 return $self->_user->$name( \@roles, \@wanted_roles );
136 #will only be used if $config->{check_roles_any} is set
137 sub check_roles_any {
138 my ( $self, @wanted_roles ) = @_;
140 my @roles = $self->roles;
141 my $name = $self->config->{check_roles_any};
143 return $self->_user->$name( \@roles, \@wanted_roles );
147 my ( $self ) = shift;
148 ## this used to load @wantedroles - but that doesn't seem to be used by the roles plugin, so I dropped it.
150 ## shortcut if we have already retrieved them
151 if (ref $self->_roles eq 'ARRAY') {
152 return(@{$self->_roles});
156 if (exists($self->config->{'role_column'})) {
157 my $role_data = $self->get($self->config->{'role_column'});
159 @roles = split /[\s,\|]+/, $self->get($self->config->{'role_column'});
161 $self->_roles(\@roles);
162 } elsif (exists($self->config->{'role_relation'})) {
163 my $relation = $self->config->{'role_relation'};
164 if ($self->_user->$relation->result_source->has_column($self->config->{'role_field'})) {
166 $_->get_column($self->config->{role_field})
167 } $self->_user->$relation->search(undef, {
168 columns => [ $self->config->{role_field} ]
170 $self->_roles(\@roles);
172 Catalyst::Exception->throw("role table does not have a column called " . $self->config->{'role_field'});
175 Catalyst::Exception->throw("user->roles accessed, but no role configuration found");
178 return @{$self->_roles};
184 #return $self->get($self->config->{'id_field'});
186 #my $frozenuser = $self->_user->result_source->schema->freeze( $self->_user );
189 my %userdata = $self->_user->get_columns();
191 # If use_userdata_from_session is set, then store all of the columns of the user obj in the session
192 if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
194 } else { # Otherwise, we just need the PKs for load() to use.
195 my %pk_fields = map { ($_ => $userdata{$_}) } @{ $self->config->{id_field} };
201 my ($self, $frozenuser, $c) = @_;
203 #my $obj = $self->resultset->result_source->schema->thaw( $frozenuser );
206 #if (!exists($self->config->{'use_userdata_from_session'}) || $self->config->{'use_userdata_from_session'} == 0) {
207 # $self->_user->discard_changes();
212 ## if use_userdata_from_session is defined in the config, we fill in the user data from the session.
213 if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
215 # We need to use inflate_result here since we -are- inflating a
216 # result object from cached data, not creating a fresh one.
217 # Components such as EncodedColumn wrap new() to ensure that a
218 # provided password is hashed on the way in, and re-running the
219 # hash function on data being restored is expensive and incorrect.
221 my $class = $self->resultset->result_class;
222 my $source = $self->resultset->result_source;
223 my $obj = $class->inflate_result($source, { %$frozenuser });
230 if (ref $frozenuser eq 'HASH') {
232 map { ($_ => $frozenuser->{$_}) }
233 @{ $self->config->{id_field} }
237 return $self->load( { $self->config->{'id_field'} => $frozenuser }, $c);
241 my ($self, $field) = @_;
243 if (my $code = $self->_user->can($field)) {
244 return $self->_user->$code;
246 elsif (my $accessor =
247 try { $self->_user->result_source->column_info($field)->{accessor} }) {
248 return $self->_user->$accessor;
250 # XXX this should probably throw
256 my ($self, $force) = @_;
259 $self->_user->discard_changes;
266 my ($self, $force) = @_;
268 return $self->get_object($force);
273 $self->_user( $self->resultset->auto_create( @_ ) );
279 $self->_user->auto_update( @_ );
284 return $self->SUPER::can(@_) || do {
288 } elsif (not $self->_user) {
290 } elsif (my $code = $self->_user->can($method)) {
291 sub { shift->_user->$code(@_) }
292 } elsif (my $accessor =
293 try { $self->_user->result_source->column_info($method)->{accessor} }) {
294 sub { shift->_user->$accessor }
303 (my $method) = (our $AUTOLOAD =~ /([^:]+)$/);
304 return if $method eq "DESTROY";
306 return unless ref $self;
308 if (my $code = $self->_user->can($method)) {
309 return $self->_user->$code(@_);
311 elsif (my $accessor =
312 try { $self->_user->result_source->column_info($method)->{accessor} }) {
313 return $self->_user->$accessor(@_);
315 # XXX this should also throw
320 __PACKAGE__->meta->make_immutable(inline_constructor => 0);
327 Catalyst::Authentication::Store::DBIx::Class::User - The backing user
328 class for the Catalyst::Authentication::Store::DBIx::Class storage
333 This documentation refers to version 0.1503.
337 Internal - not used directly, please see
338 L<Catalyst::Authentication::Store::DBIx::Class> for details on how to
339 use this module. If you need more information than is present there, read the
346 The Catalyst::Authentication::Store::DBIx::Class::User class implements user storage
347 connected to an underlying DBIx::Class schema object.
349 =head1 SUBROUTINES / METHODS
355 =head2 load ( $authinfo, $c )
357 Retrieves a user from storage using the information provided in $authinfo.
359 =head2 supported_features
361 Indicates the features supported by this class. These are currently Roles and Session.
365 Returns an array of roles associated with this user, if roles are configured for this user class.
369 Returns a serialized user for storage in the session.
373 Revives a serialized user from storage in the session.
375 =head2 get ( $fieldname )
377 Returns the value of $fieldname for the user in question. Roughly translates to a call to
378 the DBIx::Class::Row's get_column( $fieldname ) routine.
382 Retrieves the DBIx::Class object that corresponds to this user
386 Synonym for get_object
390 This is called when the auto_create_user option is turned on in
391 Catalyst::Plugin::Authentication and a user matching the authinfo provided is not found.
392 By default, this will call the C<auto_create()> method of the resultset associated
393 with this object. It is up to you to implement that method.
397 This is called when the auto_update_user option is turned on in
398 Catalyst::Plugin::Authentication. Note that by default the DBIx::Class store
399 uses every field in the authinfo hash to match the user. This means any
400 information you provide with the intent to update must be ignored during the
401 user search process. Otherwise the information will most likely cause the user
402 record to not be found. To ignore fields in the search process, you
403 have to add the fields you wish to update to the 'ignore_fields_in_find'
404 authinfo element. Alternately, you can use one of the advanced row retrieval
405 methods (searchargs or resultset).
407 By default, auto_update will call the C<auto_update()> method of the
408 DBIx::Class::Row object associated with the user. It is up to you to implement
409 that method (probably in your schema file)
413 Delegates method calls to the underlying user row.
417 Delegates handling of the C<< can >> method to the underlying user row.
421 Calls the specified check_roles method on the underlying user row.
423 Passes \@roles, \@wanted_roles, where @roles is the list of roles,
424 and @wanted_roles is the list of wanted roles
426 =head2 check_roles_any
428 Calls the specified check_roles_any method on the underlying user row.
430 Passes \@roles, \@wanted_roles, where @roles is the list of roles,
431 and @wanted_roles is the list of wanted roles
433 =head1 BUGS AND LIMITATIONS
435 None known currently, please email the author if you find any.
439 Jason Kuri (jayk@cpan.org)
443 Matt S Trout (mst) <mst@shadowcat.co.uk>
445 (fixes wrt can/AUTOLOAD sponsored by L<http://reask.com/>)
449 Copyright (c) 2007-2010 the aforementioned authors. All rights
450 reserved. This program is free software; you can redistribute
451 it and/or modify it under the same terms as Perl itself.