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