File Coverage

blib/lib/WebService/iThenticate/Client.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package WebService::iThenticate::Client;
2              
3 1     1   34810 use strict;
  1         3  
  1         32  
4 1     1   4 use warnings;
  1         1  
  1         43  
5              
6             our $VERSION = 0.16;
7              
8 1     1   5 use constant DEFAULT_URL => 'https://test.api.ithenticate.com/rpc'; ## no critic
  1         6  
  1         56  
9              
10 1     1   2954 use URI;
  1         4840  
  1         29  
11 1     1   437 use RPC::XML::Client;
  0            
  0            
12             use WebService::iThenticate::Request;
13             use WebService::iThenticate::Response;
14              
15             =head1 NAME
16              
17             WebService::iThenticate::Client - a client class to access the iThenticate service
18              
19             =head1 SYNOPSIS
20              
21             # construct a new client
22             $client = WebService::iThenticate::Client->new({
23             username => $username,
24             password => $password,
25             url => 'https://api.ithenticate.com:443/rpc', # default https://test.api.ithenticate.com:443/rpc
26             });
27              
28             # authenticate the client, returns an WebService::iThenticate::Response object
29             $response = $client->login;
30              
31             # access the session id from the response object
32             $sid = $response->sid;
33              
34             # submit a document
35             $response = $client->add_document({
36             title => 'Moby Dick',
37             author_first => 'Herman',
38             author_last => 'Melville',
39             filename => 'moby_dick.doc',
40             folder => 72, # folder id
41             submit_to => 1, # 1 => 'Generate Report Only'
42             upload => `cat moby_dick.doc`, # binary content of document
43             # the client module will base64 and chunk it
44             non_blocking_upload => 1,
45             });
46              
47             # get the newly created document id
48             $document_id = $response->id;
49             $document = $response->document;
50              
51             # get the document parts (note use of hash reference instead of object method)
52             $parts = $document->{parts};
53              
54             =head1 DESCRIPTION
55              
56             This module provides a client library interface to the iThenticate API web
57             services. It encapsulates different transport engines to provide a set
58             of methods through which the user can access the iThenticate API programmatically.
59              
60             See the iThenticate API reference web page at http://www.ithenticate.com/faq.html
61             for more details.
62              
63             =head1 METHODS
64              
65             =head2 CONSTRUCTORS AND AUTHENTICATION
66              
67             =over 4
68              
69             =item new()
70              
71             # construct a new client
72             $client = WebService::iThenticate::Client->new({
73             username => $username,
74             password => $password,
75             host => 'api.ithenticate.com', # default test.api.ithenticate.com
76             path => 'rpc', # default rpc
77             port => 3000, # default 3000
78             });
79              
80             Returns an WebService::iThenticate::Response object
81              
82             =cut
83              
84             sub new {
85             my ( $class, $args_ref ) = @_;
86              
87             # need some auth credentials to proceed
88             die 'username needed to create new client object' unless $args_ref->{username};
89             die 'password needed to create new client object' unless $args_ref->{password};
90              
91             # don't allow RPC::XML::Client to make use of Compress::Zlib
92             # Bill Moseley: related to https://rt.cpan.org/Public/Bug/Display.html?id=53448
93             local $RPC::XML::Client::COMPRESSION_AVAILABLE = q{};
94              
95             # set defaults
96             my $url = $args_ref->{url} || DEFAULT_URL;
97              
98             # canonicalize the url
99             $url = URI->new( $url )->canonical;
100             die "invalid url $url\n" unless $url;
101              
102             # create a new rpc client
103             my $rpc_client = RPC::XML::Client->new( $url );
104             die "unable to create rpc client from url $url" unless $rpc_client;
105              
106             # make an object
107             my %self;
108             bless \%self, $class;
109              
110             # stash the auth object
111             $self{auth} = {
112             username => $args_ref->{username},
113             password => $args_ref->{password},
114             };
115              
116             # stash the rpc object
117             $self{rpc_client} = $rpc_client;
118              
119             return \%self;
120             } ## end sub new
121              
122              
123             =item credentials()
124              
125             # pass basic auth credentials to the client
126             $client->credentials({
127             realm => 'My Authenticated Realm',
128             username => 'foo@example.com',
129             password => 'zimzamfoo123',
130             });
131              
132             =cut
133              
134             sub credentials {
135             my ( $self, $args ) = @_;
136              
137             my $realm = $args->{realm} || die 'no realm';
138             my $username = $args->{username} || die 'no username';
139             my $password = $args->{password} || die 'no password';
140              
141             return $self->{rpc_client}->credentials( $realm, $username, $password );
142             }
143              
144              
145             =item login()
146              
147             # authenticate the client, returns an WebService::iThenticate::Response object
148             $response = $client->login;
149              
150             # access the session id from the response object
151             $sid = $response->sid;
152              
153             The session id (sid) is stored internally in the client for future
154             authentication so there is no need to pass it explicitly
155              
156             =cut
157              
158             sub login {
159             my $self = shift;
160              
161             # we don't use _dispatch_request here because we set the sid as the authentication token
162              
163             my $request = WebService::iThenticate::Request->new( {
164             method => 'login',
165             auth => $self->{auth},
166             } );
167              
168             die 'unable to create login request' unless $request;
169              
170             my $response = $self->_make_request( $request );
171              
172             # check for errors
173             return $response if $response->errors;
174              
175             if ( my $sid = $response->sid ) {
176              
177             # successful login, stash the sid for future auth
178             $self->{auth} = $sid;
179             }
180              
181             return $response;
182             } ## end sub login
183              
184             =back
185              
186             =head2 FOLDER GROUPS
187              
188             =over 4
189              
190             =item add_folder_group()
191              
192             # add a folder group
193             $response = $client->add_folder_group({
194             name => 'iThenticate',
195             });
196              
197             $folder_group_id = $response->id;
198              
199             =cut
200              
201             sub add_folder_group {
202             my ( $self, $args ) = @_;
203              
204             return $self->_dispatch_request( 'group.add', $args );
205             }
206              
207             =item list_folder_groups()
208              
209             # list folder groups
210             $response = $client->list_folder_groups;
211              
212             # returns an array reference of hash references holding the folder group data owned by the api user
213             $folder_groups = $response->groups;
214              
215             # Example response data:
216             # $folder_groups->[0] = { id => 1, name => 'First Folder Group' };
217              
218             =cut
219              
220             sub list_folder_groups {
221             return shift->_dispatch_request( 'group.list' );
222             }
223              
224              
225             =item group_folders()
226              
227             # returns all the folders in a group
228             $response = $client->group_folders({ id => $folder_group_id });
229              
230             # returns an array reference of folder hashes
231             $folders = $response->folders;
232              
233             # Example response data:
234             # $folders->[0] = { id => 1,
235             # name => 'First Folder',
236             # group => {
237             # id => 1,
238             # name => 'First Folder Group', }, };
239              
240             =cut
241              
242             sub group_folders {
243             my ( $self, $args ) = @_;
244              
245             return $self->_dispatch_request( 'group.folders', $args );
246             }
247              
248              
249             =item drop_group()
250              
251             # remove a folder group
252             $response = $client->drop_group({ id => $folder_group_id });
253              
254             # Returns a message on successful response, with no errors
255             if (!$response->errors &&
256             $response->messages->[0] eq "Group \"$folder_group_id\" removed") {
257             print "Group $folder_group_id dropped successfully\n";
258             }
259              
260              
261             =cut
262              
263             sub drop_group {
264             my ( $self, $args ) = @_;
265              
266             return $self->_dispatch_request( 'group.drop', $args );
267             }
268              
269              
270             =back
271              
272             =head2 FOLDERS
273              
274             =over 4
275              
276             =item add_folder()
277              
278             # add a folder
279             $response = $client->add_folder({
280             name => 'API Specification',
281             description => 'Holds documentation referencing the iThenticate API',
282             folder_group => 79, # id of the folder group
283             exclude_quotes => 1, # 1 (true), or 0 (false)
284             add_to_index => 1, # 1 (true), or 0 (false), needed if account has
285             # a private storage node
286             });
287              
288             # returns the id of the newly created folder
289             $folder_id = $response->id;
290              
291             =cut
292              
293             sub add_folder {
294             my ( $self, $args ) = @_;
295              
296             return $self->_dispatch_request( 'folder.add', $args );
297             }
298              
299             =item get_folder()
300              
301             # get a folder and related documents
302             $response = $client->get_folder({ id => $folder_id });
303              
304             # see group_folders() for folder response data format
305             $folder = $response->folder;
306              
307             # get the documents for this folder
308             $documents = $response->documents;
309              
310             # Example document data
311             # $documents->[0] = { author_first => 'Jules', author_last => 'Varne',
312             # is_pending => 1, percent_match => '83.2',
313             # processed_time => '94.3', title => '10,000 Leagues Over The Sea',
314             # parts => $parts, uploaded_time => '2008-03-13 07:35:35 PST',
315             # id => 1, };
316              
317             # Example document parts data
318             # $parts->[0] = { part_id => 1, doc_id => 1, score => '95.2', word => 456, };
319              
320             =cut
321              
322             sub get_folder {
323             my ( $self, $args ) = @_;
324              
325             return $self->_dispatch_request( 'folder.get', $args );
326             }
327              
328             =item list_folders()
329              
330             # returns all the folders for a user
331             $response = $client->list_folders();
332              
333             # returns an array reference of folder hashes
334             $folders = $response->folders;
335              
336             # see get_folder() for the response folder data example
337              
338             =cut
339              
340             sub list_folders {
341             my ( $self, $args ) = @_;
342              
343             return $self->_dispatch_request( 'folder.list', $args );
344             }
345              
346              
347              
348             =item trash_folder()
349              
350             # move a folder to the trash
351             $response = $client->trash_folder({ id => $folder_id });
352              
353             print "Folder trashed ok!" if ( !$response->errors &&
354             $response->messages->[0] eq 'Moved to Trash' );
355              
356             =cut
357              
358             sub trash_folder {
359             my ( $self, $args ) = @_;
360              
361             return $self->_dispatch_request( 'folder.trash', $args );
362             }
363              
364             =back
365              
366             =head2 DOCUMENTS
367              
368             =over 4
369              
370             =item add_document()
371              
372             # submit a document
373             $response = $client->add_document({
374             title => 'Moby Dick',
375             author_first => 'Herman',
376             author_last => 'Melville',
377             filename => 'moby_dick.doc',
378              
379             # binary content of document
380             # the client module will base64 and chunk it
381             # note - don't actually use backticks like shown here :)
382             upload => `cat moby_dick.doc`,
383              
384             folder => 72, # folder id
385              
386             # options 2 and 3 only available for accounts with private nodes
387             submit_to => 1, # 1 => 'Generate Report Only'
388             # 2 => 'to Document Repository Only'
389             # 3 => 'to Document Repository & Generate Report'
390              
391             # use the non-blocking upload option (this method returns faster)
392             non_blocking_upload => 1,
393             });
394              
395             # get the newly created document id
396             $document_id = $response->id;
397             $document = $response->document;
398              
399             # see get_folder() for the response data format for the document
400              
401             =cut
402              
403             sub add_document {
404             my ( $self, $args ) = @_;
405              
406             $args->{uploads}->[0] = RPC::XML::struct->new( {
407             title => RPC::XML::string->new( delete $args->{title} ),
408             author_first => RPC::XML::string->new( delete $args->{author_first} ),
409             author_last => RPC::XML::string->new( delete $args->{author_last} ),
410             filename => RPC::XML::string->new( delete $args->{filename} ),
411             upload => RPC::XML::base64->new( delete $args->{upload} ), } );
412              
413             return $self->_dispatch_request( 'document.add', $args, 1 );
414             }
415              
416              
417             =item get_document()
418              
419             # check the status of a document submission
420             $response = $client->get_document({
421             id => $document_id,
422             });
423              
424             # access the document attributes from the response
425             $document_id = $response->id;
426              
427             # returns an array reference of document part hash references
428             $document_parts = $document->{parts};
429              
430             # see get_folder() for the document and document parts data formats
431              
432             =cut
433              
434             sub get_document {
435             my ( $self, $args ) = @_;
436              
437             return $self->_dispatch_request( 'document.get', $args );
438             }
439              
440             =item trash_document()
441              
442             # move a document to the trash
443             $response = $client->trash_document({ id => $document_id });
444              
445             =cut
446              
447             sub trash_document {
448             my ( $self, $args ) = @_;
449              
450             return $self->_dispatch_request( 'document.trash', $args );
451             }
452              
453             =back
454              
455             =head2 REPORTING
456              
457             =over 4
458              
459             =item get_report()
460              
461             # get an get report
462             $response = $client->get_report({
463             id => $document_part_id,
464             });
465              
466             # see if the report is ready
467             if ( $response->errors && ( $response->status == 404 ) ) {
468              
469             # the report may still be in progress
470             if ( $response->messages->[0] =~ m/report in progress/i ) {
471             print "Report is still being prepared, please try later\n";
472             } else {
473             print STDERR "Report not found found document part $document_part_id\n";
474             }
475              
476             } else {
477              
478             # report is ready, see WebService::iThenticate::Response for report object details
479             $report = $response->report;
480              
481             $report_url = $report->{view_only_url};
482              
483             # save the report content to disk
484             $grab_report = `wget --output-document=$HOME/reports/new.html $report_url`;
485             }
486              
487             =cut
488              
489             sub get_report {
490             my ( $self, $args ) = @_;
491              
492             return $self->_dispatch_request( 'report.get', $args );
493             }
494              
495             =back
496              
497             =head2 ACCOUNTS
498              
499             =over 4
500              
501             =item get_account()
502              
503             # get the account status
504             $response = $client->get_account;
505              
506             $account_status = $response->account_status;
507              
508             =cut
509              
510             sub get_account {
511             return shift->_dispatch_request( 'account.get' );
512             }
513              
514             =back
515              
516             =head2 USERS
517              
518             =over 4
519              
520             =item add_user()
521              
522             # add a user
523             $response = $client->add_user({
524             first_name => 'Joe',
525             last_name => 'User',
526             email => 'joe@user.com',
527             password => 'swizzlestick123',
528             });
529              
530             $user_id = $response->id;
531              
532             =cut
533              
534             sub add_user {
535             my ( $self, $args ) = @_;
536              
537             return $self->_dispatch_request( 'user.add', $args );
538             }
539              
540             =item put_user()
541              
542             # update a user's information
543             $response = $client->put_user({
544             email => 'joeuser@gmail.com',
545             });
546              
547             if ( $response->messages->[1] eq 'Email updated for user joeuser@gmail.com' ) {
548             print 'got the right message';
549             }
550              
551             =cut
552              
553             sub put_user {
554             my ( $self, $args ) = @_;
555              
556             return $self->_dispatch_request( 'user.put', $args );
557             }
558              
559             =item drop_user()
560              
561             # delete a user from the account
562             $response = $client->drop_user({ id => $user_id });
563              
564             print 'some errors occurred' if $response->errors;
565              
566             =cut
567              
568             sub drop_user {
569             my ( $self, $args ) = @_;
570              
571             return $self->_dispatch_request( 'user.drop', $args );
572             }
573              
574             =item list_users()
575              
576             # users listing
577             $response = $client->list_users;
578              
579             # returns a an array reference of user data in hashes
580             $users = $response->users;
581              
582             # Example user data format
583             # $users->[0] = { id => 1, email => 'jules@varne.com',
584             # first_name => 'Jules', last_name => 'Varne', };
585              
586             =cut
587              
588             sub list_users {
589             return shift->_dispatch_request( 'user.list' );
590             }
591              
592              
593              
594             # internal dispatch method to dispatch common requests
595              
596             sub _dispatch_request {
597             my ( $self, $method, $args, $novalidate ) = @_;
598              
599             my $request = WebService::iThenticate::Request->new( {
600             method => $method,
601             auth => { sid => $self->{auth} },
602             req_args => $args,
603             novalidate => $novalidate,
604             } );
605              
606             die "unable to create $method request" unless $request;
607              
608             my $response = $self->_make_request( $request );
609              
610             return $response;
611             }
612              
613             # internal dispatch method which makes the request based
614             # on what transport mechanism we are using, rpc, soap, etc.
615              
616             sub _make_request {
617             my ( $self, $request ) = @_;
618              
619             die 'need a request object' unless $request;
620              
621             my $response = $self->{rpc_client}->send_request( $request->{rpc_request} );
622              
623             # When no connection can be made RPC::XML returns a string (error)
624             if ( ref( \$response ) eq 'SCALAR' ) {
625             die "Error: $response\n";
626             }
627              
628             # else check for RPC::XML::fault
629             elsif ( $response->isa( 'RPC::XML::fault' ) ) {
630             require Data::Dumper;
631             die sprintf( "Error code: %s\nError string: %s\nRequest object: %s\n",
632             $response->{faultCode}->value, $response->{faultString}->value,
633             Data::Dumper::Dumper( $request ), );
634              
635             # if it isn't a fault and not a struct it is an unknown error
636             } elsif ( ref( $response ) ne 'RPC::XML::struct' ) {
637              
638             # unknown response
639             require Data::Dumper;
640             die 'unknown response returned, unable to handle: ' . Data::Dumper::Dumper( $response );
641             }
642              
643             # at this point we have a valid response
644             # transform the response to an object
645             my $ithenticate_response = WebService::iThenticate::Response->_new( $response ); ## no critic
646              
647             return $ithenticate_response;
648             } ## end sub _make_request
649              
650              
651             =back
652              
653              
654              
655             =head1 TESTING
656              
657             To enable testing against the iThenticate live test API, set the following
658             environment variables before running 'make test'.
659              
660             IT_USERNAME
661             IT_PASSWORD
662             IT_API_URL
663              
664             See your iThenticate account representative to obtain these credentials
665             to the API testing environment.
666              
667             =head1 BUGS
668              
669             =over 4
670              
671             =item IT_API_URL
672              
673             If you receive an error back from the server that looks like 'mismatched tag'
674             then you likely have an invalid url specified for IT_API_URL instead of an
675             actual mismatched tag in the request xml.
676              
677             =back
678              
679             =head1 FAQ
680              
681             Q: Why doesn't this code do X?
682              
683             A: Because that feature hasn't been requested yet :)
684              
685             Q: How is this module related to iThenticate::API::Client?
686              
687             A: This module takes the place of iThenticate::API::Client in a more
688             generally accepted namespace
689              
690             =head1 SEE ALSO
691              
692             WebService::iThenticate::Request, WebService::iThenticate::Response, RPC::XML, SOAP::Lite
693              
694             =head1 AUTHOR
695              
696             Fred Moyer
697              
698             =head1 COPYRIGHT
699              
700              
701             Copyright (C) (2011) iParadigms, LLC. All rights reserved.
702              
703             =head1 LICENSE
704              
705             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.
706              
707             =cut
708              
709             1;