File Coverage

lib/Web/Authenticate/User/Storage/Handler/SQL.pm
Criterion Covered Total %
statement 32 76 42.1
branch 2 24 8.3
condition 1 8 12.5
subroutine 8 11 72.7
pod 3 3 100.0
total 46 122 37.7


line stmt bran cond sub pod time code
1 1     1   351 use strict;
  1         2  
  1         41  
2             package Web::Authenticate::User::Storage::Handler::SQL;
3             $Web::Authenticate::User::Storage::Handler::SQL::VERSION = '0.004';
4 1     1   3 use Mouse;
  1         2  
  1         3  
5 1     1   253 use Carp;
  1         1  
  1         40  
6 1     1   494 use DBIx::Raw;
  1         46401  
  1         26  
7 1     1   6 use Web::Authenticate::Digest;
  1         7  
  1         16  
8 1     1   272 use Web::Authenticate::User;
  1         1  
  1         25  
9 1     1   411 use Ref::Util qw/is_hashref/;
  1         423  
  1         583  
10             #ABSTRACT: Implementation of Web::Authenticate::User::Storage::Handler::Role that can be used with MySQL or SQLite.
11              
12             with 'Web::Authenticate::User::Storage::Handler::Role';
13              
14              
15              
16             has users_table => (
17             isa => 'Str',
18             is => 'ro',
19             required => 1,
20             default => 'users',
21             );
22              
23              
24             has id_field => (
25             isa => 'Str',
26             is => 'ro',
27             required => 1,
28             default => 'id',
29             );
30              
31              
32             has username_field => (
33             isa => 'Str',
34             is => 'ro',
35             required => 1,
36             default => 'username',
37             );
38              
39              
40             has password_field => (
41             isa => 'Str',
42             is => 'ro',
43             required => 1,
44             default => 'password',
45             );
46              
47              
48             has digest => (
49             does => 'Web::Authenticate::Digest::Role',
50             is => 'ro',
51             required => 1,
52             default => sub { Web::Authenticate::Digest->new },
53             );
54              
55              
56             has dbix_raw => (
57             isa => 'DBIx::Raw',
58             is => 'ro',
59             required => 1,
60             );
61              
62              
63             has columns => (
64             isa => 'ArrayRef',
65             is => 'ro',
66             required => 1,
67             default => sub { [] },
68             );
69              
70              
71             sub load_user {
72 0     0 1 0 my ($self, $username, $password) = @_;
73 0 0       0 croak "must provide username" unless $username;
74 0 0       0 croak "must provide password" unless $password;
75              
76 0         0 my $users_table = $self->users_table;
77 0         0 my $id_field = $self->id_field;
78 0         0 my $username_field = $self->username_field;
79 0         0 my $password_field = $self->password_field;
80              
81 0         0 my $selection = $self->_get_selection($id_field, $password_field);
82 0         0 my $user = $self->dbix_raw->raw("SELECT $selection FROM $users_table WHERE $username_field = ?", $username);
83              
84 0 0 0     0 unless ($user->{$id_field} and $user->{$password_field} and
      0        
85             $self->digest->validate($user->{$password_field}, $password)) {
86 0         0 carp "unable to load user";
87 0         0 return;
88             }
89              
90 0         0 delete $user->{$password_field};
91              
92 0         0 return Web::Authenticate::User->new(id => $user->{$id_field}, row => $user);
93             }
94              
95              
96             sub load_user_by_id {
97 0     0 1 0 my ($self, $user_id) = @_;
98 0 0       0 croak "must provide user_id" unless $user_id;
99              
100 0         0 my $users_table = $self->users_table;
101 0         0 my $id_field = $self->id_field;
102 0         0 my $username_field = $self->username_field;
103 0         0 my $password_field = $self->password_field;
104              
105 0         0 my $selection = $self->_get_selection($id_field);
106 0         0 my $user = $self->dbix_raw->raw("SELECT $selection FROM $users_table WHERE $id_field = ?", $user_id);
107              
108 0 0       0 unless ($user) {
109 0         0 carp "unable to load user";
110 0         0 return;
111             }
112              
113             # if the only thing we selected was id, then we just got a scalar back
114 0 0       0 unless (is_hashref($user)) {
115 0         0 $user = {$id_field => $user};
116             }
117              
118 0         0 return Web::Authenticate::User->new(id => $user->{$id_field}, row => $user);
119             }
120              
121              
122             sub store_user {
123 1     1 1 5 my ($self, $username, $password, $user_values) = @_;
124 1 50       3 croak "must provide username" unless $username;
125 1 50       3 croak "must provide password" unless $password;
126              
127 1         3 my $users_table = $self->users_table;
128 1         3 my $id_field = $self->id_field;;
129 1         2 my $username_field = $self->username_field;
130 1         2 my $password_field = $self->password_field;
131              
132 1   50     5 $user_values //= {};
133 1         2 $user_values->{$username_field} = $username;
134 1         4 $user_values->{$password_field} = $self->digest->generate($password);;
135              
136 1         211751 $self->dbix_raw->insert(href => $user_values, table => $users_table);
137              
138             # select columns user wants here instead except password_hash. Then pass into extra field
139 0           my $selection = $self->_get_selection($id_field);
140 0           my $user = $self->dbix_raw->raw("SELECT $selection FROM $users_table WHERE $username_field = ?", $username);
141              
142 0 0         unless ($user) {
143 0           carp "unable to load user";
144 0           return;
145             }
146              
147             # if the only thing we selected was id, then we just got a scalar back
148 0 0         unless (is_hashref($user)) {
149 0           $user = {$id_field => $user};
150             }
151              
152 0           return Web::Authenticate::User->new(id => $user->{$id_field}, row => $user);
153             }
154              
155             sub _get_selection {
156 0     0     my ($self, @columns) = @_;
157              
158 0 0         die "columns required" unless @columns;
159              
160 0           my $selection = join ',', @columns;
161              
162 0 0         if (@{$self->columns}) {
  0            
163 0           $selection .= ',' . join ',', @{$self->columns};
  0            
164             }
165              
166 0           return $selection;
167             }
168              
169             1;
170              
171             __END__
172              
173             =pod
174              
175             =encoding UTF-8
176              
177             =head1 NAME
178              
179             Web::Authenticate::User::Storage::Handler::SQL - Implementation of Web::Authenticate::User::Storage::Handler::Role that can be used with MySQL or SQLite.
180              
181             =head1 VERSION
182              
183             version 0.004
184              
185             =head1 DESCRIPTION
186              
187             This L<Web::Authenticate::User::Storage::Handler::Role> is meant to be used with a very specific table structure:
188              
189             CREATE TABLE users (
190             id INTEGER PRIMARY KEY AUTO_INCREMENT,
191             username VARCHAR(255) NOT NULL UNIQUE,
192             password TEXT NOT NULL
193             );
194              
195             Other columns can exists, and the names of the table and column can change. But at least these columns must exist
196             in order for this storage handler to work properly. Also, the primary key does not have to be an integer, and could
197             even be the username.
198              
199             =head1 METHODS
200              
201             =head2 users_table
202              
203             Sets the name of the users table that will be used when querying the database. Default is 'users'.
204              
205             =head2 id_field
206              
207             Sets the name of the id field that will be used when querying the database. Default is 'id'.
208              
209             =head2 username_field
210              
211             Sets the name of the username field that will be used when querying the database. Default is 'username'.
212              
213             =head2 password_field
214              
215             Sets the name of the password field that will be used when querying the database. Default is 'password'.
216              
217             =head2 digest
218              
219             Sets the L<Web::Authenticate::Digest::Role> that is used. Default is L<Web::Authenticate::Digest>.
220              
221             =head2 dbix_raw
222              
223             Sets the L<DBIx::Raw> object that will be used to query the database. This is required with no default.
224              
225             =head2 columns
226              
227             The columns to select. At a minimum, L</id_field> and L</password_field> will always be selected.
228              
229             $user_storage_handler->columns([qw/name age/]);
230              
231             Default is an empty array ref.
232              
233             =head2 load_user
234              
235             Accepts the username and password for a user, and returns a L<Web::Authenticate::User> if the password is correct and the user exists.
236             Otherwise, undef is returned. Any additional L</columns> will be stored in L<Web::Authenticate::User/row>. However, the password will be
237             deleted from the row before it is stored.
238              
239             my $user = $user_storage_handler->load_user($username, $password);
240              
241             =head2 load_user_by_id
242              
243             Loads a user by id.
244              
245             my $user = $user_storage_handler->load_user_by_id($user_id);
246              
247             =head2 store_user
248              
249             Takes in username, password, and any additional values for columns in a hash and we create that user with their hashed password.
250             Returns a L<Web::Authenticate::User> with any values from L</columns> stored in L<Web::Authenticate::User/row>.
251              
252             my $user_values => {
253             name => 'Fred',
254             age => 34,
255             insert_time => \'NOW()', # scalar ref for literal values. See DBIx::Raw
256             };
257             my $user = $user_storage_handler->store_user($username, $password, $user_vaules);
258              
259             # if you need no extra user values in the row
260             my $user = $user_storage_handler->store_user($username, $password);
261              
262             =head1 AUTHOR
263              
264             Adam Hopkins <srchulo@cpan.org>
265              
266             =head1 COPYRIGHT AND LICENSE
267              
268             This software is copyright (c) 2017 by Adam Hopkins.
269              
270             This is free software; you can redistribute it and/or modify it under
271             the same terms as the Perl 5 programming language system itself.
272              
273             =cut