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   56907 use Moo;
  10         9006  
  10         54  
3 10     10   8522 use JSON 'decode_json';
  10         78040  
  10         48  
4 10     10   5001 use Filter::signatures;
  10         177617  
  10         58  
5 10     10   342 no warnings 'experimental::signatures';
  10         20  
  10         325  
6 10     10   49 use feature 'signatures';
  10         18  
  10         245  
7 10     10   45 use Carp 'croak';
  10         15  
  10         398  
8 10     10   5020 use WWW::Mechanize;
  10         939765  
  10         408  
9 10     10   3789 use Mozilla::CA;
  10         2017  
  10         253  
10 10     10   3428 use HTTP::CookieJar::LWP;
  10         202241  
  10         347  
11 10     10   6761 use IO::Socket::SSL qw(SSL_VERIFY_PEER SSL_VERIFY_NONE);
  10         526236  
  10         84  
12              
13 10     10   4294 use HAL::Resource;
  10         27  
  10         290  
14 10     10   3991 use Finance::Bank::Postbank_de::APIv1::Finanzstatus;
  10         30  
  10         302  
15 10     10   64 use Finance::Bank::Postbank_de::APIv1::Message;
  10         119  
  10         199  
16 10     10   3841 use Finance::Bank::Postbank_de::APIv1::Transaction;
  10         38  
  10         275  
17 10     10   59 use Finance::Bank::Postbank_de::APIv1::Account;
  10         22  
  10         191  
18 10     10   3558 use Finance::Bank::Postbank_de::APIv1::Depot;
  10         27  
  10         288  
19 10     10   3636 use Finance::Bank::Postbank_de::APIv1::Position;
  10         28  
  10         11016  
20              
21             our $VERSION = '0.56';
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         19  
  11         18  
99             # Do an initial fetch to set up cookies
100 11         33 my $ua = $self->ua;
101 11         42 $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         1476 eval {
107 11         65 $ua->get('https://meine.postbank.de');
108 11         1833772 $ua->get('https://meine.postbank.de/configuration.json');
109             };
110 11 50       364794 if( my $err = $@ ) {
111 0         0 $self->diagnoseCertificateError( "$@ ");
112             };
113 11         55 my $config = decode_json( $ua->content );
114 11 50       1422 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         68 $self->config( $config );
119 11         29 $config
120             }
121              
122 11     11 0 21 sub configure_ua_ssl( $self, $ua=$self->ua ) {
  11         23  
  11         31  
  11         18  
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         18 my @verify;
126              
127 11 50       272 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         50 @verify = (
138             SSL_ocsp_mode => IO::Socket::SSL::SSL_OCSP_FULL_CHAIN(),
139             );
140             };
141 11         68 $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 26 sub configure_ua( $self, $config = $self->fetch_config ) {
  11         17  
  11         40  
  11         21  
154 11         43 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         117 );
164             };
165              
166 8     8 0 15 sub login_url( $self ) {
  8         15  
  8         14  
167 8         22 my $config = $self->config;
168 8         23 my $loginUrl = $config->{'iob5-base'}->{loginUrl};
169 8         48 $loginUrl =~ s!%(\w+)%!$config->{'iob5-base'}->{$1}!ge;
  8         40  
170 8         24 $loginUrl
171             }
172              
173 8     8 0 47 sub login( $self, $username, $password ) {
  8         15  
  8         16  
  8         16  
  8         13  
174 8         25 my $ua = $self->ua;
175 8         26 my $loginUrl = $self->login_url();
176              
177 8         21 local $ua->{autocheck};
178 8         61 my $r = $ua->post(
179             $loginUrl,
180             content => sprintf 'username=%s&password=%s', $username, $password
181             );
182 8 100       1186861 if( ! $r->is_success ) {
183 3         28 die sprintf "HTTP Error: %03d %s", $r->code, $r->message;
184             };
185             my $postbank = HAL::Resource->new(
186             ua => $ua,
187 5         43 %{ decode_json($ua->content)}
  5         22  
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, E<lt>corion@cpan.orgE<gt>
210              
211             =head1 SEE ALSO
212              
213             L<perl>, L<WWW::Mechanize>.
214              
215             =head1 REPOSITORY
216              
217             The public repository of this module is
218             L<https://github.com/Corion/Finance-Bank-Postbank_de>.
219              
220             =head1 SUPPORT
221              
222             The public support forum of this module is
223             L<https://perlmonks.org/>.
224              
225             =head1 BUG TRACKER
226              
227             Please report bugs in this module via the RT CPAN bug queue at
228             L<https://rt.cpan.org/Public/Dist/Display.html?Name=Finance-Bank-Postbank_de>
229             or via mail to L<finance-bank-postbank_de-Bugs@rt.cpan.org>.
230              
231             =head1 COPYRIGHT (c)
232              
233             Copyright 2003-2018 by Max Maischein C<corion@cpan.org>.
234              
235             =head1 LICENSE
236              
237             This module is released under the same terms as Perl itself.
238              
239             =cut
240