line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Catalyst::Authentication::Store::DBI::ButMaintained; |
2
|
2
|
|
|
2
|
|
22151
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
69
|
|
3
|
2
|
|
|
2
|
|
7
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
49
|
|
4
|
2
|
|
|
2
|
|
872
|
use namespace::autoclean; |
|
2
|
|
|
|
|
29292
|
|
|
2
|
|
|
|
|
12
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
1406
|
use Storable; |
|
2
|
|
|
|
|
5193
|
|
|
2
|
|
|
|
|
107
|
|
7
|
2
|
|
|
2
|
|
397
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
use MooseX::Types::LoadableClass qw/ClassName/; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.03'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
has 'config' => ( |
13
|
|
|
|
|
|
|
isa => 'HashRef' |
14
|
|
|
|
|
|
|
, is => 'ro' |
15
|
|
|
|
|
|
|
, required => 1 |
16
|
|
|
|
|
|
|
, traits => ['Hash'] |
17
|
|
|
|
|
|
|
, handles => { |
18
|
|
|
|
|
|
|
get_config => 'get' |
19
|
|
|
|
|
|
|
} |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
has 'store_user_class' => ( |
23
|
|
|
|
|
|
|
isa => ClassName |
24
|
|
|
|
|
|
|
, is => 'ro' |
25
|
|
|
|
|
|
|
, coerce => 1 |
26
|
|
|
|
|
|
|
, lazy => 1 |
27
|
|
|
|
|
|
|
, default => sub { |
28
|
|
|
|
|
|
|
my $self = shift; |
29
|
|
|
|
|
|
|
defined $self->get_config('store_user_class') |
30
|
|
|
|
|
|
|
? $self->get_config('store_user_class') |
31
|
|
|
|
|
|
|
: 'Catalyst::Authentication::Store::DBI::ButMaintained::User' |
32
|
|
|
|
|
|
|
; |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# locates a user using data contained in the hashref |
37
|
|
|
|
|
|
|
sub find_user { |
38
|
|
|
|
|
|
|
my ($self, $authinfo, $c) = @_; |
39
|
|
|
|
|
|
|
my $dbh = $c->model('DBI')->dbh; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my @col = sort keys %$authinfo; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
my $abs_user_dest = $self->_safe_escape( |
44
|
|
|
|
|
|
|
$dbh |
45
|
|
|
|
|
|
|
, {map { $_ => $self->get_config("user_$_") } qw/database schema table/} |
46
|
|
|
|
|
|
|
); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $sql = "SELECT * FROM $abs_user_dest WHERE " |
49
|
|
|
|
|
|
|
. join( ' AND ', map $dbh->quote_identifier($_) . " = ?", @col ) |
50
|
|
|
|
|
|
|
; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
my $sth = $dbh->prepare($sql) or die($dbh->errstr()); |
53
|
|
|
|
|
|
|
$sth->execute(@$authinfo{@col}) or die($dbh->errstr()); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my %user; |
56
|
|
|
|
|
|
|
$sth->bind_columns(\( @user{ @{ $sth->{'NAME_lc'} } } )) or |
57
|
|
|
|
|
|
|
die($dbh->errstr()); |
58
|
|
|
|
|
|
|
unless ($sth->fetch()) { |
59
|
|
|
|
|
|
|
$sth->finish(); |
60
|
|
|
|
|
|
|
return undef; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
$sth->finish(); |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
## Fail silently clause |
65
|
|
|
|
|
|
|
return undef |
66
|
|
|
|
|
|
|
unless exists $user{$self->get_config('user_key')} |
67
|
|
|
|
|
|
|
&& length $user{$self->get_config('user_key')} |
68
|
|
|
|
|
|
|
; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my $class = $self->store_user_class; |
71
|
|
|
|
|
|
|
return $class->new({ |
72
|
|
|
|
|
|
|
store => $self |
73
|
|
|
|
|
|
|
, user => \%user |
74
|
|
|
|
|
|
|
, authinfo => $authinfo |
75
|
|
|
|
|
|
|
, dbi_model => $c->model('DBI') |
76
|
|
|
|
|
|
|
}); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub _safe_escape { |
81
|
|
|
|
|
|
|
my $self = shift; |
82
|
|
|
|
|
|
|
my ( $dbh, $unescaped ) = @_; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
join '.' |
85
|
|
|
|
|
|
|
, map $dbh->quote_identifier( $unescaped->{$_} ) |
86
|
|
|
|
|
|
|
, grep exists $unescaped->{$_} && defined $unescaped->{$_} |
87
|
|
|
|
|
|
|
, qw/database schema table column/ |
88
|
|
|
|
|
|
|
; |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
## Not sure how for_session would work with ACCEPT_CONTEXT in the Model::DBI |
94
|
|
|
|
|
|
|
## If you don't have the same context in the DBI you could presumably get a |
95
|
|
|
|
|
|
|
## different user |
96
|
|
|
|
|
|
|
sub for_session { |
97
|
|
|
|
|
|
|
my $self = shift; |
98
|
|
|
|
|
|
|
my ( $c, $user) = @_; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
## TODO: Freeze whole user, this should just be fallback |
101
|
|
|
|
|
|
|
if ( |
102
|
|
|
|
|
|
|
exists $self->config->{user_key} |
103
|
|
|
|
|
|
|
&& $user->get( $self->get_config('user_key') ) |
104
|
|
|
|
|
|
|
) { |
105
|
|
|
|
|
|
|
my $k = $self->get_config('user_key'); |
106
|
|
|
|
|
|
|
my $uid = $user->get( $k ); |
107
|
|
|
|
|
|
|
return Storable::nfreeze({ $k => $uid }); |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
## Support users with composite key |
110
|
|
|
|
|
|
|
else { |
111
|
|
|
|
|
|
|
return Storable::nfreeze( $user->authinfo ); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub from_session { |
117
|
|
|
|
|
|
|
my $self = shift; |
118
|
|
|
|
|
|
|
my ( $c, $frozen ) = @_; |
119
|
|
|
|
|
|
|
$self->find_user( Storable::thaw($frozen), $c ); |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub user_supports { |
123
|
|
|
|
|
|
|
return; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub BUILDARGS { |
127
|
|
|
|
|
|
|
my $class = shift; |
128
|
|
|
|
|
|
|
my ( $config, $app, $realm ) = @_; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
scalar @_ == 1 |
131
|
|
|
|
|
|
|
? $class->SUPER::BUILDARGS(@_) |
132
|
|
|
|
|
|
|
: { config => $config, app => $app, realm => $realm } |
133
|
|
|
|
|
|
|
; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
1; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
__END__ |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head1 NAME |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Catalyst::Authentication::Store::DBI::ButMaintained - Storage class for Catalyst Authentication using DBI |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head1 SYNOPSIS |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
use Catalyst qw(Authentication); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
__PACKAGE__->config->{'authentication'} = { |
150
|
|
|
|
|
|
|
default_realm => 'default' |
151
|
|
|
|
|
|
|
, realms => { |
152
|
|
|
|
|
|
|
default => { |
153
|
|
|
|
|
|
|
credential => { |
154
|
|
|
|
|
|
|
class => 'Password' |
155
|
|
|
|
|
|
|
, password_field => 'password' |
156
|
|
|
|
|
|
|
, password_type => 'hashed' |
157
|
|
|
|
|
|
|
, password_hash_type => 'SHA-1' |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
store => { |
160
|
|
|
|
|
|
|
class => 'DBI::ButMaintained' |
161
|
|
|
|
|
|
|
, user_schema => 'authentication' # Not required |
162
|
|
|
|
|
|
|
, user_table => 'login' |
163
|
|
|
|
|
|
|
, user_key => 'id' |
164
|
|
|
|
|
|
|
, user_name => 'name' |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
## Role stuff is not needed if you want to subclass or not use roles |
167
|
|
|
|
|
|
|
, role_table => 'authority' |
168
|
|
|
|
|
|
|
, role_key => 'id' |
169
|
|
|
|
|
|
|
, role_name => 'name' |
170
|
|
|
|
|
|
|
, user_role_table => 'competence' |
171
|
|
|
|
|
|
|
, user_role_user_key => 'login' |
172
|
|
|
|
|
|
|
, user_role_role_key => 'authority' |
173
|
|
|
|
|
|
|
}, |
174
|
|
|
|
|
|
|
}, |
175
|
|
|
|
|
|
|
}, |
176
|
|
|
|
|
|
|
}; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub login :Global { |
179
|
|
|
|
|
|
|
my ($self, $c) = @_; |
180
|
|
|
|
|
|
|
my $req = $c->request(); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# catch login failures |
183
|
|
|
|
|
|
|
unless ($c->authenticate({ |
184
|
|
|
|
|
|
|
'name' => $req->param('name') |
185
|
|
|
|
|
|
|
, 'password' => $req->param('password') |
186
|
|
|
|
|
|
|
})) { |
187
|
|
|
|
|
|
|
... |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
... |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub something :Path { |
194
|
|
|
|
|
|
|
my ($self, $c) = @_; |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# handle missing role case |
197
|
|
|
|
|
|
|
unless ($c->check_user_roles('editor')) { |
198
|
|
|
|
|
|
|
... |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head1 DESCRIPTION |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
This module implements the L<Catalyst::Authentication> API using L<Catalyst::Model::DBI>. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
It uses DBI to let your application authenticate users against a database and it provides support for L<Catalyst::Plugin::Authorization::Roles>. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head2 History |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
This module started off as a patch to L<Catalyst::Authentication::Store::DBI>. I was unable to get ahold of the author, JANUS after he had said that he was willing to cede maintainership. This combined with my inability to provide support on official catalyst mediums -- I credit (mst) Matthew Trout's desire to instigate matters when someone is trying to provide a patch -- leads me to fork. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
You can get official support on this module in on irc.freenode.net's #perlcafe. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head2 Config |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
The store is fully capable of dealing with more complex schemas by utilizing the where condition in C<find_user>. Now, if your role schema is different from the below diagram then simply subclass L<Catalyst::Authentication::Store::DBI::ButMaintained::User> and set C<store_user_class> in the config. Currently, this is probably the most likely reason to subclass the User. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
The C<authenticate> method takes a hash ref that will be used to serialize and unserialize the user if there is no single L<user_key>. Composite keys are not currently supported in L<user_key> |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head3 The default database configuration |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
This module was created for the following configuration: |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
role_table user_role_table |
224
|
|
|
|
|
|
|
=================== =================== |
225
|
|
|
|
|
|
|
role_id | role_name role_id | user_id |
226
|
|
|
|
|
|
|
------------------- ------------------- |
227
|
|
|
|
|
|
|
0 | role 0 | 1 |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
user_table |
230
|
|
|
|
|
|
|
=================== |
231
|
|
|
|
|
|
|
user_id | user_name |
232
|
|
|
|
|
|
|
------------------- |
233
|
|
|
|
|
|
|
0 | Evan "The Man" Carroll |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=head1 METHODS |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head2 new |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head2 find_user |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
Will find a user with provided information |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head2 for_session |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
This does not truely serialize a user from the session. If there is a L<user_key> in the config it saves that users value to a hash; otherwise, it saves the entire authinfo condition from the call to authenticate. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=head2 from_session |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Will either C<find_user> based on the C<user_key>, or C<auth_info> provided to C<authenticate> |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 user_supports |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 get_config( $scalar ) |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
Accessor used for getting to the authentication modules configuration as set in the Catalyst config. |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head2 _safe_escape |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Internal method only: takes a copy of $dbh, and a hash with keys of B<database>, B<schema>, B<table> and B<column> and escapes all that is provided joining them on a period for use in prepaired statements. |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head1 SEE ALSO |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=over 4 |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=item L<Catalyst::Plugin::Authentication> |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=item L<Catalyst::Model::DBI> |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=item L<Catalyst::Plugin::Authorization::Roles> |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=back |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head1 AUTHOR |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Evan Carroll, E<lt>cpan@evancarroll.comE<gt> |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
(v.01) Simon Bertrang, E<lt>simon.bertrang@puzzworks.comE<gt> |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=head1 AUTHOR |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
Copyright (c) 2010 Evan Carroll, L<http://www.evancarroll.com/> |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head2 Original L<Catalyst::Authentication::Store::DBI> |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Copyright (c) 2008 PuzzWorks OHG, L<http://puzzworks.com/> |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head1 LICENSE |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify it under |
290
|
|
|
|
|
|
|
the same terms as Perl itself. |