File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Role/Provider.pm
Criterion Covered Total %
statement 15 15 100.0
branch 3 4 75.0
condition n/a
subroutine 4 4 100.0
pod 2 2 100.0
total 24 25 96.0


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::Extensible::Role::Provider;
2              
3 11     11   353595 use Crypt::SaltedHash;
  11         50728  
  11         472  
4 11     11   94 use Moo::Role;
  11         32  
  11         165  
5             requires qw(authenticate_user);
6              
7             our $VERSION = '0.711';
8              
9             =head1 NAME
10              
11             Dancer2::Plugin::Auth::Extensible::Role::Provider - base role for authentication providers
12              
13             =head1 DESCRIPTION
14              
15             Base L<Moo::Role> for authentication providers.
16              
17             Also provides secure password matching which automatically handles crypted
18             passwords via Crypt::SaltedHash.
19              
20             =head1 ATTRIBUTES
21              
22             =head2 plugin
23              
24             The calling L<Dancer2::Plugin::Auth::Extensible> object.
25              
26             Required.
27              
28             =cut
29              
30             has plugin => (
31             is => 'ro',
32             required => 1,
33             weak_ref => 1,
34             );
35              
36             =head2 disable_roles
37              
38             Defaults to the value of L<Dancer2::Plugin::Auth::Extensible/disable_roles>.
39              
40             =cut
41              
42             has disable_roles => (
43             is => 'ro',
44             lazy => 1,
45             default => sub { $_[0]->plugin->disable_roles },
46             );
47              
48             =head2 encryption_algorithm
49              
50             The encryption_algorithm used by L</encrypt_password>.
51              
52             Defaults to 'SHA-512';
53              
54             =cut
55              
56             has encryption_algorithm => (
57             is => 'ro',
58             default => 'SHA-512',
59             );
60              
61             =head1 METHODS
62              
63             =head2 match_password $given, $correct
64              
65             Matches C<$given> password with the C<$correct> one.
66              
67             =cut
68              
69             sub match_password {
70 62     62 1 21493 my ( $self, $given, $correct ) = @_;
71              
72             # If $correct is undefined, then do not attempt a match, otherwise an
73             # uninnitialized warning will be thrown. If stack trace warnings are
74             # enabled and if the user is using a password that is correct for another
75             # system, then the user's attempted password may be written in logs. This
76             # is certainly an edge-case, but it has happened :)
77             # Also as a safety check, do not allow blank passwords, in case a user has
78             # not set a password yet and a blank password is submitted for
79             # authentication.
80 62 50       199 $correct or return;
81              
82             # TODO: perhaps we should accept a configuration option to state whether
83             # passwords are crypted or not, rather than guessing by looking for the
84             # {...} tag at the start.
85             # I wanted to let it try straightforward comparison first, then try
86             # Crypt::SaltedHash->validate, but that has a weakness: if a list of hashed
87             # passwords got leaked, you could use the hashed password *as it is* to log
88             # in, rather than cracking it first. That's obviously Not Fucking Good.
89             # TODO: think about this more. This shit is important. I'm thinking a
90             # config option to indicate whether passwords are crypted - yes, no, auto
91             # (where auto would do the current guesswork, and yes/no would just do as
92             # told.)
93 62 100       367 if ( $correct =~ /^{.+}/ ) {
94              
95             # Looks like a crypted password starting with the scheme, so try to
96             # validate it with Crypt::SaltedHash:
97 26         306 return Crypt::SaltedHash->validate( $correct, $given );
98             }
99             else {
100             # Straightforward comparison, then:
101 36         168 return $given eq $correct;
102             }
103             }
104              
105             =head2 encrypt_password $password
106              
107             Encrypts password C<$password> with L</encryption_algorithm>
108             and returns the encrypted password.
109              
110             =cut
111              
112             sub encrypt_password {
113 14     14 1 4433 my ( $self, $password ) = @_;
114 14         183 my $crypt =
115             Crypt::SaltedHash->new( algorithm => $self->encryption_algorithm );
116 14         6877 $crypt->add($password);
117 14         224 $crypt->generate;
118             }
119              
120             =head1 METHODS IMPLEMENTED BY PROVIDER
121              
122             The following methods must be implemented by the consuming provider class.
123              
124             =head2 required methods
125              
126             =over
127              
128             =item * authenticate_user $username, $password
129              
130             If either of C<$username> or C<$password> are undefined then die.
131              
132             Return true on success.
133              
134             =back
135              
136             =head2 optional methods
137              
138             The following methods are optional and extend the functionality of the
139             provider.
140              
141             =over
142              
143             =item * get_user_details $username
144              
145             Die if C<$username> is undefined. Otherwise return a user object (if
146             appropriate) or a hash reference of user details.
147              
148             =item * get_user_roles $username
149              
150             Die if C<$username> is undefined. Otherwise return an array reference of
151             user roles.
152              
153             =item * create_user %user
154              
155             Create user with fields specified in C<%user>.
156              
157             Method should croak if C<username> key is empty or undefined. If a user with
158             the specified username already exists then we would normally expect the
159             method to die though this is of course dependent on the backend in use.
160              
161             The new user should be returned.
162              
163             =item * get_user_by_code $code
164              
165             Try to find a user which has C<pw_reset_code> field set to C<$code>.
166              
167             Returns the user on success.
168              
169             =item * set_user_details $username, %update
170              
171             Update user with C<$username> according to C<%update>.
172              
173             Passing an empty or undefined C<$username> should cause the method to die.
174              
175             The update user should be returned.
176              
177             =item * set_user_password $username, $password
178              
179             Set the password for the user specified by C<$username> to <$password>
180             encrypted using L</encrypt_password> or via whatever other method is
181             appropriate for the backend.
182              
183             =item * password_expired $user
184              
185             The C<$user> should be as returned from L</get_user_details>. The method
186             checks whether the user's password has expired and returns 1 if it has and
187             0 if it has not.
188              
189             =back
190              
191             =cut
192              
193             1;
194