File Coverage

blib/lib/Finance/Bank/Postbank_de/APIv1.pm
Criterion Covered Total %
statement 99 118 83.9
branch 6 14 42.8
condition n/a
subroutine 22 23 95.6
pod 0 6 0.0
total 127 161 78.8


line stmt bran cond sub pod time code
1             package Finance::Bank::Postbank_de::APIv1;
2 10     10   69893 use Moo;
  10         11279  
  10         59  
3 10     10   10887 use JSON 'decode_json';
  10         98912  
  10         134  
4 10     10   6294 use Filter::signatures;
  10         221496  
  10         62  
5 10     10   427 no warnings 'experimental::signatures';
  10         31  
  10         464  
6 10     10   63 use feature 'signatures';
  10         24  
  10         367  
7 10     10   55 use Carp 'croak';
  10         21  
  10         470  
8 10     10   6351 use WWW::Mechanize;
  10         1166257  
  10         411  
9 10     10   4770 use Mozilla::CA;
  10         2567  
  10         339  
10 10     10   4518 use HTTP::CookieJar::LWP;
  10         258309  
  10         492  
11 10     10   9667 use IO::Socket::SSL qw(SSL_VERIFY_PEER SSL_VERIFY_NONE);
  10         656448  
  10         91  
12              
13 10     10   5562 use HAL::Resource;
  10         35  
  10         361  
14 10     10   5045 use Finance::Bank::Postbank_de::APIv1::Finanzstatus;
  10         38  
  10         401  
15 10     10   75 use Finance::Bank::Postbank_de::APIv1::Message;
  10         198  
  10         266  
16 10     10   4706 use Finance::Bank::Postbank_de::APIv1::Transaction;
  10         33  
  10         379  
17 10     10   76 use Finance::Bank::Postbank_de::APIv1::Account;
  10         21  
  10         256  
18 10     10   4861 use Finance::Bank::Postbank_de::APIv1::Depot;
  10         59  
  10         439  
19 10     10   4993 use Finance::Bank::Postbank_de::APIv1::Position;
  10         40  
  10         14260  
20              
21             our $VERSION = '0.57';
22              
23             =head1 NAME
24              
25             Finance::Bank::Postbank_de::APIv1 - Postbank connection
26              
27             =head1 SYNOPSIS
28              
29             my $api = Finance::Bank::Postbank_de::APIv1->new();
30             $api->configure_ua();
31             my $postbank = $api->login( 'Petra.Pfiffig', '11111' );
32              
33             =cut
34              
35             has diagnostics => (
36             is => 'ro',
37             default => undef,
38             );
39              
40             has logger => (
41             is => 'rw',
42             default => undef,
43             );
44              
45             has ua => (
46             is => 'ro',
47             default => sub( $self ) {
48             my $ua = WWW::Mechanize->new(
49             autocheck => 0,
50             keep_alive => 1,
51             cookie_jar => HTTP::CookieJar::LWP->new(),
52             );
53              
54             if( $self->diagnostics ) {
55             require LWP::ConsoleLogger::Easy;
56             my $logger = LWP::ConsoleLogger::Easy::debug_ua( $ua );
57             $logger->dump_content(0);
58             $logger->dump_text(0);
59             $self->logger($logger);
60             };
61             $ua
62             }
63             );
64              
65             has config => (
66             is => 'rw',
67             );
68              
69             has certificate_subject => (
70             is => 'ro',
71             default => sub {
72             +{
73             #/jurisdictionC=DE/jurisdictionST=Nordrhein-Westfalen/jurisdictionL=Bonn/businessCategory=Private Organization/serialNumber=HRB6793/C=DE/postalCode=53113/ST=Nordrhein-Westfalen/L=Bonn/street=Friedrich Ebert Allee 114 126/O=Deutsche Postbank AG/OU=PB Systems AG/CN=meine.postbank.de
74             #meine_postbank_de => qr{^/(?:\Q1.3.6.1.4.1.311.60.2.1.3\E|jurisdictionC|jurisdictionCountryName)=DE/(?:\Q1.3.6.1.4.1.311.60.2.1.2\E|jurisdictionST|jurisdictionStateOrProvinceName)=Nordrhein-Westfalen/(?:\Q1.3.6.1.4.1.311.60.2.1.1\E|jurisdictionL|jurisdictionLocalityName)=Bonn/businessCategory=Private Organization/serialNumber=HRB6793/C=DE/postalCode=53113/ST=Nordrhein-Westfalen/L=Bonn/street=Friedrich Ebert Allee 114 126/O=Deutsche Postbank AG/OU=PB Systems AG/CN=meine.postbank.de$},
75             api_public_postbank_de => qr{^/(?:\Q2.5.4.15\E|businessCategory)=Private Organization/(?:\Q1.3.6.1.4.1.311.60.2.1.3\E|jurisdictionC|jurisdictionCountryName)=DE/(?:\Q1.3.6.1.4.1.311.60.2.1.2\E|jurisdictionST|jurisdictionStateOrProvinceName)=Hessen/(?:\Q1.3.6.1.4.1.311.60.2.1.1\E|jurisdictionL|jurisdictionLocalityName)=Frankfurt am Main/serialNumber=HRB 47141/C=DE/ST=Nordrhein-Westfalen/L=Bonn/O=DB Privat- und Firmenkundenbank AG/OU=Postbank Systems AG/CN=bankapi-public.postbank.de$}
76             },
77             },
78             );
79              
80 0     0 0 0 sub diagnoseCertificateError( $self, $error=$@ ) {
  0         0  
  0         0  
  0         0  
81 0 0       0 my( $found, $re ) = ($error =~ m#'(.+?)' !~ /\Q(?^:\E(.+?)/ at #)
82             or die "$error"; # reraise
83 0         0 warn $found;
84 0         0 warn $re;
85 0         0 my @found_parts = split m!/!, $found;
86 0         0 my @re_parts = split m!/!, $re;
87              
88 0         0 for my $i (0..$#re_parts ) {
89 0 0       0 if( $found_parts[ $i ] =~ $re_parts[ $i ]) {
90 0         0 warn "'$found_parts[ $i ]' =~ /$re_parts[ $i ]/, OK\n";
91             } else {
92 0         0 warn "'$found_parts[ $i ]' !~ /$re_parts[ $i ]/, not OK\n";
93             };
94             };
95 0         0 die "Certificate mismatch";
96             }
97              
98 11     11 0 23 sub fetch_config( $self ) {
  11         24  
  11         24  
99             # Do an initial fetch to set up cookies
100 11         42 my $ua = $self->ua;
101 11         47 $self->configure_ua_ssl;
102             #my $re = join "|", values %{ $self->certificate_subject };
103             #$ua->add_header(
104             # "If-SSL-Cert-Subject" => qr/$re/,
105             #);
106 11         1850 eval {
107 11         71 $ua->get('https://meine.postbank.de');
108 11         3323008 $ua->get('https://meine.postbank.de/configuration.json');
109             };
110 11 50       681167 if( my $err = $@ ) {
111 0         0 $self->diagnoseCertificateError( "$@ ");
112             };
113 11         85 my $config = decode_json( $ua->content );
114 11 50       2084 if( ! exists $config->{ 'iob5-base' }) {
115 0         0 require Data::Dumper;
116 0         0 croak "Invalid config retrieved: " . Data::Dumper::Dumper($config);
117             };
118 11         88 $self->config( $config );
119 11         42 $config
120             }
121              
122 11     11 0 22 sub configure_ua_ssl( $self, $ua=$self->ua ) {
  11         20  
  11         35  
  11         21  
123             # OpenSSL 1.0.1 doesn't properly scan the certificate chain as supplied
124             # by Mozilla::CA, so we only verify the certificate directly there:
125 11         24 my @verify;
126              
127 11 50       361 if( IO::Socket::SSL->VERSION <= 1.990 ) {
    50          
128             # No OCSP support
129 0         0 @verify = ();
130             } elsif( Net::SSLeay::SSLeay() <= 0x100010bf ) { # 1.0.1k
131 0         0 @verify = (
132             SSL_fingerprint => 'sha256$99043D1F58197BDDFAEA3F914A8693588B067D7DC85BF532D7B773A9ED98F915',
133             SSL_ocsp_mode => IO::Socket::SSL::SSL_OCSP_NO_STAPLE(),
134             );
135             } else {
136             # We need no special additional options to verify the certificate chain
137 11         49 @verify = (
138             SSL_ocsp_mode => IO::Socket::SSL::SSL_OCSP_FULL_CHAIN(),
139             );
140             };
141 11         90 $ua->ssl_opts(
142             SSL_ca_file => Mozilla::CA::SSL_ca_file(),
143             SSL_verify_mode => SSL_VERIFY_PEER(),
144             @verify,
145             #SSL_verify_callback => sub {
146             #use Data::Dumper;
147             #warn Dumper \@_;
148             #return 1;
149             #},
150             );
151             };
152              
153 11     11 0 31 sub configure_ua( $self, $config = $self->fetch_config ) {
  11         25  
  11         44  
  11         22  
154 11         60 my $ua = $self->ua;
155              
156             $ua->add_header(
157             'api-key' => $config->{'iob5-base'}->{apiKey},
158             'device-signature' => $config->{'iob5-base'}->{apiKey},
159             accept => ['application/hal+json', '*/*'],
160             keep_alive => 1,
161             # / businessCategory =Private Organization/ jurisdictionC =DE/ jurisdictionST =Hessen/ jurisdictionL =Frankfurt am Main/serialNumber=HRB 47141/C=DE/ST=Nordrhein-Westfalen/L=Bonn/O=DB Privat- und Firmenkundenbank AG/OU=Postbank Systems AG/CN=(?:banking|bankapi-public).postbank.de
162             "If-SSL-Cert-Subject" => $self->certificate_subject->{ api_public_postbank_de },
163 11         136 );
164             };
165              
166 8     8 0 23 sub login_url( $self ) {
  8         18  
  8         17  
167 8         31 my $config = $self->config;
168 8         30 my $loginUrl = $config->{'iob5-base'}->{loginUrl};
169 8         68 $loginUrl =~ s!%(\w+)%!$config->{'iob5-base'}->{$1}!ge;
  8         53  
170 8         30 $loginUrl
171             }
172              
173 8     8 0 90 sub login( $self, $username, $password ) {
  8         19  
  8         32  
  8         19  
  8         19  
174 8         34 my $ua = $self->ua;
175 8         36 my $loginUrl = $self->login_url();
176              
177 8         32 local $ua->{autocheck};
178 8         80 my $r = $ua->post(
179             $loginUrl,
180             content => sprintf 'username=%s&password=%s', $username, $password
181             );
182 8 100       2145209 if( ! $r->is_success ) {
183 3         31 die sprintf "HTTP Error: %03d %s", $r->code, $r->message;
184             };
185             my $postbank = HAL::Resource->new(
186             ua => $ua,
187 5         63 %{ decode_json($ua->content)}
  5         30  
188             );
189              
190             };
191              
192             1;
193              
194             =head1 RESOURCE HIERARCHY
195              
196             This is the hierarchy of the resources in the API:
197              
198             APIv1
199             Finanzstatus
200             BusinessPartner
201             Account
202             Transaction
203             Message
204             Attachment
205             Depot
206              
207             =head1 AUTHOR
208              
209             Max Maischein, Ecorion@cpan.orgE
210              
211             =head1 SEE ALSO
212              
213             L, L.
214              
215             =head1 REPOSITORY
216              
217             The public repository of this module is
218             L.
219              
220             =head1 SUPPORT
221              
222             The public support forum of this module is
223             L.
224              
225             =head1 BUG TRACKER
226              
227             Please report bugs in this module via the RT CPAN bug queue at
228             L
229             or via mail to L.
230              
231             =head1 COPYRIGHT (c)
232              
233             Copyright 2003-2019 by Max Maischein C.
234              
235             =head1 LICENSE
236              
237             This module is released under the same terms as Perl itself.
238              
239             =cut
240