File Coverage

blib/lib/Catalyst/Authentication/Store/DBI/ButMaintained.pm
Criterion Covered Total %
statement 18 54 33.3
branch 0 14 0.0
condition 0 9 0.0
subroutine 6 12 50.0
pod 5 5 100.0
total 29 94 30.8


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Store::DBI::ButMaintained;
2 1     1   70535 use strict;
  1         3  
  1         31  
3 1     1   5 use warnings;
  1         3  
  1         24  
4 1     1   537 use namespace::autoclean;
  1         18506  
  1         5  
5              
6 1     1   756 use Storable;
  1         3325  
  1         57  
7 1     1   630 use Moose;
  1         469142  
  1         8  
8 1     1   8679 use MooseX::Types::LoadableClass qw/ClassName/;
  1         113829  
  1         10  
9              
10             our $VERSION = '0.02_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 0     0 1   my ($self, $authinfo, $c) = @_;
39 0           my $dbh = $c->model('DBI')->dbh;
40              
41 0           my @col = map { $_ } sort keys %$authinfo;
  0            
42              
43             my $abs_user_dest = $self->_safe_escape(
44             $dbh
45 0           , {map { $_ => $self->get_config("user_$_") } qw/database schema table/}
  0            
46             );
47              
48 0           my $sql = "SELECT * FROM $abs_user_dest WHERE "
49             . join( ' AND ', map $dbh->quote_identifier($_) . " = ?", @col )
50             ;
51              
52 0 0         my $sth = $dbh->prepare($sql) or die($dbh->errstr());
53 0 0         $sth->execute(@$authinfo{@col}) or die($dbh->errstr());
54              
55 0           my %user;
56 0 0         $sth->bind_columns(\( @user{ @{ $sth->{'NAME_lc'} } } )) or
  0            
57             die($dbh->errstr());
58 0 0         unless ($sth->fetch()) {
59 0           $sth->finish();
60 0           return undef;
61             }
62 0           $sth->finish();
63              
64             ## Fail silently clause
65             return undef
66             unless exists $user{$self->get_config('user_key')}
67 0 0 0       && length $user{$self->get_config('user_key')}
68             ;
69              
70 0           my $class = $self->store_user_class;
71 0           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 0     0     my $self = shift;
82 0           my ( $dbh, $unescaped ) = @_;
83              
84             join '.'
85             , map $dbh->quote_identifier( $unescaped->{$_} )
86 0   0       , 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 0     0 1   my $self = shift;
98 0           my ( $c, $user) = @_;
99              
100             ## TODO: Freeze whole user, this should just be fallback
101 0 0 0       if (
102             exists $self->config->{user_key}
103             && $user->get( $self->get_config('user_key') )
104             ) {
105 0           my $k = $self->get_config('user_key');
106 0           my $uid = $user->get( $k );
107 0           return Storable::nfreeze({ $k => $uid });
108             }
109             ## Support users with composite key
110             else {
111 0           return Storable::nfreeze( $user->authinfo );
112             }
113              
114             }
115              
116             sub from_session {
117 0     0 1   my $self = shift;
118 0           my ( $c, $frozen ) = @_;
119 0           $self->find_user( Storable::thaw($frozen), $c );
120             }
121              
122             sub user_supports {
123 0     0 1   return;
124             }
125              
126             sub BUILDARGS {
127 0     0 1   my $class = shift;
128 0           my ( $config, $app, $realm ) = @_;
129              
130 0 0         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             ...
202             }
203              
204             =head1 DESCRIPTION
205              
206             This module implements the L<Catalyst::Authentication> API using L<Catalyst::Model::DBI>.
207              
208             It uses DBI to let your application authenticate users against a database and it provides support for L<Catalyst::Plugin::Authorization::Roles>.
209              
210             =head2 History
211              
212             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.
213              
214             You can get official support on this module in on irc.freenode.net's #perlcafe.
215              
216             =head2 Config
217              
218             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.
219              
220             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>
221              
222             =head3 The default database configuration
223              
224             This module was created for the following configuration:
225              
226             role_table user_role_table
227             =================== ===================
228             role_id | role_name role_id | user_id
229             ------------------- -------------------
230             0 | role 0 | 1
231              
232             user_table
233             ===================
234             user_id | user_name
235             -------------------
236             0 | Evan "The Man" Carroll
237              
238             =head1 METHODS
239              
240             =head2 new
241              
242             =head2 find_user
243              
244             Will find a user with provided information
245              
246             =head2 for_session
247              
248             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.
249              
250             =head2 from_session
251              
252             Will either C<find_user> based on the C<user_key>, or C<auth_info> provided to C<authenticate>
253              
254             =head2 user_supports
255              
256             =head2 get_config( $scalar )
257              
258             Accessor used for getting to the authentication modules configuration as set in the Catalyst config.
259              
260             =head2 _safe_escape
261              
262             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.
263              
264             =head1 SEE ALSO
265              
266             =over 4
267              
268             =item L<Catalyst::Plugin::Authentication>
269              
270             =item L<Catalyst::Model::DBI>
271              
272             =item L<Catalyst::Plugin::Authorization::Roles>
273              
274             =back
275              
276             =head1 AUTHOR
277              
278             Evan Carroll, E<lt>cpan@evancarroll.comE<gt>
279              
280             (v.01) Simon Bertrang, E<lt>simon.bertrang@puzzworks.comE<gt>
281              
282             =head1 COPYRIGHT
283              
284             Copyright (c) 2010 Evan Carroll, L<http://www.evancarroll.com/>
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.