File Coverage

blib/lib/WebService/Xero/Agent/PublicApplication.pm
Criterion Covered Total %
statement 26 68 38.2
branch 0 12 0.0
condition n/a
subroutine 9 12 75.0
pod 2 2 100.0
total 37 94 39.3


line stmt bran cond sub pod time code
1             package WebService::Xero::Agent::PublicApplication;
2              
3              
4 2     2   1843 use 5.006;
  2         6  
5 2     2   7 use strict;
  2         4  
  2         36  
6 2     2   6 use warnings;
  2         3  
  2         50  
7 2     2   6 use base ('WebService::Xero::Agent');
  2         3  
  2         186  
8              
9 2     2   584 use Crypt::OpenSSL::RSA;
  2         6472  
  2         55  
10 2     2   12 use Digest::MD5 qw( md5_base64 );
  2         1  
  2         105  
11              
12 2     2   8 use URI::Encode qw(uri_encode uri_decode );
  2         3  
  2         118  
13 2     2   8 use Data::Random qw( rand_chars );
  2         3  
  2         86  
14 2     2   8 use Net::OAuth 0.20;
  2         31  
  2         1440  
15             $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
16              
17             =head1 NAME
18              
19             WebService::Xero::Agent::PublicApplication - Connects to Xero Public Application API
20              
21             =head1 VERSION
22              
23             Version 0.11
24              
25             =cut
26              
27             our $VERSION = '0.11';
28              
29              
30             =head1 SYNOPSIS
31              
32             Public Applications
33              
34             Public applications use a 3-legged authorisation process. A user will need to authorise your application against each organisation that
35             you want access to. For a great description of the 3-legged flow see L .
36              
37             For a working example that uses Mojolicious Web Framework see L
38              
39              
40             =head2 XERO PUBLIC APPLICATION API CONFIGURATION
41              
42             Public applications are configured in the Xero Developer API Console. These setting are used in your application to access your user's Xero Accounting data through Xero's Public Application API.
43              
44             Your users will be directed from your website to Xero and asked for access confirmation. If they agree your application will use an Access Token to query Xero data for the life of the session (up to 30 minutes).
45              
46             You application can then access the Xero Services to retrieve, update and create contact, invoices etc.
47              
48             See L for more detail.
49              
50             =head2 TODO
51              
52              
53              
54             =head1 METHODS
55              
56             =cut
57              
58             sub _validate_agent
59             {
60 0     0     my ( $self ) = @_;
61             ## TODO: validate required WebService::Xero::Agent properties required for a public application.
62              
63 0           return $self;
64             }
65              
66              
67             =head2 get_request_token()
68              
69             Takes the callback URL as a parameter which is used to create the request for
70             a request token. The request is submitted to Xero and if successful this
71             method eturns the Token and sets the 'login_url' property of the agent.
72              
73             Assumes that the public application API configuration is set in the agent ( CONSUMER KEY and SECRET )
74              
75             =cut
76              
77             sub get_request_token ## FOR PUBLIC APP (from old Xero::get_auth_token)
78             {
79             ## talks to Xero to get an auth token
80 0     0 1   my ( $self, $my_callback_url ) = @_;
81 0           my $data = undef;
82              
83            
84             my $access = Net::OAuth->request("request token")->new(
85             'version' => '1.0',
86             'request_url' => 'https://api.xero.com/oauth/RequestToken?oauth_callback=' . uri_encode( $my_callback_url ),
87             callback => $my_callback_url,
88             consumer_key => $self->{CONSUMER_KEY},
89             consumer_secret => $self->{CONSUMER_SECRET},
90 0           request_method => 'GET',
91             signature_method => 'HMAC-SHA1',
92             timestamp => time,
93             nonce => 'ccp' . md5_base64( join('', rand_chars(size => 8, set => 'alphanumeric')) . time ), #$nonce
94             );
95 0           $access->sign();
96             #warn $access->to_url."\n";
97 0           my $res = $self->{ua}->get( $access->to_url ); ## {oauth_callback=> uri_encode('http://localhost/')}
98 0 0         if ($res->is_success)
99             {
100 0           my $response = $res->content();
101             #warn("GOT A NEW auth_token ---" . $response);
102 0 0         if ( $response =~ /oauth_token=([^&]+)&oauth_token_secret=([^&]+)&oauth_callback_confirmed=true/m)
103             {
104 0           $self->{oauth_token} = $1;#, "\n";
105 0           $self->{oauth_token_secret} = $2;#, "\n";
106              
107             $self->{login_url} = 'https://api.xero.com/oauth/Authorize?oauth_token='
108             . $self->{oauth_token}
109 0           . '&oauth_callback='
110             . $my_callback_url;
111              
112 0           $self->{status} = 'GOT REQUEST TOKEN AND GENERATED Xero login_url that includes callback';
113 0           return $self->{oauth_token};
114             }
115             }
116             else
117             {
118 0           return $self->_error("ERROR: " . $res->content);
119             }
120             }
121             #####################################
122              
123              
124             =head2 get_access_token()
125              
126             When Xero redirects the user back to the application it includes parameters that
127             when combined with the previously generated token can be used to create an access
128             token that can access the Xero API services directly.
129              
130             INPUT PARAMETERS AS A LIST ( NOT NAMED )
131              
132             $oauth_token oauth_token GET Param includes in the redirected request back from Xero
133             $oauth_verifier auth_verifier GET Param includes in the redirected request back from Xero
134             $org org GET Param includes in the redirected request back from Xero
135             $stored_token_secret
136             $stored_token
137              
138             When the Xero callback redirect returns the user to the application after authorising the
139             app in Xero, the get params oauth_token and oauth_verifier are included in the URL which
140              
141              
142             =cut
143              
144              
145             #####################################
146             sub get_access_token ## FOR PUBLIC APP
147             {
148 0     0 1   my ( $self, $oauth_token, $oauth_verifier, $org, $stored_token_secret, $stored_token ) = @_;
149 0           my $data = undef;
150 0 0         if ( defined $stored_token_secret )
151             {
152              
153 0           my $new_oauth_token_secret = $self->{CONSUMER_SECRET} . '&' .$stored_token_secret;
154              
155 0           my $uri = "https://api.xero.com/oauth/AccessToken";
156             my $access = Net::OAuth->request("access token")->new(
157             consumer_key => $self->{CONSUMER_KEY},
158             consumer_secret => $self->{CONSUMER_SECRET},
159 0           token_secret => $stored_token_secret, ## persistently stored session token
160             token => $stored_token, ## persistently stored session token
161             verifier => $oauth_verifier,
162             request_url => $uri,
163             request_method => 'GET',
164             signature_method => 'HMAC-SHA1',
165             timestamp => time,
166             nonce => join('', rand_chars(size=>16, set=>'alphanumeric')),
167             version => '1.0',
168             );
169 0           $access->sign();
170 0 0         return $self->_error( "COULDN'T VERIFY! Check OAuth parameters.") unless $access->verify;
171 0           my $res = $self->{ua}->get( $access->to_url );
172 0           my $x = $res->content;
173 0 0         if ($res->is_success)
174             {
175 0           $data = $x;
176 0 0         if ( $x =~ /oauth_token=([^\&]+)\&oauth_token_secret=([^\&]+)\&oauth_expires_in=(\d+)\&xero_org_muid=(.*)$/m )
177             {
178 0           $self->{oauth_token} = $1; $self->{oauth_token_secret} = $2; $self->{oauth_expires_in} = $3; $self->{xero_org_muid} = $4;
  0            
  0            
  0            
179 0           $self->{TOKEN} = $self->{oauth_token}; $self->{TOKEN_SECRET} = $self->{oauth_token_secret};
  0            
180 0           $self->{status} = 'GOT ACCESS TOKEN';
181             #warn (qq{replacing oauth_token=$self->{oauth_token} and token_secret= $self->{oauth_token_secret}});
182             }
183             else {
184 0           $self->{status} = 'GOT A RESPONSE FROM XERO TO REQUEST FOR ACCESS TOKEN BUT UNABLE TO UNDERSTAND IT';
185 0           return $self->_error("Failed to extract tokens from $x");
186             }
187            
188 0           return $res->content;
189             } else {
190 0           return $self->_error("ERROR: " . $res->content);
191             }
192             }
193             else
194             {
195 0           return $self->_error("Unable to recover xero_token for user_id=$self->{customer_id} to build request for access token");;
196             }
197 0           return $data;
198             }
199             #####################################
200              
201              
202             =head2 do_xero_api_call()
203              
204             INPUT PARAMETERS AS A LIST ( NOT NAMED )
205              
206             * $uri (required) - the API endpoint URI ( eg 'https://api.xero.com/api.xro/2.0/Contacts/')
207             * $method (optional) - 'POST' or 'GET' .. PUT not currently supported
208             * $xml (optional) - the payload for POST updates as XML
209              
210             RETURNS
211              
212             The response is requested in JSON format which is then processed into a Perl structure that
213             is returned to the caller.
214              
215              
216              
217             =head2 The OAuth Dance
218              
219             Public Applications require the negotiation of a token by directing the user to Xero to authenticate and accepting the callback as the
220             user is redirected to your application.
221              
222             To implement you need to persist token details across multiple user web page requests in your application.
223              
224             To fully understand the integration implementation requirements it is useful to familiarise yourself with the terminology.
225              
226             =head3 OAUTH 1.0a TERMINOLOGY
227              
228             =begin TEXT
229              
230             User A user who has an account of the Service Provider (Xero) and tries to use the Consumer. (The API Application config in Xero API Dev Center .)
231             Service Provider Service that provides Open API that uses OAuth. (Xero.)
232             Consumer An application or web service that wants to use functions of the Service Provider through OAuth authentication. (End User)
233             Request Token A value that a Consumer uses to be authorized by the Service Provider After completing authorization, it is exchanged for an Access Token.
234             (The identity of the guest.)
235             Access Token A value that contains a key for the Consumer to access the resource of the Service Provider. (A visitor card.)
236              
237             =end TEXT
238              
239              
240             =head2 Authentication occurs in 3 steps (legs):
241              
242             =head3 Step 1 - Get an Unauthorised Request Token
243              
244             use WebService::Xero::Agent::PublicApplication;
245              
246             my $xero = WebService::Xero::Agent::PublicApplication->new( CONSUMER_KEY => 'YOUR_OAUTH_CONSUMER_KEY',
247             CONSUMER_SECRET => 'YOUR_OAUTH_CONSUMER_SECRET',
248             CALLBACK_URL => 'http://localhost/xero_tester.cgi'
249             );
250             my $callback_url = 'http://localhost/cgi-bin/test_xero_public_application.cgi'; ## NB Domain must be configured in Xero App Config
251             $xero->get_request_token( $callback_url ); ## This generates the token to include in the user redirect (A)
252             ## NB need to store $xero->{oauth_token},$xero->{oauth_token_secret} in persistent storage as will be required at later steps for this session.
253             print $xero->{login_url}; ## need to include a link to this URL in your app for the user to click on (B)
254              
255             =head3 Step 2 - Redirect User
256              
257             user click on link to $xero->{login_url} which takes them to Xero - when they authorise your app they are redirected back to your callback URL (C)(D)
258              
259              
260             =head3 Step 3 - Swap a Request Token for an Access Token
261              
262             The callback URL includes extra GET parameters that are used with the token details stored earlier to obtain an access token.
263            
264             my $oauth_verifier = $cgi->url_param('oauth_verifier');
265             my $org = $cgi->param('org');
266             my $oauth_token = $cgi->url_param('oauth_token');
267              
268             $xero->get_access_token( $oauth_token, $oauth_verifier, $org, $stored_token_secret, $stored_oauth_token ); ## (E)(F)
269              
270             =head3 Step 4 - Access the Xero API using the access token
271              
272             my $contact_struct = $xero->do_xero_api_call( 'https://api.xero.com/api.xro/2.0/Contacts' ); ## (G)
273              
274              
275             =head2 Other Notes
276              
277             The access token received will expire after 30 minutes. If you want access for longer you will need the user to re-authorise your application.
278              
279             Xero API Applications have a limit of 1,000/day and 60/minute request per organisation.
280              
281             Your application can have access to many organisations at once by going through the authorisation process for each organisation.
282              
283             =head3 Xero URLs used for authorisation and using the API
284              
285             Get an Unauthorised Request Token: https://api.xero.com/oauth/RequestToken
286             Redirect a user: https://api.xero.com/oauth/Authorize
287             Swap a Request Token for an Access Token: https://api.xero.com/oauth/AccessToken
288             Connect to the Xero API: https://api.xero.com/api.xro/2.0/
289              
290              
291             =head1 AUTHOR
292              
293             Peter Scott, C<< >>
294              
295             =head1 BUGS
296              
297             Please report any bugs or feature requests to C, or through
298             the web interface at L. I will be notified, and then you'll
299             automatically be notified of progress on your bug as I make changes.
300              
301              
302              
303              
304             =head1 SUPPORT
305              
306             You can find documentation for this module with the perldoc command.
307              
308             perldoc WebService::Xero::Agent::PublicApplication
309              
310              
311             You can also look for information at:
312              
313             =over 4
314              
315             =item * RT: CPAN's request tracker (report bugs here)
316              
317             L
318              
319             =item * AnnoCPAN: Annotated CPAN documentation
320              
321             L
322              
323             =item * CPAN Ratings
324              
325             L
326              
327             =item * Search CPAN
328              
329             L
330              
331             =back
332              
333              
334             =head1 ACKNOWLEDGEMENTS
335              
336              
337             =head1 LICENSE AND COPYRIGHT
338              
339             Copyright 2016 Peter Scott.
340              
341             This program is free software; you can redistribute it and/or modify it
342             under the terms of the the Artistic License (2.0). You may obtain a
343             copy of the full license at:
344              
345             L
346              
347             Any use, modification, and distribution of the Standard or Modified
348             Versions is governed by this Artistic License. By using, modifying or
349             distributing the Package, you accept this license. Do not use, modify,
350             or distribute the Package, if you do not accept this license.
351              
352             If your Modified Version has been derived from a Modified Version made
353             by someone other than you, you are nevertheless required to ensure that
354             your Modified Version complies with the requirements of this license.
355              
356             This license does not grant you the right to use any trademark, service
357             mark, tradename, or logo of the Copyright Holder.
358              
359             This license includes the non-exclusive, worldwide, free-of-charge
360             patent license to make, have made, use, offer to sell, sell, import and
361             otherwise transfer the Package with respect to any patent claims
362             licensable by the Copyright Holder that are necessarily infringed by the
363             Package. If you institute patent litigation (including a cross-claim or
364             counterclaim) against any party alleging that the Package constitutes
365             direct or contributory patent infringement, then this Artistic License
366             to you shall terminate on the date that such litigation is filed.
367              
368             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
369             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
370             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
371             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
372             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
373             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
374             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
375             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
376              
377              
378              
379              
380             =begin HTML
381              
382            

383              
384             =end HTML
385              
386             =cut
387              
388             1; # End of WebService::Xero