File Coverage

blib/lib/CGI/ACL.pm
Criterion Covered Total %
statement 91 93 97.8
branch 47 52 90.3
condition 6 9 66.6
subroutine 11 11 100.0
pod 5 5 100.0
total 160 170 94.1


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 5     5   623094 use 5.006_001;
  5         49  
16 5     5   27 use warnings;
  5         10  
  5         146  
17 5     5   27 use strict;
  5         9  
  5         106  
18 5     5   2355 use namespace::clean;
  5         80213  
  5         32  
19 5     5   1071 use Carp;
  5         15  
  5         247  
20 5     5   2770 use Net::CIDR;
  5         29354  
  5         5019  
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.03
29              
30             =cut
31              
32             our $VERSION = '0.03';
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());
44              
45             =head1 SUBROUTINES/METHODS
46              
47             =head2 new
48              
49             Creates a CGI::ACL object.
50              
51             =cut
52              
53             sub new {
54 6     6 1 1331931 my $proto = shift;
55 6   33     42 my $class = ref($proto) || $proto;
56              
57 6 50       24 return unless(defined($class));
58              
59 6         43 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 5     5 1 1446 my $self = shift;
75 5         11 my %params;
76              
77 5 100       27 if(ref($_[0]) eq 'HASH') {
    100          
    100          
78 1         2 %params = %{$_[0]};
  1         5  
79             } elsif(ref($_[0])) {
80 1         4 Carp::carp('Usage: allow_ip($ip_address)');
81             } elsif(@_ % 2 == 0) {
82 2         6 %params = @_;
83             } else {
84 1         4 $params{'ip'} = shift;
85             }
86              
87 5 100       319 if(defined($params{'ip'})) {
88 3         16 $self->{_allowed_ips}->{$params{'ip'}} = 1;
89             } else {
90 2         6 Carp::carp('Usage: allow_ip($ip_address)');
91             }
92 5         365 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 5     5 1 2328512 my $self = shift;
111 5         13 my %params;
112              
113 5 100       40 if(ref($_[0]) eq 'HASH') {
    100          
    100          
114 1         2 %params = %{$_[0]};
  1         6  
115             } elsif(ref($_[0])) {
116 1         4 Carp::carp('Usage: deny_country($ip_address)');
117             } elsif(@_ % 2 == 0) {
118 1         4 %params = @_;
119             } else {
120 2         7 $params{'country'} = shift;
121             }
122              
123 5 100       458 if(defined(my $c = $params{'country'})) {
124             # This shenanegans allows country to be a scalar or list
125 3 100       12 if(ref($c) eq 'ARRAY') {
126 1         2 foreach my $country(@{$c}) {
  1         5  
127 18         39 $self->{_deny_countries}->{lc($country)} = 1;
128             }
129             } else {
130 2         13 $self->{_deny_countries}->{lc($c)} = 1;
131             }
132             } else {
133 2         7 Carp::carp('Usage: deny_country($ip_address)');
134             }
135 5         478 return $self;
136             }
137              
138             =head2 allow_country
139              
140             Give a country, or a reference to a list of countries, that we will allow to access us
141              
142             use CGI::ACL;
143              
144             # Allow only the UK and US to connect to us
145             my @allow_list = ('GB', 'US');
146             my $acl = CGI::ACL->new()->deny_country->('*')->allow_country(country => \@allow_list);
147              
148             =cut
149              
150             sub allow_country {
151 3     3 1 136 my $self = shift;
152 3         6 my %params;
153              
154 3 100       16 if(ref($_[0]) eq 'HASH') {
    100          
    50          
155 1         2 %params = %{$_[0]};
  1         5  
156             } elsif(ref($_[0])) {
157 1         5 Carp::carp('Usage: allow_country($country)');
158             } elsif(@_ % 2 == 0) {
159 1         3 %params = @_;
160             } else {
161 0         0 $params{'country'} = shift;
162             }
163              
164 3 100       495 if(defined(my $c = $params{'country'})) {
165             # This shenanegans allows country to be a scalar or list
166 1 50       4 if(ref($c) eq 'ARRAY') {
167 1         2 foreach my $country(@{$c}) {
  1         3  
168 2         6 $self->{_allow_countries}->{lc($country)} = 1;
169             }
170             } else {
171 0         0 $self->{_allow_countries}->{lc($c)} = 1;
172             }
173             } else {
174 2         6 Carp::carp('Usage: allow_country($country)');
175             }
176 3         426 return $self;
177             }
178              
179             =head2 all_denied
180              
181             If any of the restrictions return false then return false, which should allow access
182              
183             use CGI::Lingua;
184             use CGI::ACL;
185              
186             # Allow Google to connect to us
187             my $acl = CGI::ACL->new()->allow_ip(ip => '8.35.80.39');
188              
189             if($acl->all_denied()) {
190             print 'You are not allowed to view this site';
191             return;
192             }
193              
194             $acl = CGI::ACL->new()->deny_country(country => 'br');
195              
196             if($acl->all_denied(lingua => CGI::Lingua->new(supported => ['en']))) {
197             print 'Brazilians cannot view this site for now';
198             return;
199             }
200              
201             =cut
202              
203             sub all_denied {
204 14     14 1 8199561 my $self = shift;
205              
206 14 100 100     95 if((!defined($self->{_allowed_ips})) && !defined($self->{_deny_countries})) {
207 1         5 return 0;
208             }
209              
210 13 50       49 my $addr = $ENV{'REMOTE_ADDR'} ? $ENV{'REMOTE_ADDR'} : '127.0.0.1';
211              
212 13 100       42 if($self->{_allowed_ips}) {
213 7 100       23 if($self->{_allowed_ips}->{$addr}) {
214 1         15 return 0;
215             }
216              
217 6         12 my @cidrlist;
218 6         9 foreach my $block(keys(%{$self->{_allowed_ips}})) {
  6         23  
219 6         28 @cidrlist = Net::CIDR::cidradd($block, @cidrlist);
220             }
221 6 100       2439 if(Net::CIDR::cidrlookup($addr, @cidrlist)) {
222 2         485 return 0;
223             }
224             }
225              
226 10 100 66     862 if($self->{_deny_countries} || $self->{_allow_countries}) {
227 8         25 my %params;
228              
229 8 100       49 if(ref($_[0]) eq 'HASH') {
    100          
230 1         2 %params = %{$_[0]};
  1         4  
231             } elsif(@_ % 2 == 0) {
232 6         22 %params = @_;
233             } else {
234 1         3 $params{'lingua'} = shift;
235             }
236              
237 8 100       28 if(!defined($params{'lingua'})) {
238 1         4 Carp::carp 'Usage: all_denied($$lingua)';
239 1         415 return 1;
240             }
241              
242 7 50       24 if(my $lingua = $params{'lingua'}) {
243 7 100       21 if($self->{_deny_countries}->{'*'}) {
244 3         17 return !$self->{_allow_countries}->{$lingua->country()};
245             }
246 4         18 return $self->{_deny_countries}->{$lingua->country()};
247             }
248             }
249              
250 2         9 return 1;
251             }
252              
253             =head1 AUTHOR
254              
255             Nigel Horne, C<< <njh at bandsman.co.uk> >>
256              
257             =head1 BUGS
258              
259             Please report any bugs or feature requests to C<bug-cgi-acl at rt.cpan.org>,
260             or through the web interface at
261             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-ACL>.
262             I will be notified, and then you'll
263             automatically be notified of progress on your bug as I make changes.
264              
265             =head1 SEE ALSO
266              
267             L<CGI::Lingua>
268              
269             =head1 SUPPORT
270              
271             You can find documentation for this module with the perldoc command.
272              
273             perldoc CGI::ACL
274              
275             You can also look for information at:
276              
277             =over 4
278              
279             =item * RT: CPAN's request tracker
280              
281             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-ACL>
282              
283             =item * CPAN Ratings
284              
285             L<http://cpanratings.perl.org/d/CGI-ACL>
286              
287             =item * Search CPAN
288              
289             L<http://search.cpan.org/dist/CGI-ACL/>
290              
291             =back
292              
293             =head1 LICENSE AND COPYRIGHT
294              
295             Copyright 2017,2018 Nigel Horne.
296              
297             This program is released under the following licence: GPL2
298              
299             =cut
300              
301             1; # End of CGI::ACL