File Coverage

blib/lib/Catalyst/Authentication/Store/DBI/ButMaintained.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


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.