File Coverage

blib/lib/Captcha/Peoplesign.pm
Criterion Covered Total %
statement 55 158 34.8
branch 1 32 3.1
condition 2 62 3.2
subroutine 18 28 64.2
pod 3 3 100.0
total 79 283 27.9


line stmt bran cond sub pod time code
1             package Captcha::Peoplesign;
2              
3             BEGIN {
4 1     1   26591 $Captcha::Peoplesign::VERSION = '0.00005';
5             }
6              
7 1     1   10 use strict;
  1         1  
  1         37  
8 1     1   7 use warnings;
  1         1  
  1         34  
9              
10 1     1   10 use Carp qw/croak/;
  1         2  
  1         90  
11 1     1   1071 use HTML::Tiny;
  1         3116  
  1         29  
12 1     1   1008 use LWP::UserAgent;
  1         119040  
  1         153  
13              
14 1     1   14 use constant MODULE_VERSION => $Captcha::Peoplesign::VERSION;
  1         2  
  1         270  
15              
16 1     1   6 use constant PEOPLESIGN_HOST => 'peoplesign.com';
  1         2  
  1         66  
17              
18 1         62 use constant PEOPLESIGN_GET_CHALLENGE_SESSION_ID_URL =>
19 1     1   5 'http://'.PEOPLESIGN_HOST.'/main/getChallengeSessionID';
  1         2  
20              
21 1         53 use constant PEOPLESIGN_CHALLENGE_URL =>
22 1     1   4 'http://'.PEOPLESIGN_HOST.'/main/challenge.html';
  1         2  
23              
24 1         42 use constant PEOPLESIGN_GET_CHALLENGE_SESSION_STATUS_URL =>
25 1     1   4 'http://'.PEOPLESIGN_HOST.'/main/getChallengeSessionStatus_v2';
  1         3  
26              
27 1     1   5 use constant PEOPLESIGN_CHALLENGE_SESSION_ID_NAME => 'challengeSessionID';
  1         3  
  1         39  
28 1     1   4 use constant PEOPLESIGN_CHALLENGE_RESPONSE_NAME => 'captcha_peoplesignCRS';
  1         2  
  1         39  
29              
30 1     1   5 use constant PEOPLESIGN_IFRAME_WIDTH => '335';
  1         2  
  1         53  
31 1     1   5 use constant PEOPLESIGN_IFRAME_HEIGHT => '335';
  1         1  
  1         38  
32              
33 1     1   4 use constant PEOPLESIGN_CSID_SESSION_VAR_TIMEOUT_SECONDS => 3600;
  1         2  
  1         54  
34              
35 1     1   5 use constant PEOPLESIGN_PLUGIN_VERSION => 'Captcha_Peoplesign_perl_' . MODULE_VERSION;
  1         1  
  1         2111  
36              
37             sub new {
38 1     1 1 208 my $class = shift;
39 1         4 my $self = bless {}, $class;
40              
41 1   50     10 my $args = shift || {};
42              
43 1 50       5 croak "new must be called with a reference to a hash of parameters"
44             unless ref $args eq 'HASH';
45            
46 1   50     12 $self->{_html_mode} = $args->{html_mode} || 'html';
47              
48 1         8 return $self;
49             }
50              
51             sub _html {
52 0     0     my $self = shift;
53            
54 0   0       $self->{_html} ||= HTML::Tiny->new(
55             mode => $self->{_html_mode}
56             );
57             }
58              
59             sub get_html {
60 0     0 1   my ($self, $args) = @_;
61            
62 0 0         ref $args eq 'HASH' || croak 'Arguments must be an hashref';
63 0   0       my $ps_key = $args->{ps_key} || croak 'Provide a key';
64 0   0       my $ps_location = $args->{ps_location} || croak 'Provide a location';
65 0   0       my $ps_clientip = $args->{ps_clientip} || croak 'Provide the IP address of the client';
66 0   0       my $ps_options = $args->{ps_psoptions} || '';
67 0   0       my $ps_sessionid = $args->{ps_sessionid} || '';
68            
69             # TODO: remove this
70 0           my $ps_wversion = '';
71              
72 0           my $status = '';
73 0           ($status, $ps_sessionid) = $self->_get_peoplesign_sessionid(
74             $ps_key,
75             $ps_clientip,
76             $ps_options,
77             $ps_location,
78             $ps_wversion,
79             $ps_sessionid,
80             );
81              
82 0 0         if ($status eq 'success') {
83             # An iframe will only be displayed if javascript is disabled
84             # in the browser.
85 0   0       my $iframe_width = $args->{iframe_width} || PEOPLESIGN_IFRAME_WIDTH;
86 0   0       my $iframe_height = $args->{iframe_height} || PEOPLESIGN_IFRAME_HEIGHT;
87            
88 0           return $self->_get_html_js(
89             $ps_sessionid,
90             $iframe_width,
91             $iframe_height,
92             );
93             }
94            
95 0           return $self->_html->p('peoplesign is unavailable ($status)');
96             }
97              
98             sub check_answer {
99 0     0 1   my ($self, $args) = @_;
100              
101 0 0         ref $args eq 'HASH' || croak 'Arguments must be an hashref';
102 0   0       my $ps_key = $args->{ps_key} || croak 'Provide ps_key';
103 0   0       my $ps_location = $args->{ps_location} || croak 'Provide ps_location';
104 0   0       my $ps_sessionid = $args->{ps_sessionid} || 'Provide ps_sessioid';
105 0   0       my $ps_response = $args->{ps_response} || croak 'Provide ps_response';
106              
107 0           my $status = $self->_get_peoplesign_session_status(
108             $ps_sessionid,
109             $ps_response,
110             $ps_location,
111             $ps_key,
112             );
113              
114             # If CAPTCHA is solved correcly, pass
115 0 0         return { is_valid => 1 } if $status eq 'pass';
116              
117             # Usual states for which the user can not pass
118 0 0 0       return { is_valid => 0, error => $status } if
      0        
119             $status eq 'fail' || $status eq 'notRequested'
120             || $status eq 'awaitingResponse';
121            
122             # If Peoplesign server has problems, do not pass but return
123             # error so call decide if he/she wants to pass in such case
124 0 0         return { is_valid => 0, error => $status }
125             if $status eq 'badHTTPResponseFromServer';
126              
127             # If $status is invalidChallengeSessionID we can not allow the user to pass.
128             # It's highly unusual for this to occur, and probably means the
129             # peoplesignSession expired and the client session was still alive.
130             # We now abandon this client session. This will trigger a new client session
131             # and a new peoplesign session.
132 0 0         return { is_valid => 0, error => $status . ' [' .$self->_get_caller_info_string() . ']' }
133             if $status eq 'invalidChallengeSessionID';
134            
135             # All other cases are an exception, so croak!
136 0           croak "Exception processing Peoplesign response: [status $status]"
137             . $self->_get_caller_info_string();
138             }
139              
140             # ## Private methods ##
141              
142             # Contacts the peoplesign server to validate the user's response.
143             # Return: string ('pass', 'fail', 'awaitingResponse', 'badHTTPResponseFromServer')
144             sub _get_peoplesign_session_status {
145 0     0     my $self = shift;
146 0   0       my $peoplesignSessionID = shift || croak 'Provide challengeSessionID';
147 0   0       my $peoplesignResponseString = shift || croak 'Provide response string';
148 0   0       my $clientLocation = shift || "default";
149 0           my $peoplesignKey = shift;
150              
151 0           $peoplesignResponseString = $self->_trim($peoplesignResponseString);
152              
153 0           my $ua = LWP::UserAgent->new();
154              
155             # Note that the constant values are referenced below using CONSTANT()
156             # when they are needed as hash names.
157 0           my $response = $ua->post(
158             PEOPLESIGN_GET_CHALLENGE_SESSION_STATUS_URL, {
159             PEOPLESIGN_CHALLENGE_SESSION_ID_NAME() => $peoplesignSessionID,
160             PEOPLESIGN_CHALLENGE_RESPONSE_NAME() => $peoplesignResponseString,
161             privateKey => $peoplesignKey,
162             clientLocation => $clientLocation
163             }
164             );
165              
166 0 0         return $self->_trim( $response->content )
167             if ($response->is_success);
168            
169 0           $self->_print_error("bad HTTP response from server: " .$response ->status_line."\n", $self->_get_caller_info_string());
170 0           return 'badHTTPResponseFromServer';
171             }
172              
173             # Return value : array with 2 elements (status, eoplesignSessionID)
174             # A peoplesignSessionID is assigned to a given visitor and is valid
175             # until he/she passes a challenge
176             sub _get_peoplesign_sessionid {
177 0     0     my $self = shift;
178 0           my $peoplesignKey = shift;
179 0           my $visitorIP = shift;
180 0           my $peoplesignOptions = shift;
181 0   0       my $clientLocation = shift || "default";
182 0           my $pluginWrapperVersionInfo = shift;
183 0           my $peoplesignSessionID = shift;
184              
185 0           my $ua = LWP::UserAgent->new();
186              
187 0           my $status;
188              
189             # Peoplesign callenge option string
190 0 0         if (ref($peoplesignOptions) ne "HASH") {
191 0           my %hash = ();
192              
193             # decode the encoded string into a hash
194 0           $peoplesignOptions = $self->_html->url_decode($peoplesignOptions);
195 0           foreach my $pair (split('&',$peoplesignOptions)){
196 0           my ($key,$value) = split('=', $pair);
197 0           $hash{$key} = $value;
198             }
199 0           $peoplesignOptions = \%hash;
200             }
201              
202 0           $peoplesignKey = $self->_trim($peoplesignKey);
203 0           $visitorIP = $self->_trim($visitorIP);
204            
205             # Ensure private key is not the empty string
206 0 0         if ($peoplesignKey eq '') {
207 0           $self->_print_error("received a private key that was all whitespace or empty\n", $self->_get_caller_info_string());
208 0           return ('invalidPrivateKey', '');
209             }
210              
211             # Ensure visitorIP is ipv4
212 0 0         if ( !($visitorIP =~ /^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?$/) ) {
213 0           $self->_print_error("invalid visitorIP: $visitorIP\n", $self->_get_caller_info_string());
214 0           return ('invalidVisitorIP', '');
215             }
216              
217 0           my $response = $ua->post(
218             PEOPLESIGN_GET_CHALLENGE_SESSION_ID_URL, {
219             privateKey => $peoplesignKey,
220             visitorIP => $visitorIP,
221             clientLocation => $clientLocation,
222             pluginInfo => $pluginWrapperVersionInfo
223             .' '.PEOPLESIGN_PLUGIN_VERSION,
224             PEOPLESIGN_CHALLENGE_SESSION_ID_NAME() => $peoplesignSessionID,
225 0           %{$peoplesignOptions},
226             }
227             );
228              
229 0 0         if ($response->is_success){
230 0           ($status, $peoplesignSessionID) = split(/\n/, $response->content);
231 0 0         if ($status ne 'success') {
232 0           $self->_print_error("Unsuccessful attempt to get a peoplesign "
233             ."challenge session: ($status)\n", $self->_get_caller_info_string());
234             }
235             } else {
236 0           $self->_print_error("bad HTTP response from server: "
237             . $response ->status_line."\n", $self->_get_caller_info_string());
238 0           $status = "invalidServerResponse";
239 0           $peoplesignSessionID = "";
240             }
241              
242 0           return ($status, $peoplesignSessionID);
243             }
244              
245              
246             sub _get_html_js {
247 0     0     my $self = shift;
248 0           my $peoplesignSessionID = shift;
249              
250             # iframe will only be displayed if javascript is disabled in browser
251 0   0       my $iframeWidth = shift || PEOPLESIGN_IFRAME_WIDTH;
252 0   0       my $iframeHeight = shift || PEOPLESIGN_IFRAME_HEIGHT;
253              
254 0 0         if ( $peoplesignSessionID eq "" ) {return "";}
  0            
255              
256 0           my $h = $self->_html;
257              
258 0           my $htmlcode = $h->script({
259             type => 'text/javascript',
260             src => PEOPLESIGN_CHALLENGE_URL . '?' . PEOPLESIGN_CHALLENGE_SESSION_ID_NAME
261             . '=' . $peoplesignSessionID . '&addJSWrapper=true&ts=\''
262             . '+\(new Date\(\)\).getTime\(\) +\'" id="yeOldePeopleSignJS">'
263             })
264             . $h->noscript(
265             $self->_get_html_iframe($peoplesignSessionID, $iframeWidth, $iframeHeight)
266             );
267              
268 0           return $htmlcode;
269             }
270              
271             sub _get_html_iframe {
272 0     0     my $self = shift;
273 0           my $peoplesignSessionID = shift;
274 0   0       my $width = shift || PEOPLESIGN_IFRAME_WIDTH;
275 0   0       my $height = shift || PEOPLESIGN_IFRAME_HEIGHT;
276 0 0         if ( $peoplesignSessionID eq "") {return "";}
  0            
277            
278 0           my $h = $self->_html;
279              
280 0           my $htmlcode = $h->iframe({
281             src => PEOPLESIGN_CHALLENGE_URL . '?' . PEOPLESIGN_CHALLENGE_SESSION_ID_NAME,
282             height => $width,
283             width => $height,
284             frameborder => 0,
285             allowTransparency => 'true',
286             scrolling => 'auto',
287             },
288             $h->p(
289             'Since it appears your browser does not support "iframes", you need to click '
290             . $h->a({
291             href => PEOPLESIGN_CHALLENGE_URL
292             }, 'here')
293             . ' to verify you\'re a human.'
294             )
295             . $h->input({
296             name => PEOPLESIGN_CHALLENGE_SESSION_ID_NAME,
297             type => 'hidden',
298             value => $peoplesignSessionID,
299             })
300             );
301            
302 0           return $htmlcode;
303             }
304              
305             sub _get_caller_info_string {
306 0     0     my $self = shift;
307             # For the second subroutine up the call stack return the following:
308             # file: subroutine: line number
309 0           return (caller(2))[1] .": " .(caller(2))[3] .": line " .(caller(2))[2];
310             }
311              
312             sub _print_error {
313 0     0     my $self = shift;
314 0           my $message = shift;
315              
316             # If an error source was passed here, print it. Else
317             # we have to determine it;
318 0   0       my $errorSourceInfo = shift || $self->_get_caller_info_string();
319              
320 0           print STDERR "ERROR: peoplesign client: $errorSourceInfo: $message\n";
321 0           return;
322             }
323              
324             sub _trim {
325 0     0     my ($self, $string) = @_;
326 0           $string =~ s/^\s*//;
327 0           $string =~ s/\s*$//;
328 0           return $string;
329             }
330              
331             1;
332             __END__