File Coverage

blib/lib/CGI/ACL.pm
Criterion Covered Total %
statement 94 95 98.9
branch 51 54 94.4
condition 8 12 66.6
subroutine 11 11 100.0
pod 5 5 100.0
total 169 177 95.4


line stmt bran cond sub pod time code
1             package CGI::ACL;
2              
3             # Author Nigel Horne: njh@bandsman.co.uk
4             # Copyright (C) 2018, Nigel Horne
5              
6             # Usage is subject to licence terms.
7             # The licence terms of this software are as follows:
8             # Personal single user, single computer use: GPL2
9             # All other users (including Commercial, Charity, Educational, Government)
10             # must apply in writing for a licence for use from Nigel Horne at the
11             # above e-mail.
12              
13             # TODO: Add deny_all_countries() and allow_country() methods, so that we can easily block all but a few countries.
14              
15 6     6   833517 use 5.006_001;
  6         62  
16 6     6   33 use warnings;
  6         13  
  6         189  
17 6     6   46 use strict;
  6         11  
  6         144  
18 6     6   3415 use namespace::clean;
  6         101974  
  6         45  
19 6     6   1284 use Carp;
  6         13  
  6         306  
20 6     6   3771 use Net::CIDR;
  6         36344  
  6         5908  
21              
22             =head1 NAME
23              
24             CGI::ACL - Decide whether to allow a client to run this script
25              
26             =head1 VERSION
27              
28             Version 0.04
29              
30             =cut
31              
32             our $VERSION = '0.04';
33              
34             =head1 SYNOPSIS
35              
36             Does what it says on the tin.
37              
38             use CGI::Lingua;
39             use CGI::ACL;
40              
41             my $acl = CGI::ACL->new();
42             # ...
43             my $denied = $acl->all_denied(info => CGI::Lingua->new(supported => 'en'));
44              
45             =head1 SUBROUTINES/METHODS
46              
47             =head2 new
48              
49             Creates a CGI::ACL object.
50              
51             =cut
52              
53             sub new {
54 8     8 1 1171 my $proto = shift;
55 8   33     59 my $class = ref($proto) || $proto;
56              
57 8 50       23 return unless(defined($class));
58              
59 8         32 return bless { }, $class;
60             }
61              
62             =head2 allow_ip
63              
64             Give an IP (or CIDR) that we allow to connect to us
65              
66             use CGI::ACL;
67              
68             # Allow Google to connect to us
69             my $acl = CGI::ACL->new()->allow_ip(ip => '8.35.80.39');
70              
71             =cut
72              
73             sub allow_ip {
74 7     7 1 1010 my $self = shift;
75 7         12 my %params;
76              
77 7 100       44 if(ref($_[0]) eq 'HASH') {
    100          
    100          
78 1         2 %params = %{$_[0]};
  1         4  
79             } elsif(ref($_[0])) {
80 1         3 Carp::carp('Usage: allow_ip($ip_address)');
81             } elsif(@_ % 2 == 0) {
82 2         6 %params = @_;
83             } else {
84 3         24 $params{'ip'} = shift;
85             }
86              
87 7 100       341 if(defined($params{'ip'})) {
88 5         21 $self->{_allowed_ips}->{$params{'ip'}} = 1;
89             } else {
90 2         6 Carp::carp('Usage: allow_ip($ip_address)');
91             }
92 7         364 return $self;
93             }
94              
95             =head2 deny_country
96              
97             Give a country, or a reference to a list of countries, that we will not allow to access us
98              
99             use CGI::ACL;
100              
101             # Don't allow the UK to connect to us
102             my $acl = CGI::ACL->new()->deny_country('GB');
103              
104             # Don't allow any countries to connect to us (a sort of 'default deny')
105             my $acl = CGI::ACL->new()->deny_country('*');
106              
107             =cut
108              
109             sub deny_country {
110 7     7 1 2393 my $self = shift;
111 7         26 my %params;
112              
113 7 100       68 if(ref($_[0]) eq 'HASH') {
    100          
    100          
114 1         4 %params = %{$_[0]};
  1         5  
115             } elsif(ref($_[0])) {
116 1         8 Carp::carp('Usage: deny_country($ip_address)');
117 1         557 return;
118             } elsif(@_ % 2 == 0) {
119 2         9 %params = @_;
120             } else {
121 3         12 $params{'country'} = shift;
122             }
123              
124 6 100       28 if(defined(my $c = $params{'country'})) {
125             # This shenanigans allows country to be a scalar or list
126 5 100       18 if(ref($c) eq 'ARRAY') {
127 2         4 foreach my $country(@{$c}) {
  2         7  
128 36         75 $self->{_deny_countries}->{lc($country)} = 1;
129             }
130             } else {
131 3         14 $self->{_deny_countries}->{lc($c)} = 1;
132             }
133             } else {
134 1         8 Carp::carp('Usage: deny_country($ip_address)');
135             }
136 6         650 return $self;
137             }
138              
139             =head2 allow_country
140              
141             Give a country, or a reference to a list of countries, that we will allow to access us
142              
143             use CGI::ACL;
144              
145             # Allow only the UK and US to connect to us
146             my @allow_list = ('GB', 'US');
147             my $acl = CGI::ACL->new()->deny_country('*')->allow_country(country => \@allow_list);
148              
149             =cut
150              
151             sub allow_country {
152 4     4 1 228 my $self = shift;
153 4         9 my %params;
154              
155 4 100       30 if(ref($_[0]) eq 'HASH') {
    100          
    100          
156 1         5 %params = %{$_[0]};
  1         7  
157             } elsif(ref($_[0])) {
158 1         19 Carp::carp('Usage: allow_country($country)');
159             } elsif(@_ % 2 == 0) {
160 1         3 %params = @_;
161             } else {
162 1         4 $params{'country'} = shift;
163             }
164              
165 4 100       618 if(defined(my $c = $params{'country'})) {
166             # This shenanigans allows country to be a scalar or list
167 2 100       10 if(ref($c) eq 'ARRAY') {
168 1         2 foreach my $country(@{$c}) {
  1         3  
169 2         8 $self->{_allow_countries}->{lc($country)} = 1;
170             }
171             } else {
172 1         3 $self->{_allow_countries}->{lc($c)} = 1;
173             }
174             } else {
175 2         10 Carp::carp('Usage: allow_country($country)');
176             }
177 4         649 return $self;
178             }
179              
180             =head2 all_denied
181              
182             If any of the restrictions return false then return false, which should allow access.
183             Note that by default localhost isn't allowed access, call allow_ip('127.0.0.1') to enable it.
184              
185             use CGI::Lingua;
186             use CGI::ACL;
187              
188             # Allow Google to connect to us
189             my $acl = CGI::ACL->new()->allow_ip(ip => '8.35.80.39');
190              
191             if($acl->all_denied()) {
192             print 'You are not allowed to view this site';
193             return;
194             }
195              
196             $acl = CGI::ACL->new()->deny_country(country => 'br');
197              
198             if($acl->all_denied(lingua => CGI::Lingua->new(supported => ['en']))) {
199             print 'Brazilians cannot view this site for now';
200             return;
201             }
202              
203             =cut
204              
205             sub all_denied {
206 19     19 1 2579866 my $self = shift;
207              
208 19 100 100     146 if((!defined($self->{_allowed_ips})) && !defined($self->{_deny_countries})) {
209 1         5 return 0;
210             }
211              
212 18 50       106 my $addr = $ENV{'REMOTE_ADDR'} ? $ENV{'REMOTE_ADDR'} : '127.0.0.1';
213              
214 18 100       69 if($self->{_allowed_ips}) {
215 8 100       29 if($self->{_allowed_ips}->{$addr}) {
216 1         12 return 0;
217             }
218              
219 7         14 my @cidrlist;
220 7         12 foreach my $block(keys(%{$self->{_allowed_ips}})) {
  7         26  
221 8         646 @cidrlist = Net::CIDR::cidradd($block, @cidrlist);
222             }
223 7 100       3983 if(Net::CIDR::cidrlookup($addr, @cidrlist)) {
224 2         516 return 0;
225             }
226             }
227              
228 15 100 66     1891 if($self->{_deny_countries} || $self->{_allow_countries}) {
229 13         33 my %params;
230              
231 13 100       91 if(ref($_[0]) eq 'HASH') {
    100          
232 1         2 %params = %{$_[0]};
  1         3  
233             } elsif(@_ % 2 == 0) {
234 9         29 %params = @_;
235             } else {
236 3         12 $params{'lingua'} = shift;
237             }
238              
239 13 100       57 if(my $lingua = $params{'lingua'}) {
240 12 50 66     65 if($self->{_deny_countries}->{'*'} && !defined($self->{_allow_countries})) {
241 0         0 return 0;
242             }
243 12 100       58 if(my $country = $lingua->country()) {
244 11 100       17107556 if($self->{_deny_countries}->{'*'}) {
245             # Default deny
246 6         107 return !$self->{_allow_countries}->{$country};
247             }
248             # Default allow
249 5         68 return $self->{_deny_countries}->{$country};
250             }
251             # Unknown country - disallow access
252             } else {
253 1         8 Carp::carp('Usage: all_denied($lingua)');
254             }
255             }
256              
257 4         995 return 1;
258             }
259              
260             =head1 AUTHOR
261              
262             Nigel Horne, C<< <njh at bandsman.co.uk> >>
263              
264             =head1 BUGS
265              
266             Please report any bugs or feature requests to C<bug-cgi-acl at rt.cpan.org>,
267             or through the web interface at
268             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-ACL>.
269             I will be notified, and then you'll
270             automatically be notified of progress on your bug as I make changes.
271              
272             =head1 SEE ALSO
273              
274             L<CGI::Lingua>
275              
276             =head1 SUPPORT
277              
278             You can find documentation for this module with the perldoc command.
279              
280             perldoc CGI::ACL
281              
282             You can also look for information at:
283              
284             =over 4
285              
286             =item * MetaCPAN
287              
288             L<https://metacpan.org/release/CGI-ACL>
289              
290             =item * RT: CPAN's request tracker
291              
292             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-ACL>
293              
294             =item * CPANTS
295              
296             L<http://cpants.cpanauthors.org/dist/CGI-ACL>
297              
298             =item * CPAN Testers' Matrix
299              
300             L<http://matrix.cpantesters.org/?dist=CGI-ACL>
301              
302             =item * CPAN Ratings
303              
304             L<http://cpanratings.perl.org/d/CGI-ACL>
305              
306             =item * CPAN Testers Dependencies
307              
308             L<http://deps.cpantesters.org/?module=CGI::ACL>
309              
310             =back
311              
312             =head1 LICENSE AND COPYRIGHT
313              
314             Copyright 2017-2021 Nigel Horne.
315              
316             This program is released under the following licence: GPL2
317              
318             =cut
319              
320             1;