| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package WWW::Suffit::Plugin::FileAuth; |
|
2
|
1
|
|
|
1
|
|
154577
|
use strict; |
|
|
1
|
|
|
|
|
6
|
|
|
|
1
|
|
|
|
|
45
|
|
|
3
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
73
|
|
|
4
|
1
|
|
|
1
|
|
868
|
use utf8; |
|
|
1
|
|
|
|
|
311
|
|
|
|
1
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=encoding utf8 |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 NAME |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
WWW::Suffit::Plugin::FileAuth - The Suffit plugin for authentication and authorization by password file |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub startup { |
|
15
|
|
|
|
|
|
|
my $self = shift->SUPER::startup(); |
|
16
|
|
|
|
|
|
|
$self->plugin('FileAuth', { |
|
17
|
|
|
|
|
|
|
configsection => 'auth', |
|
18
|
|
|
|
|
|
|
}); |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# . . . |
|
21
|
|
|
|
|
|
|
} |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
... configuration: |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# FileAuth configuration |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
AuthUserFile /etc/myapp/passwd.db |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
This plugin provides authentication and authorization by looking up users in plain text password files |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
The C configuration directive sets the path to the user file of a textual file containing the list of users and passwords |
|
35
|
|
|
|
|
|
|
for user authentication. |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
If it is not absolute, it is treated as relative to the project C directory. |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
By default use C file name |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Each line of the user file contains a username followed by a colon, followed by the encrypted password. |
|
42
|
|
|
|
|
|
|
If the same user ID is defined multiple times, plugin will use the first occurrence to verify the password. |
|
43
|
|
|
|
|
|
|
Try to avoid such cases! |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
The encrypted password format depends on which length of this encrypted-string and character-set: |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
md5 32 hex digits and chars |
|
48
|
|
|
|
|
|
|
sha1 40 hex digits and chars |
|
49
|
|
|
|
|
|
|
sha224 56 hex digits and chars |
|
50
|
|
|
|
|
|
|
sha256 64 hex digits and chars |
|
51
|
|
|
|
|
|
|
sha384 96 hex digits and chars |
|
52
|
|
|
|
|
|
|
sha512 128 hex digits and chars |
|
53
|
|
|
|
|
|
|
unsafe plain text otherwise |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Also, each line of the user file can contain parameters in the C format |
|
56
|
|
|
|
|
|
|
(L), which must be placed at the end of the line with |
|
57
|
|
|
|
|
|
|
a leading colon character, which is the delimiter |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
For example: |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
admin:5f4dcc3b5aa765d61d8327deb882cf99 |
|
62
|
|
|
|
|
|
|
test:5f4dcc3b5aa765d61d8327deb882cf99:uid=1&name=Test%20user |
|
63
|
|
|
|
|
|
|
anonymous:password |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head1 OPTIONS |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
This plugin supports the following options |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 configsection |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
configsection => 'auth' |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
This option sets a section name of the config file for define |
|
74
|
|
|
|
|
|
|
namespace of configuration directives for this plugin |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Default: none (without section) |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head1 HELPERS |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
This plugin provides the following helpers |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 fileauth.init |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
my $init = $self->fileauth->init; |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
This method returns the init object (L) |
|
87
|
|
|
|
|
|
|
that contains data of initialization: |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
{ |
|
90
|
|
|
|
|
|
|
error => '...', # Error message |
|
91
|
|
|
|
|
|
|
status => 500, # HTTP status code |
|
92
|
|
|
|
|
|
|
code => 'E7000', # The Suffit error code |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
For example (in your controller): |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# Check init status |
|
98
|
|
|
|
|
|
|
my $init = $self->fileauth->init; |
|
99
|
|
|
|
|
|
|
if (my $err = $init->get('/error')) { |
|
100
|
|
|
|
|
|
|
$self->reply->error($init->get('/status'), |
|
101
|
|
|
|
|
|
|
$init->get('/code'), $err); |
|
102
|
|
|
|
|
|
|
return; |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head2 fileauth.authenticate |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
my $auth = $self->fileauth->authenticate({ |
|
108
|
|
|
|
|
|
|
username => $username, |
|
109
|
|
|
|
|
|
|
password => $password, |
|
110
|
|
|
|
|
|
|
loginpage => 'login', # -- To login-page!! |
|
111
|
|
|
|
|
|
|
expiration => $remember ? SESSION_EXPIRE_MAX : SESSION_EXPIRATION, |
|
112
|
|
|
|
|
|
|
realm => "Test zone", |
|
113
|
|
|
|
|
|
|
}); |
|
114
|
|
|
|
|
|
|
if (my $err = $auth->get('/error')) { |
|
115
|
|
|
|
|
|
|
if (my $location = $auth->get('/location')) { # Redirect |
|
116
|
|
|
|
|
|
|
$self->flash(message => $err); |
|
117
|
|
|
|
|
|
|
$self->redirect_to($location); # 'login' -- To login-page!! |
|
118
|
|
|
|
|
|
|
} elsif ($auth->get('/status') >= 500) { # Fatal server errors |
|
119
|
|
|
|
|
|
|
$self->reply->error($auth->get('/status'), $auth->get('/code'), $err); |
|
120
|
|
|
|
|
|
|
} else { # User errors (show on login page) |
|
121
|
|
|
|
|
|
|
$self->stash(error => $err); |
|
122
|
|
|
|
|
|
|
return $self->render; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
return; |
|
125
|
|
|
|
|
|
|
} |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
This helper performs authentication backend subprocess and returns |
|
128
|
|
|
|
|
|
|
result object (L) that contains data structure: |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
{ |
|
131
|
|
|
|
|
|
|
error => '', # Error message |
|
132
|
|
|
|
|
|
|
status => 200, # HTTP status code |
|
133
|
|
|
|
|
|
|
code => 'E0000', # The Suffit error code |
|
134
|
|
|
|
|
|
|
username => $username, # User name |
|
135
|
|
|
|
|
|
|
referer => $referer, # Referer |
|
136
|
|
|
|
|
|
|
loginpage => $loginpage, # Login page for redirects (location) |
|
137
|
|
|
|
|
|
|
location => undef, # Location URL for redirects |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=head2 fileauth.authorize |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
my $auth = $self->fileauth->authorize({ |
|
143
|
|
|
|
|
|
|
referer => $referer, |
|
144
|
|
|
|
|
|
|
username => $username, |
|
145
|
|
|
|
|
|
|
loginpage => 'login', # -- To login-page!! |
|
146
|
|
|
|
|
|
|
}); |
|
147
|
|
|
|
|
|
|
if (my $err = $auth->get('/error')) { |
|
148
|
|
|
|
|
|
|
if (my $location = $auth->get('/location')) { |
|
149
|
|
|
|
|
|
|
$self->flash(message => $err); |
|
150
|
|
|
|
|
|
|
$self->redirect_to($location); # 'login' -- To login-page!! |
|
151
|
|
|
|
|
|
|
} else { |
|
152
|
|
|
|
|
|
|
$self->reply->error($auth->get('/status'), $auth->get('/code'), $err); |
|
153
|
|
|
|
|
|
|
} |
|
154
|
|
|
|
|
|
|
return; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
This helper performs authorization backend subprocess and returns |
|
158
|
|
|
|
|
|
|
result object (L) that contains data structure: |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
{ |
|
161
|
|
|
|
|
|
|
error => '', # Error message |
|
162
|
|
|
|
|
|
|
status => 200, # HTTP status code |
|
163
|
|
|
|
|
|
|
code => 'E0000', # The Suffit error code |
|
164
|
|
|
|
|
|
|
username => $username, # User name |
|
165
|
|
|
|
|
|
|
referer => $referer, # Referer |
|
166
|
|
|
|
|
|
|
loginpage => $loginpage, # Login page for redirects (location) |
|
167
|
|
|
|
|
|
|
location => undef, # Location URL for redirects |
|
168
|
|
|
|
|
|
|
user => { # User data |
|
169
|
|
|
|
|
|
|
address => "127.0.0.1", # User (client) IP address |
|
170
|
|
|
|
|
|
|
base => "http://localhost:8080", # Base URL of request |
|
171
|
|
|
|
|
|
|
comment => "No comments", # Comment |
|
172
|
|
|
|
|
|
|
email => 'test@example.com', # Email address |
|
173
|
|
|
|
|
|
|
email_md5 => "a84450...366", # MD5 hash of email address |
|
174
|
|
|
|
|
|
|
method => "ANY", # Current method of request |
|
175
|
|
|
|
|
|
|
name => "Bob Smith", # Full user name |
|
176
|
|
|
|
|
|
|
path => "/", # Current query-path of request |
|
177
|
|
|
|
|
|
|
role => "Regular user", # User role |
|
178
|
|
|
|
|
|
|
status => true, # User status in JSON::PP::Boolean notation |
|
179
|
|
|
|
|
|
|
uid => 1, # User ID |
|
180
|
|
|
|
|
|
|
username => $username, # User name |
|
181
|
|
|
|
|
|
|
}, |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head1 METHODS |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Internal methods |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head2 register |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
This method register the plugin and helpers in L application |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
L, L |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head1 AUTHOR |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Copyright (C) 1998-2026 D&D Corporation |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head1 LICENSE |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
This program is distributed under the terms of the Artistic License Version 2.0 |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
See the C file or L for details |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
|
211
|
|
|
|
|
|
|
|
|
212
|
1
|
|
|
1
|
|
892
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
|
1
|
|
|
|
|
15562
|
|
|
|
1
|
|
|
|
|
11
|
|
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
our $VERSION = '1.01'; |
|
215
|
|
|
|
|
|
|
|
|
216
|
1
|
|
|
1
|
|
2919
|
use Digest::SHA qw/sha224_hex sha256_hex sha384_hex sha512_hex/; |
|
|
1
|
|
|
|
|
4824
|
|
|
|
1
|
|
|
|
|
388
|
|
|
217
|
1
|
|
|
1
|
|
672
|
use Mojo::File qw/path/; |
|
|
1
|
|
|
|
|
318029
|
|
|
|
1
|
|
|
|
|
120
|
|
|
218
|
1
|
|
|
1
|
|
14
|
use Mojo::Util qw/trim encode md5_sum sha1_sum hmac_sha1_sum secure_compare/; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
113
|
|
|
219
|
1
|
|
|
1
|
|
877
|
use Mojo::JSON::Pointer; |
|
|
1
|
|
|
|
|
1129
|
|
|
|
1
|
|
|
|
|
11
|
|
|
220
|
1
|
|
|
1
|
|
812
|
use Mojo::Parameters; |
|
|
1
|
|
|
|
|
3284
|
|
|
|
1
|
|
|
|
|
9
|
|
|
221
|
1
|
|
|
1
|
|
884
|
use WWW::Suffit::Const qw/ :session /; |
|
|
1
|
|
|
|
|
3623
|
|
|
|
1
|
|
|
|
|
7429
|
|
|
222
|
1
|
|
|
1
|
|
582
|
use WWW::Suffit::Util qw/json_load json_save/; |
|
|
1
|
|
|
|
|
71027
|
|
|
|
1
|
|
|
|
|
158
|
|
|
223
|
|
|
|
|
|
|
|
|
224
|
1
|
|
|
1
|
|
12
|
use constant PASSWD_FILENAME => 'passwd.db'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
3796
|
|
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub register { |
|
227
|
0
|
|
|
0
|
1
|
|
my ($plugin, $app, $opts) = @_; # $self = $plugin |
|
228
|
0
|
|
0
|
|
|
|
$opts //= {}; |
|
229
|
0
|
|
|
|
|
|
my $configsection = $opts->{configsection}; |
|
230
|
0
|
|
|
|
|
|
my %payload = ( # Ok by default |
|
231
|
|
|
|
|
|
|
error => '', # Error message |
|
232
|
|
|
|
|
|
|
status => 200, # HTTP status code |
|
233
|
|
|
|
|
|
|
code => 'E0000', # The Suffit error code |
|
234
|
|
|
|
|
|
|
); |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Load pwdb file |
|
237
|
0
|
|
|
|
|
|
my @users = (); |
|
238
|
0
|
0
|
|
|
|
|
my $pwdb_file = $configsection |
|
239
|
|
|
|
|
|
|
? $app->conf->latest("/$configsection/authuserfile") |
|
240
|
|
|
|
|
|
|
: $app->conf->latest("/authuserfile"); |
|
241
|
0
|
|
0
|
|
|
|
$pwdb_file ||= path($app->app->datadir, PASSWD_FILENAME)->to_string; |
|
242
|
0
|
0
|
|
|
|
|
$pwdb_file = path($app->app->datadir, $pwdb_file)->to_string unless path($pwdb_file)->is_abs; |
|
243
|
0
|
0
|
|
|
|
|
if (-e $pwdb_file) { |
|
244
|
0
|
0
|
|
|
|
|
if (open my $records, '<', $pwdb_file) { |
|
245
|
0
|
|
|
|
|
|
while(<$records>) { |
|
246
|
0
|
|
|
|
|
|
chomp; |
|
247
|
0
|
0
|
|
|
|
|
next unless $_; |
|
248
|
0
|
|
|
|
|
|
my $l = trim($_); |
|
249
|
0
|
0
|
|
|
|
|
next unless $l; |
|
250
|
0
|
0
|
|
|
|
|
next if $l =~ /^[#;]/; |
|
251
|
0
|
|
|
|
|
|
push @users, $l; |
|
252
|
|
|
|
|
|
|
} |
|
253
|
0
|
|
|
|
|
|
close $records; |
|
254
|
|
|
|
|
|
|
} else { |
|
255
|
0
|
|
|
|
|
|
$app->log->error(sprintf("[E7000] Error opening password file \"%s\: %s", $pwdb_file, $!)); |
|
256
|
0
|
|
|
|
|
|
$payload{error} = "Error opening password file: $!"; |
|
257
|
0
|
|
|
|
|
|
$payload{status} = 500; |
|
258
|
0
|
|
|
|
|
|
$payload{code} = 'E7000'; |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
} else { |
|
261
|
0
|
|
|
|
|
|
$app->log->error(sprintf("[E7000] Password file \"%s\" not found", $pwdb_file)); |
|
262
|
0
|
|
|
|
|
|
$payload{error} = "Password file not found"; |
|
263
|
0
|
|
|
|
|
|
$payload{status} = 500; |
|
264
|
0
|
|
|
|
|
|
$payload{code} = 'E7000'; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# List of users from config |
|
268
|
0
|
|
|
0
|
|
|
$app->helper('fileauth.users' => sub { \@users }); |
|
|
0
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# Auth helpers (methods) |
|
271
|
0
|
|
|
|
|
|
$app->helper('fileauth.authenticate'=> \&_authenticate); |
|
272
|
0
|
|
|
|
|
|
$app->helper('fileauth.authorize' => \&_authorize); |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Return with errors |
|
275
|
0
|
|
|
0
|
|
|
return $app->helper('fileauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) }) |
|
276
|
0
|
0
|
|
|
|
|
if $payload{error}; |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Check users |
|
279
|
0
|
0
|
|
|
|
|
unless (scalar @users) { |
|
280
|
0
|
|
|
|
|
|
$app->log->error(sprintf("[E7010] No any users found in password file \"%s\"", $pwdb_file)); |
|
281
|
0
|
|
|
|
|
|
$payload{error} = "No any users found in password file"; |
|
282
|
0
|
|
|
|
|
|
$payload{status} = 500; |
|
283
|
0
|
|
|
|
|
|
$payload{code} = 'E7010'; |
|
284
|
0
|
|
|
0
|
|
|
return $app->helper('fileauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) }); |
|
|
0
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
} |
|
286
|
|
|
|
|
|
|
#$app->log->error(Mojo::Util::dumper($users)); |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Ok |
|
289
|
0
|
|
|
0
|
|
|
return $app->helper('fileauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) }); |
|
|
0
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
sub _authenticate { |
|
292
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
293
|
0
|
0
|
|
|
|
|
my %args = scalar(@_) ? scalar(@_) % 2 ? ref($_[0]) eq 'HASH' ? (%{$_[0]}) : () : (@_) : (); |
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
|
my $cache = $self->app->cache; |
|
295
|
0
|
|
|
|
|
|
my $now = time(); |
|
296
|
0
|
|
0
|
|
|
|
my $username = $args{username} || ''; |
|
297
|
0
|
|
0
|
|
|
|
my $password = $args{password} // ''; |
|
298
|
0
|
0
|
|
|
|
|
$password = encode('UTF-8', $password) if length $password; # chars to bytes |
|
299
|
0
|
|
0
|
|
|
|
my $referer = $args{referer} // $self->req->headers->header("Referer") // ''; |
|
|
|
|
0
|
|
|
|
|
|
300
|
0
|
|
0
|
|
|
|
my $loginpage = $args{loginpage} // ''; |
|
301
|
0
|
|
0
|
|
|
|
my $expiration = $args{expiration} || 0; |
|
302
|
0
|
|
|
|
|
|
my %payload = ( # Ok by default |
|
303
|
|
|
|
|
|
|
error => '', # Error message |
|
304
|
|
|
|
|
|
|
status => 200, # HTTP status code |
|
305
|
|
|
|
|
|
|
code => 'E0000', # The Suffit error code |
|
306
|
|
|
|
|
|
|
username => $username, # User name |
|
307
|
|
|
|
|
|
|
referer => $referer, # Referer |
|
308
|
|
|
|
|
|
|
loginpage => $loginpage, # Login page for redirects (location) |
|
309
|
|
|
|
|
|
|
location => undef, # Location URL for redirects |
|
310
|
|
|
|
|
|
|
); |
|
311
|
0
|
|
|
|
|
|
my $json_file = path($self->app->datadir, sprintf("u.%s.json", $username)); |
|
312
|
0
|
|
|
|
|
|
my $file = $json_file->to_string; |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Check username |
|
315
|
0
|
0
|
|
|
|
|
unless (length $username) { |
|
316
|
0
|
|
|
|
|
|
$self->log->error("[E7001] Incorrect username"); |
|
317
|
0
|
|
|
|
|
|
$payload{error} = "Incorrect username"; |
|
318
|
0
|
|
|
|
|
|
$payload{status} = 400; |
|
319
|
0
|
|
|
|
|
|
$payload{code} = 'E7001'; |
|
320
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Get user key and file |
|
324
|
0
|
|
|
|
|
|
my $ustat_key = sprintf("auth.ustat.%s", hmac_sha1_sum(sprintf("%s:%s", encode('UTF-8', $username), $password), $self->app->mysecret)); |
|
325
|
0
|
|
0
|
|
|
|
my $ustat_tm = $cache->get($ustat_key) || 0; |
|
326
|
0
|
0
|
0
|
|
|
|
if ($expiration && (-e $file) && ($ustat_tm + $expiration) > $now) { # Ok! |
|
|
|
|
0
|
|
|
|
|
|
327
|
0
|
|
|
|
|
|
$self->log->debug(sprintf("$$: User data is still valid. Expired at %s", scalar(localtime($ustat_tm + $expiration)))); |
|
328
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# Get password database from cache |
|
332
|
0
|
|
|
|
|
|
my $pwdb = $cache->get('auth.pwdb'); |
|
333
|
0
|
0
|
|
|
|
|
unless ($pwdb) { |
|
334
|
0
|
|
0
|
|
|
|
my $users = $self->fileauth->users || []; |
|
335
|
0
|
|
|
|
|
|
$pwdb = { (_parse_pwdb_lines(@$users)) }; |
|
336
|
0
|
|
|
|
|
|
$cache->set('auth.pwdb' => $pwdb); # store whole password database to cache |
|
337
|
|
|
|
|
|
|
#$self->log->error(Mojo::Util::dumper( $pwdb )); |
|
338
|
|
|
|
|
|
|
} |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# Authentication: Check by password database |
|
341
|
0
|
|
0
|
|
|
|
my $pw = encode('UTF-8', $pwdb->{$username}->{pwd} // ''); |
|
342
|
0
|
|
0
|
|
|
|
my $ar = Mojo::Parameters->new($pwdb->{$username}->{arg} // '')->charset('UTF-8'); |
|
343
|
0
|
0
|
|
|
|
|
unless (_check_pw($password, $pw)) { # Oops. Incorrect username/password |
|
344
|
0
|
|
|
|
|
|
$self->log->error(sprintf("[%s] %s: %s", 401, 'E7005', 'Incorrect username/password')); |
|
345
|
0
|
|
|
|
|
|
$payload{error} = 'Incorrect username/password'; |
|
346
|
0
|
|
|
|
|
|
$payload{status} = 401; |
|
347
|
0
|
|
|
|
|
|
$payload{code} = 'E7005'; |
|
348
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
#$self->log->error(Mojo::Util::dumper( $ar )); |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# User data with required fields! |
|
353
|
0
|
|
0
|
|
|
|
my $data = $ar->to_hash || {}; |
|
354
|
0
|
|
|
|
|
|
$data->{address} = $self->remote_ip($self->app->trustedproxies); |
|
355
|
0
|
|
0
|
|
|
|
$data->{base} = $args{base_url} || $self->base_url; |
|
356
|
0
|
|
0
|
|
|
|
$data->{method} = $args{method} || $self->req->method || "ANY"; |
|
357
|
0
|
|
0
|
|
|
|
$data->{path} = $self->req->url->path->to_string || "/"; |
|
358
|
0
|
|
|
|
|
|
$data->{referer} = $referer; |
|
359
|
|
|
|
|
|
|
# required fields: |
|
360
|
0
|
0
|
|
|
|
|
$data->{status} = $data->{status} ? \1 : \0; |
|
361
|
0
|
|
0
|
|
|
|
$data->{uid} ||= 0; |
|
362
|
0
|
|
0
|
|
|
|
$data->{username} //= $username; |
|
363
|
0
|
|
0
|
|
|
|
$data->{name} //= $username; |
|
364
|
0
|
|
0
|
|
|
|
$data->{role} //= ''; |
|
365
|
0
|
|
0
|
|
|
|
$data->{email} //= ''; |
|
366
|
|
|
|
|
|
|
$data->{email_md5} //= $data->{email} ? md5_sum($data->{email}) : '', |
|
367
|
0
|
0
|
0
|
|
|
|
$data->{comment} //= ''; |
|
|
|
|
0
|
|
|
|
|
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# Save json file with user data |
|
370
|
0
|
|
|
|
|
|
json_save($file, $data); |
|
371
|
0
|
0
|
|
|
|
|
unless (-e $file) { |
|
372
|
0
|
|
|
|
|
|
$self->log->error(sprintf("[E7007] Can't save file %s", $file)); |
|
373
|
0
|
|
|
|
|
|
$payload{error} = sprintf("Can't save file DATADIR/u.%s.json", $username); |
|
374
|
0
|
|
|
|
|
|
$payload{status} = 500; |
|
375
|
0
|
|
|
|
|
|
$payload{code} = 'E7007'; |
|
376
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# Fixed to cache |
|
380
|
0
|
|
|
|
|
|
$cache->set($ustat_key, $now); |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
# Ok |
|
383
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
sub _authorize { |
|
386
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
387
|
0
|
0
|
|
|
|
|
my %args = scalar(@_) ? scalar(@_) % 2 ? ref($_[0]) eq 'HASH' ? (%{$_[0]}) : () : (@_) : (); |
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
388
|
0
|
|
0
|
|
|
|
my $username = $args{username} || ''; |
|
389
|
0
|
|
0
|
|
|
|
my $referer = $args{referer} // $self->req->headers->header("Referer") // ''; |
|
|
|
|
0
|
|
|
|
|
|
390
|
0
|
|
0
|
|
|
|
my $loginpage = $args{loginpage} // ''; |
|
391
|
0
|
|
|
|
|
|
my %payload = ( # Ok by default |
|
392
|
|
|
|
|
|
|
error => '', # Error message |
|
393
|
|
|
|
|
|
|
status => 200, # HTTP status code |
|
394
|
|
|
|
|
|
|
code => 'E0000', # The Suffit error code |
|
395
|
|
|
|
|
|
|
username => $username, # User name |
|
396
|
|
|
|
|
|
|
referer => $referer, # Referer |
|
397
|
|
|
|
|
|
|
loginpage => $loginpage, # Login page for redirects (location) |
|
398
|
|
|
|
|
|
|
location => undef, # Location URL for redirects |
|
399
|
|
|
|
|
|
|
user => { # User data with required fields (defaults) |
|
400
|
|
|
|
|
|
|
status => \0, # User status |
|
401
|
|
|
|
|
|
|
uid => 0, # User ID |
|
402
|
|
|
|
|
|
|
username => $username, # User name |
|
403
|
|
|
|
|
|
|
name => $username, # Full name |
|
404
|
|
|
|
|
|
|
role => "", # User role |
|
405
|
|
|
|
|
|
|
email => "", # Email address |
|
406
|
|
|
|
|
|
|
email_md5 => "", # MD5 of email address |
|
407
|
|
|
|
|
|
|
comment => "", # Comment |
|
408
|
|
|
|
|
|
|
}, |
|
409
|
|
|
|
|
|
|
); |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# Check username |
|
412
|
0
|
0
|
|
|
|
|
unless (length $username) { |
|
413
|
0
|
|
|
|
|
|
$self->log->error("[E7009] Incorrect username"); |
|
414
|
0
|
|
|
|
|
|
$payload{error} = "Incorrect username"; |
|
415
|
0
|
|
|
|
|
|
$payload{status} = 400; |
|
416
|
0
|
|
|
|
|
|
$payload{code} = 'E7009'; |
|
417
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Get user file name |
|
421
|
0
|
|
|
|
|
|
my $file = path($self->app->datadir, sprintf("u.%s.json", $username))->to_string; |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# Load user file with user data |
|
424
|
0
|
0
|
|
|
|
|
my $user = -e $file ? json_load($file) : {}; |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# Check user data |
|
427
|
0
|
0
|
|
|
|
|
unless ($user->{username}) { |
|
428
|
0
|
|
|
|
|
|
$self->log->error(sprintf("[E7008] File %s not found or incorrect", $file)); |
|
429
|
0
|
|
|
|
|
|
$payload{error} = sprintf("File DATADIR/u.%s.json not found or incorrect", $username); |
|
430
|
0
|
|
|
|
|
|
$payload{status} = 500; |
|
431
|
0
|
|
|
|
|
|
$payload{code} = 'E7008'; |
|
432
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
433
|
|
|
|
|
|
|
} |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# Ok |
|
436
|
0
|
|
|
|
|
|
$payload{user} = {%{$user}}; # Set user data to pyload hash |
|
|
0
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
|
return Mojo::JSON::Pointer->new({%payload}); |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
sub _parse_pwdb_lines { |
|
441
|
0
|
|
|
0
|
|
|
my @lines = @_; |
|
442
|
0
|
|
|
|
|
|
my %r = (); |
|
443
|
0
|
|
|
|
|
|
for (@lines) { |
|
444
|
0
|
0
|
|
|
|
|
next unless $_; |
|
445
|
0
|
|
|
|
|
|
my @line = split ':', $_; |
|
446
|
0
|
|
0
|
|
|
|
my ($usr, $pwd, $arg) = ($line[0] // '', $line[1] // '', $line[2] // ''); |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
447
|
0
|
0
|
0
|
|
|
|
next unless length($usr) && length($pwd); |
|
448
|
0
|
0
|
|
|
|
|
if (@line == 3) { # username:password:params |
|
|
|
0
|
|
|
|
|
|
|
449
|
0
|
|
|
|
|
|
$r{$usr} = { |
|
450
|
|
|
|
|
|
|
pwd => $pwd, |
|
451
|
|
|
|
|
|
|
arg => $arg, |
|
452
|
|
|
|
|
|
|
}; |
|
453
|
|
|
|
|
|
|
} elsif (@line == 2) { # username:password |
|
454
|
0
|
|
|
|
|
|
$r{$usr} = { |
|
455
|
|
|
|
|
|
|
pwd => $pwd |
|
456
|
|
|
|
|
|
|
}; |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
} |
|
459
|
0
|
|
|
|
|
|
return %r; |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
sub _check_pw { |
|
462
|
0
|
|
0
|
0
|
|
|
my $pwd = shift // ''; |
|
463
|
0
|
|
0
|
|
|
|
my $sum = shift // ''; |
|
464
|
0
|
0
|
0
|
|
|
|
return 0 unless length($pwd) && length($sum); |
|
465
|
0
|
0
|
|
|
|
|
if ($sum =~ /^[0-9a-f]+$/i) { |
|
466
|
0
|
0
|
|
|
|
|
if (length($sum) == 32) { # md5: acbd18db4cc2f85cedef654fccc4a4d8 |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
467
|
0
|
|
|
|
|
|
return secure_compare(md5_sum($pwd), lc($sum)); |
|
468
|
|
|
|
|
|
|
} elsif(length($sum) == 40) { # sha1: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 |
|
469
|
0
|
|
|
|
|
|
return secure_compare(sha1_sum($pwd), lc($sum)); |
|
470
|
|
|
|
|
|
|
} elsif(length($sum) == 56) { # sha224: d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01 |
|
471
|
0
|
|
|
|
|
|
return secure_compare(sha224_hex($pwd), lc($sum)); |
|
472
|
|
|
|
|
|
|
} elsif(length($sum) == 64) { # sha224: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 |
|
473
|
0
|
|
|
|
|
|
return secure_compare(sha256_hex($pwd), lc($sum)); |
|
474
|
|
|
|
|
|
|
} elsif(length($sum) == 96) { # sha384: a8b64babd0aca91a59bdbb7761b421d4f2bb38280d3a75ba0f21f2bebc45583d446c598660c94ce680c47d19c30783a7 |
|
475
|
0
|
|
|
|
|
|
return secure_compare(sha384_hex($pwd), lc($sum)); |
|
476
|
|
|
|
|
|
|
} elsif(length($sum) == 128) { # sha512: b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86 |
|
477
|
0
|
|
|
|
|
|
return secure_compare(sha512_hex($pwd), lc($sum)); |
|
478
|
|
|
|
|
|
|
} else { # Plain text (unsafe) |
|
479
|
0
|
|
|
|
|
|
return secure_compare($pwd, $sum); |
|
480
|
|
|
|
|
|
|
} |
|
481
|
|
|
|
|
|
|
} else { # Plain text (unsafe) |
|
482
|
0
|
|
|
|
|
|
return secure_compare($pwd, $sum); |
|
483
|
|
|
|
|
|
|
} |
|
484
|
0
|
|
|
|
|
|
return 0; |
|
485
|
|
|
|
|
|
|
} |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
1; |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
__END__ |