File Coverage

lib/Dancer/Plugin/Auth/Basic.pm
Criterion Covered Total %
statement 53 54 98.1
branch 19 20 95.0
condition 20 25 80.0
subroutine 9 9 100.0
pod n/a
total 101 108 93.5


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Auth::Basic;
2              
3 5     5   1433544 use strict;
  5         14  
  5         125  
4 5     5   26 use warnings;
  5         11  
  5         203  
5              
6             # ABSTRACT: Basic HTTP authentication for Dancer web apps
7              
8             our $VERSION = '0.030'; # VERSION
9              
10 5     5   26 use Dancer ':syntax';
  5         11  
  5         27  
11 5     5   5832 use Dancer::Plugin;
  5         6625  
  5         402  
12 5     5   33 use Dancer::Response;
  5         11  
  5         133  
13 5     5   26 use HTTP::Headers;
  5         10  
  5         113  
14 5     5   29 use MIME::Base64;
  5         10  
  5         3895  
15              
16             my $settings = plugin_setting;
17              
18             # Protected paths defined in the configuration
19             my $paths = {};
20             # "Global" users
21             my $users = {};
22              
23             if (exists $settings->{paths}) {
24             $paths = $settings->{paths};
25             }
26              
27             if (exists $settings->{users}) {
28             $users = $settings->{users};
29             }
30              
31             sub _check_password {
32 22     22   49 my ($password, $text) = @_;
33            
34 22         37 my $crypt;
35            
36 22 100 100     168 if (($crypt = $password =~ /^\$\w+\$/) || $password =~ /^\{\w+\}/) {
37             # Crypt or RFC 2307 format
38             eval {
39 7         1819 require Authen::Passphrase;
40 6         1604 1;
41             }
42 7 100       18 or do {
43 1         9 error "Can't use Authen::Passphrase: " . $@;
44 1         96 return 0;
45             };
46            
47 6         9 my $ppr;
48            
49             eval {
50 6 100       43 $ppr = $crypt ? Authen::Passphrase->from_crypt($password) :
51             Authen::Passphrase->from_rfc2307($password);
52             }
53 6 100       12 or do {
54 2         559 error "Can't construct an Authen::Passphrase recognizer object: " .
55             $@;
56 2         137 return 0;
57             };
58            
59 4         29137 return $ppr->match($text);
60             }
61             else {
62             # Password in cleartext
63 15         70 return $password eq $text;
64             }
65             }
66              
67             sub _auth_basic {
68 32     32   876 my (%options) = @_;
69              
70             # Get authentication data from request
71 32         104 my $auth = request->header('Authorization');
72            
73 32         1355 my $authorized = undef;
74            
75 32 100 66     257 if (defined $auth && $auth =~ /^Basic (.*)$/) {
76 23   50     196 my ($user, $password) = split(/:/, (MIME::Base64::decode($1) || ":"));
77            
78 23 100       92 if (exists $options{user}) {
79             # A single user is defined
80             $authorized = $user eq $options{user} &&
81 8   100     41 _check_password($options{password}, $password);
82             }
83            
84 23 100 66     112 if (!defined($authorized) && exists($options{users})) {
85             # Multiple users are defined
86             $authorized = exists($options{users}->{$user}) &&
87 13   100     67 _check_password($options{users}->{$user}, $password);
88             }
89            
90 23 100 66     33120 if (!$authorized && defined($users)) {
91             # Use the "global" users list
92             $authorized = exists $users->{$user} &&
93 9   66     71 _check_password($users->{$user}, $password);
94             }
95            
96 23 100       63 if ($authorized) {
97             # Authorization successful
98 18         74 request->env->{REMOTE_USER} = $user;
99 18         287 return 1;
100             }
101            
102 5 50       23 if (!defined($authorized)) {
103             # No users defined? NONE SHALL PASS!
104 0         0 warning __PACKAGE__ . ": No user/password defined";
105             }
106             }
107            
108 14         45 my $content = "Authorization required";
109            
110             return halt(Dancer::Response->new(
111             status => 401,
112             content => $content,
113             headers => [
114             'Content-Type' => 'text/plain',
115             'Content-Length' => length($content),
116             'WWW-Authenticate' => 'Basic realm="' . ($options{realm} ||
117 14   100     149 "Restricted area") . '"'
118             ]
119             ));
120             }
121              
122             my $check = sub {
123             # Check if the request matches one of the protected paths (reverse sort the
124             # paths to find the longest matching path first)
125             foreach my $path (reverse sort keys %$paths) {
126             my $path_re = '^' . quotemeta($path);
127             if (request->path_info =~ qr{$path_re}) {
128             _auth_basic %{$paths->{$path}};
129             last;
130             }
131             }
132             };
133              
134             # Dynamic paths
135             hook before => $check;
136             # Static paths
137             hook before_file_render => $check;
138              
139             register auth_basic => \&_auth_basic;
140             register_plugin;
141              
142             1; # End of Dancer::Plugin::Auth::Basic
143              
144             __END__