File Coverage

blib/lib/Finance/Bank/Natwest/Connection.pm
Criterion Covered Total %
statement 99 105 94.2
branch 36 58 62.0
condition 9 24 37.5
subroutine 15 15 100.0
pod 0 3 0.0
total 159 205 77.5


line stmt bran cond sub pod time code
1             package Finance::Bank::Natwest::Connection;
2 3     3   1544 use strict;
  3         8  
  3         116  
3 3     3   16 use vars qw( $VERSION );
  3         6  
  3         142  
4 3     3   16 use Carp;
  3         5  
  3         199  
5 3     3   16 use LWP::UserAgent;
  3         6  
  3         171  
6              
7             $VERSION = '0.04';
8              
9             require Finance::Bank::Natwest;
10              
11 3     3   14 use constant POSS_PIN => { first => 0, second => 1, third => 2, fourth => 3 };
  3         7  
  3         335  
12 3         5400 use constant POSS_PASS =>
13             { first => 0, second => 1, third => 2, fourth => 3, fifth => 4,
14             sixth => 5, seventh => 6, eighth => 7, ninth => 8, tenth => 9,
15             eleventh => 10, twelfth => 11, thirteenth => 12, fourteenth => 13,
16             fifteenth => 14, sixteenth => 15, seventeenth => 16,
17             eighteenth => 17, nineteenth => 18, twentieth => 19
18 3     3   15 };
  3         4  
19              
20              
21             sub new{
22 31     31 0 8216 my ($class, %opts) = @_;
23              
24 31         74 my $self = bless {}, $class;
25              
26 31   66     126 $self->{url_base} = $opts{url_base} || Finance::Bank::Natwest->url_base;
27              
28 31         78 $self->_set_credentials( %opts );
29 4         20 $self->_new_ua( %opts );
30            
31 4         26 return $self;
32             }
33              
34             sub _new_ua{
35 4     4   11 my ($self, %opts) = @_;
36              
37 4         5 my %proxy;
38              
39 4 50       13 if (exists $opts{proxy}) {
40 0         0 $proxy{env_proxy} = 0;
41 0 0 0     0 $proxy{proxy} = $opts{proxy} if
42             $opts{proxy} ne 'no' and $opts{proxy} ne 'env';
43 0 0       0 $proxy{env_proxy} = 1 if $opts{proxy} eq 'env';
44             } else {
45 4         10 $proxy{env_proxy} = 1;
46             }
47              
48 4         43 $self->{ua} = LWP::UserAgent->new(
49             env_proxy => $proxy{env_proxy},
50             keep_alive => 1,
51             timeout => 30,
52             cookie_jar => {},
53             requests_redirectable => [ 'GET', 'HEAD', 'POST' ],
54             agent => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
55             );
56              
57 4 50       32 $self->{ua}->proxy('https', $proxy{proxy}) if exists $proxy{proxy};
58             }
59              
60             sub _set_credentials{
61 31     31   60 my ($self, %opts) = @_;
62              
63 31 100       171 croak "Must provide either a premade credentials object or ".
64             "a class name together with options, stopped" if
65             !exists $opts{credentials};
66              
67 25 100       57 if (ref($opts{credentials})) {
68 9 100       66 croak "Can't accept credential options if supplying a premade ".
69             "credentials object, stopped" if
70             exists $opts{credentials_options};
71              
72 3 100       10 croak "Not a valid credentials object, stopped" unless
73             $self->_isa_credentials($opts{credentials});
74              
75 1         3 $self->{credentials} = $opts{credentials};
76             } else {
77 16 100       115 croak "Must provide credential options unless suppying a premade ".
78             "credentials object, stopped" if
79             !exists $opts{credentials_options};
80              
81 8         25 $self->{credentials} =
82             $self->_new_credentials(
83             $opts{credentials}, $opts{credentials_options}
84             );
85             };
86             }
87              
88             sub _new_credentials{
89 8     8   20 my ($self, $class, $options) = @_;
90              
91 8 50       63 croak "Invalid class name, stopped" if
92             $class !~ /^(?:\w|::)+$/;
93            
94 8         18 my $full_class = "Finance::Bank::Natwest::CredentialsProvider::$class";
95            
96 8         895 eval "local \$SIG{'__DIE__'};
97             local \$SIG{'__WARN__'};
98             require $full_class;
99             ";
100 8 100       51 croak "Not a valid credentials class, stopped"
101             if $@;
102              
103 7 50       20 croak "Not a valid credentials class, stopped"
104             unless $self->_isa_credentials($full_class);
105              
106             {
107 7         8 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
  7         14  
108 7         8 return $full_class->new(%{$options});
  7         33  
109             }
110             }
111              
112             sub _isa_credentials{
113 10     10   17 my ($self, $credentials) = @_;
114              
115 10         23 my @required_subs = qw( new get_start get_stop get_identity get_pinpass );
116            
117 10         19 foreach my $sub (@required_subs) {
118 42 100       45 return unless defined eval {
119 42         91 local $SIG{'__DIE__'};
120 42         94 local $SIG{'__WARN__'};
121 42         364 $credentials->can($sub);
122             };
123             }
124              
125 8         33 return 1;
126             }
127              
128             sub login{
129 3     3 0 6 my ($self) = @_;
130              
131 3         4 my $page;
132              
133 3         8 $self->{login_ok} = 0;
134 3         7 $self->{in_login} = 1;
135 3         5 delete $self->{rb_id};
136              
137 3         14 $self->{credentials}->get_start();
138              
139 3         11 my $identity = $self->{credentials}->get_identity();
140              
141 3         25 ($self->{rb_id}, $page) = $self->post( 'logon.asp',
142             { DBIDa => $identity->{dob}, DBIDb => $identity->{uid},
143             radType => '', scriptingon => 'yup' } );
144              
145 3 50       475 croak "Error during login process. " .
146             "The website is temporarily unavailable, stopped" if
147             $page =~ m|Service Temporarily Unvailable|i;
148              
149 3 50       19 croak "Error during login process, stopped" if
150             $page =~ m|
.*?
|i;
151              
152 3 50       33 croak "Error during login process. " .
153             "Current page cannot be recognised, stopped" unless
154             $page =~ m#
155             Please \s enter \s the \s
156             ([a-z]{5,6}), \s ([a-z]{5,6}) \s and \s ([a-z]{5,6}) \s
157             digits \s from \s your \s (?:Security \s Number|PIN):
158             #ix;
159              
160 3 50 33     56 croak "Error during login process. " .
      33        
161             "Unrecognised pin request ($1, $2, $3), stopped" unless
162             exists POSS_PIN->{$1} &&
163             exists POSS_PIN->{$2} &&
164             exists POSS_PIN->{$3};
165              
166 3         14 my $pin_digits = [ POSS_PIN->{$1}, POSS_PIN->{$2}, POSS_PIN->{$3} ];
167              
168 3 50       29 croak "Error during login process. " .
169             "Current page cannot be recognised, stopped" unless
170             $page =~ m|
171             Please \s enter \s the \s
172             ([a-z]{5,11}), \s ([a-z]{5,11}) \s and \s ([a-z]{5,11}) \s
173             characters \s from \s your \s Password:
174             |ix;
175            
176 3 50 33     37 croak "Error during login process. " .
      33        
177             "Unrecognised password request ($1, $2, $3), stopped" unless
178             exists POSS_PASS->{$1} &&
179             exists POSS_PASS->{$2} &&
180             exists POSS_PASS->{$3};
181            
182 3         14 my $pass_chars = [ POSS_PASS->{$1}, POSS_PASS->{$2}, POSS_PASS->{$3} ];
183              
184 3         25 my $pinpass = $self->{credentials}->get_pinpass( $pin_digits, $pass_chars );
185 3         19 $self->{credentials}->get_stop();
186              
187 3         37 $page = $self->post('Logon-PinPass.asp',
188             { pin1 => $pinpass->{pin}[0],
189             pin2 => $pinpass->{pin}[1],
190             pin3 => $pinpass->{pin}[2],
191             pass1 => $pinpass->{password}[0],
192             pass2 => $pinpass->{password}[1],
193             pass3 => $pinpass->{password}[2],
194             buttonComplete => 'Submitted',
195             buttonFinish => 'Finish' } );
196              
197 3 50       169 $page = $self->post('LogonMessage.asp', { buttonOK => 'Next' }) if
198             $page =~ m|LogonMessage\.asp|i;
199              
200 3 50       14 croak "Error during login process, stopped" if
201             $page =~ m|
.*?
|i;
202              
203 3         7 $self->{login_ok} = 1;
204 3         22 delete $self->{in_login};
205             }
206              
207             sub post{
208 8     8 0 13 my $self = shift;
209              
210 8 100 33     62 $self->login(@_)
211             if !$self->{login_ok} and !exists $self->{in_login};
212              
213 8         26 my $resp = $self->_post(@_);
214              
215 8 50       23 if ($self->_check_expired($resp)) {
216 0         0 $self->_login(@_);
217            
218 0         0 $resp = $self->_post(@_);
219 0 0       0 croak "Error talking to nwolb. " .
220             "Session has timed out even though only just logged in, stopped"
221             if $self->_check_expired($resp);
222             }
223              
224 8 50       894 return unless defined wantarray;
225              
226 8 100       49 if (wantarray) {
227 3         18 return (($resp->base->path_segments)[2], $resp->content);
228             } else {
229 5         22 return $resp->content;
230             }
231             }
232              
233             sub _check_expired{
234 8     8   14 my ($self, $resp) = @_;
235              
236 8         45 return lc(($resp->base->path_segments)[-1]) eq 'exit.asp';
237             }
238              
239             sub _post{
240 8     8   10 my $self = shift;
241 8         11 my $url = shift;
242 8         9 my $full_url;
243              
244 8 100       28 if (exists $self->{rb_id}) {
245 5         19 $full_url = $self->{url_base} . $self->{rb_id} . '/' . $url;
246             } else {
247 3         9 $full_url = $self->{url_base} . $url;
248             }
249              
250 8         87 my $resp = $self->{ua}->post($full_url, @_);
251              
252 8 50       35511 croak "Error talking to nwolb: " . $resp->message . ", stopped"
253             if !$resp->is_success;
254              
255 8 50 66     541 croak "Unknown error talking to nwolb, stopped"
256             if !exists $self->{in_login} and
257             lc($resp->base->as_string) ne lc($full_url);
258              
259 8         237 return $resp;
260             }
261              
262             1;