File Coverage

blib/lib/Articulate/Authentication/Internal.pm
Criterion Covered Total %
statement 15 39 38.4
branch 0 6 0.0
condition 0 7 0.0
subroutine 5 11 45.4
pod 4 4 100.0
total 24 67 35.8


line stmt bran cond sub pod time code
1             package Articulate::Authentication::Internal;
2 6     6   2914 use strict;
  6         10  
  6         194  
3 6     6   25 use warnings;
  6         7  
  6         116  
4              
5 6     6   20 use Moo;
  6         13  
  6         28  
6              
7 6     6   5007 use Digest::SHA;
  6         15461  
  6         323  
8 6     6   20434 use Time::HiRes; # overrides time()
  6         8714  
  6         76  
9              
10             =head1 NAME
11              
12             Articulate::Authentication::Internal
13              
14             =cut
15              
16             =head1 METHODS
17              
18             =cut
19              
20             =head3 authenticate
21              
22             $self->authenticate( $credentials );
23              
24             Accepts and returns the credentials if the C matches the C. Always returns the credentials passed in.
25              
26             =cut
27              
28             has extra_salt => (
29             is => 'rw',
30             default => "If you haven't already, try powdered vegetable bouillon"
31             );
32              
33             sub authenticate {
34 0     0 1   my $self = shift;
35 0           my $credentials = shift;
36 0   0       my $user_id = $credentials->fields->{user_id} // return;
37 0   0       my $password = $credentials->fields->{password} // return;
38              
39 0 0         if ( $self->verify_password( $user_id, $password ) ) {
40 0           return $credentials->accept('Passwords match');
41             }
42              
43             # if we ever need to know if the user does not exist, now is the time to ask,
44             # but we do not externally expose the difference between
45             # "user not found" and "password doesn't match"
46 0           return $credentials;
47             }
48              
49             sub _password_salt_and_hash {
50 0     0     my $self = shift;
51 0           return Digest::SHA::sha512_base64(
52             $_[0] . $_[1] #:5.10 doesn't like shift . shift
53             );
54             }
55              
56             sub _generate_salt {
57              
58             # pseudorandom salt
59 0     0     my $self = shift;
60 0           return Digest::SHA::sha512_base64(
61             time . (
62             $self->extra_salt # don't allow the admin not to set a salt:
63             )
64             );
65             }
66              
67             =head3 verify_password
68              
69             $self->verify_password( $user_id, $password );
70              
71             Hashes the password provided with the user's salt and checks to see if the string matches the encrypted password in the user's meta.
72              
73             Returns the result of C.
74              
75             =cut
76              
77             sub verify_password {
78 0     0 1   my ( $self, $user_id, $plaintext_password ) = @_;
79              
80 0           my $user_meta = $self->storage->get_meta("/users/$user_id");
81 0           my $real_encrypted_password = $user_meta->{encrypted_password};
82 0           my $salt = $user_meta->{salt};
83              
84             return undef
85 0 0 0       unless defined $real_encrypted_password and defined $plaintext_password;
86              
87 0           return ( $real_encrypted_password eq
88             $self->_password_salt_and_hash( $plaintext_password, $salt ) );
89             }
90              
91             =head3 set_password
92              
93             $self->set_password( $user_id, $password );
94              
95             Creates a new pseudorandom salt and uses it to hash the password provided.
96              
97             Amends the C and C fields of the user's meta.
98              
99             =cut
100              
101             # note: currently this implicitly creates a user. Should set/patch create new content, or just edit it?
102             # maybe a create verb - but is is this going to be compatible with kvp stores? How will this work when you have content and meta and settings all to be created?
103             sub set_password {
104 0     0 1   my ( $self, $user_id, $plaintext_password ) = @_;
105             return undef
106 0 0         unless $plaintext_password; # as empty passwords will only cause trouble.
107 0           my $new_salt = $self->_generate_salt;
108 0           $self->storage->patch_meta(
109             "/user/$user_id",
110             {
111             encrypted_password =>
112             $self->_password_salt_and_hash( $plaintext_password, $new_salt ),
113             salt => $new_salt
114             }
115             );
116             }
117              
118             =head3 create_user
119              
120             $self->create_user( $user_id, $password );
121              
122             Creates a new user and sets the C and C fields of the user's meta.
123              
124             =cut
125              
126             sub create_user {
127 0     0 1   my ( $self, $user_id, $plaintext_password ) = @_;
128 0           $self->storage->create("/user/$user_id");
129 0           $self->storage->set_password( $user_id, $plaintext_password );
130             }
131              
132             1;