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