File Coverage

blib/lib/Siebel/SOAP/Auth.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 Siebel::SOAP::Auth;
2             $Siebel::SOAP::Auth::VERSION = '0.003';
3 1     1   870 use strict;
  1         1  
  1         27  
4 1     1   6 use warnings;
  1         2  
  1         38  
5 1     1   841 use Moo 2.000001;
  1         13583  
  1         7  
6 1     1   7920 use Types::Standard 1.000005 qw(Str Int RegexpRef Num);
  1         71808  
  1         12  
7 1     1   1658 use XML::LibXML 2.0115;
  0            
  0            
8             use namespace::clean 0.25;
9             use Encode qw(encode);
10             use Scalar::Util qw(blessed);
11             use Time::HiRes qw(time);
12             use Log::Report 1.05 'siebel-soap-auth', syntax => 'SHORT';
13             use Carp;
14              
15             =pod
16              
17             =head1 NAME
18              
19             Siebel::SOAP::Auth - Moo based class to implement transparent Siebel Session Management for XML::Compile::WSDL11
20              
21             =head1 SYNOPSIS
22              
23             use XML::Compile::WSDL11;
24             use XML::Compile::SOAP11;
25             use XML::Compile::Transport::SOAPHTTP;
26             use Siebel::SOAP::Auth;
27              
28             my $wsdlfile = File::Spec->catfile( 't', 'SWIContactServices.WSDL' );
29             my %request = (
30             ListOfSwicontactio => {
31             Contact =>
32             { Id => '0-1', FirstName => 'Siebel', LastName => 'Administrator' }
33             }
34             );
35              
36             my $wsdl = XML::Compile::WSDL11->new($wsdlfile);
37             my $auth = Siebel::SOAP::Auth->new(
38             {
39             user => 'sadmin',
40             password => 'XXXXXXX',
41             token_timeout => MAGIC_NUMBER,
42             remain_ttl => 0
43             }
44             );
45              
46             my $call = $wsdl->compileClient(
47             operation => 'SWIContactServicesQueryByExample',
48             transport_hook => sub {
49             my ( $request, $trace, $transporter ) = @_;
50             # request was modified
51             my $new_request = $auth->add_auth_header($request);
52             return $trace->{user_agent}->request($new_request);
53             }
54             );
55              
56             my ( $answer, $trace ) = $call->(%request);
57              
58             if ( my $e = $@->wasFatal ) {
59              
60             $e->throw;
61              
62             } else {
63              
64             # do something with the answer
65              
66             }
67              
68             =head1 DESCRIPTION
69              
70             Siebel::SOAP::Auth provides authentication for Oracle's Siebel inbound web services by implementing Session Management.
71              
72             Implementation uses a instance of Siebel::SOAP::Auth inside a C<transport_hook> C<sub>, passing to it the original request. The original request will
73             be modified, adding necessary authentication data to the SOAP Header. The instance of Siebel::SOAP::Auth will also try to manage the session and token expiration times and
74             request a new one before expiration, avoiding a new round-trip to the server for another successful request.
75              
76             Session Management for calling Siebel web services great improves speed, since regular authentication takes a loot of additional steps. This class will implement the management
77             of requesting tokens automatically (but some tuning with the parameters might be necessary).
78              
79             This class is tight coupled to L<XML::Compile::WSDL11> interface. By using it, it is expected that you will use everything else from L<XML::Compile>.
80              
81             This class is L<Moo> based and it uses also L<Log::Report> to provide debug information if required.
82              
83             =head1 ATTRIBUTES
84              
85             The following attributes are available:
86              
87             =head2 token_key
88              
89             A string of the respective key of the token value after response from the Siebel Server is converted to a hash by L<XML::Compile::WSDL11>.
90              
91             By default, it is defined as C<'{' . $self->get_header_ns() . '}SessionToken'>.
92              
93             This is a lazy attribute, as defined by L<Moo>.
94              
95             =cut
96              
97             has token_key => ( is => 'lazy', isa => Str, reader => 'get_token_key' );
98              
99             =head2 header_ns
100              
101             A string that represents the SOAP Header namespace.
102              
103             By default it is C<http://siebel.com/webservices>. You might want to change it depending
104             on the WSDL (current this is not done automatically).
105              
106             This is a read/write attribute.
107              
108             =cut
109              
110             has header_ns => (
111             is => 'rw',
112             isa => Str,
113             default => 'http://siebel.com/webservices',
114             reader => 'get_header_ns',
115             writer => 'set_header_ns'
116             );
117              
118             =head2 user
119              
120             A string representing the login to be used for web service authentication.
121              
122             This is a required attribute during object creation.
123              
124             =cut
125              
126             has user => (
127             is => 'rw',
128             isa => Str,
129             reader => 'get_user',
130             writer => 'set_user',
131             required => 1
132             );
133              
134             =head2 password
135              
136             A string representing the login password used for authentication.
137              
138             This is a required attribute during object creation.
139              
140             =cut
141              
142             has password => (
143             is => 'rw',
144             isa => Str,
145             reader => 'get_pass',
146             writer => 'set_pass',
147             required => 1
148             );
149              
150             =head2 token
151              
152             A string type attribute that holds the token.
153              
154             All token details are handled internally.
155              
156             =cut
157              
158             has token => (
159             is => 'ro',
160             isa => Str,
161             reader => 'get_token',
162             writer => '_set_token',
163             default => 'unset'
164             );
165              
166             =head2 lookup_ns
167              
168             A string type attribute that holds the namespace that must be used to find
169             the namespace prefix that should be used to declare the elements for authentication
170             in the SOAP header.
171              
172             By default is set to C<http://schemas.xmlsoap.org/soap/envelope/>.
173              
174             This is a read/write attribute.
175              
176             =cut
177              
178             has lookup_ns => (
179             is => 'rw',
180             isa => Str,
181             default => 'http://schemas.xmlsoap.org/soap/envelope/',
182             reader => 'get_lookup_ns',
183             writer => 'set_lookup_ns'
184             );
185              
186             =head2 remain_ttl
187              
188             A integer that defines the minimum amount of remaining seconds the token should have before asking
189             for a new one.
190              
191             By default it is set to 10 seconds, but you might want to fine tune it depending on several factor, specially
192             the average time the request to a web services takes to have an answer.
193              
194             This is a read-only attribute, so you must set it's value during object creation.
195              
196             =cut
197              
198             has remain_ttl =>
199             ( is => 'ro', isa => Int, default => 10, reader => 'get_remain_ttl' );
200              
201             =head2 session_type
202              
203             A string type attribute that defines the session type to be used.
204              
205             By default it is set to C<Stateless>.
206              
207             This is a read-only attribute, so you must set it's value during object creation.
208              
209             =cut
210              
211             has session_type => (
212             is => 'ro',
213             isa => Str,
214             reader => 'get_session_type',
215             default => 'Stateless'
216             );
217              
218             =head2 last_fault
219              
220             A string attribute type that holds the error message received from the Siebel Server.
221              
222             This is a read-only attribute.
223              
224             =cut
225              
226             has last_fault => (
227             is => 'ro',
228             isa => Str,
229             reader => 'get_last_fault',
230             writer => '_set_last_fault'
231             );
232              
233             =head2 auth_fault
234              
235             A compiled regular expression that is used to match specific SOAP faults returned by the Siebel Server
236             that means "token expired".
237              
238             It is set by default to C</^Error\sCode:\s10944642/>.
239              
240             =cut
241              
242             has auth_fault => (
243             is => 'ro',
244             isa => RegexpRef,
245             reader => 'get_auth_fault',
246             default => sub { qr/^Error\sCode:\s10944642/ }
247             );
248              
249             =head2 session_timeout
250              
251             A integer type attribute that defines the Session Timeout to be considered, as defined on the Siebel Server.
252              
253             By default is set to 900 seconds.
254              
255             This is a read-only attribute.
256              
257             =cut
258              
259             has session_timeout =>
260             ( is => 'ro', isa => Int, default => 900, reader => 'get_session_timeout' );
261              
262             =head2 token_timeout
263              
264             A integer type attribute that defines the Token Timeout to be considered, as defined on the Siebel Server.
265              
266             By default is set to 900 seconds.
267              
268             This is a read-only attribute.
269              
270             =cut
271              
272             has token_timeout =>
273             ( is => 'ro', isa => Int, default => 900, reader => 'get_token_timeout' );
274              
275             =head2 token_max_age
276              
277             A integer type attribute that defines the Token Maximum Age to be considered, as defined on the Siebel Server.
278              
279             By default is set to 172800 seconds.
280              
281             This is a read-only attribute.
282              
283             =cut
284              
285             has token_max_age =>
286             ( is => 'ro', isa => Int, default => 172800, reader => 'get_token_max_age' )
287             ; # 2880 minutes
288              
289             has _token_birth => (
290             is => 'ro',
291             isa => Num,
292             reader => '_get_token_birth',
293             writer => '_set_token_birth',
294             predicate => 1,
295             clearer => 1
296             );
297              
298             =head1 METHODS
299              
300             All attributes have their respectiver getters and setters as defined in the Perl Best Practices book.
301              
302             Of course, read-only attributes have only getters.
303              
304             This is the list of those methods with a brief explanation:
305              
306             =head2 get_token_key
307              
308             Getter for the C<token_key> attribute.
309              
310             =head2 get_header_ns
311              
312             Getter for the C<header_ns> attribute.
313              
314             =head2 set_header_ns
315              
316             Setter for the C<header_ns> attribute.
317              
318             =head2 get_user
319              
320             Getter for the C<user> attribute.
321              
322             =head2 set_user
323              
324             Setter for the C<user> attribute.
325              
326             =head2 get_pass
327              
328             Getter for the C<password> attribute.
329              
330             =head2 set_pass
331              
332             Setter for the C<password> attribute.
333              
334             =head2 get_token
335              
336             Getter for the C<token> attribute.
337              
338             =head2 get_lookup_ns
339              
340             Getter for the C<lookup_ns> attribute.
341              
342             =head2 get_remain_ttl
343              
344             Getter for the C<remain_ttl> attribute.
345              
346             =head2 get_session_type
347              
348             Getter for the C<session_type> attribute.
349              
350             =head2 get_session_timeout
351              
352             Getter for the C<session_timeout> attribute.
353              
354             =head2 get_token_timeout
355              
356             Getter for the C<token_timeout> attribute.
357              
358             =head2 get_token_max_age
359              
360             Getter for the C<token_max_age> attribute.
361              
362             =head2 get_auth_fault
363              
364             Getter for the C<auth_fault> attribute.
365              
366             =head2 get_last_fault
367              
368             Getter for the C<last_fault> attribute.
369              
370             =cut
371              
372             sub _build_token_key {
373              
374             my ($self) = @_;
375             return '{' . $self->get_header_ns() . '}SessionToken';
376              
377             }
378              
379             =head2 add_auth_header
380              
381             This method B<must> be invoked inside the C<transport_hook> parameter definition during the call to C<compileClient> method
382             of L<XML::Compile::WSDL11>.
383              
384             It expects as parameter the original L<HTTP::Request> object, that will have its payload modified (SOAP Header).
385              
386             It returns the L<HTTP::Request> with its payload modified.
387              
388             =cut
389              
390             sub add_auth_header {
391              
392             #my ($self, $request, $ua) = @_;
393             my ( $self, $request ) = @_;
394              
395             croak "Expect as parameter a HTTP::Request instance"
396             unless ( ( defined($request) )
397             and ( defined( blessed($request) ) )
398             and ( $request->isa('HTTP::Request') ) );
399              
400             #die "Expect as parameter a LWP::UserAgent object" unless ( (defined($ua)) and (defined(blessed($ua))) and ($ua->isa('LWP::UserAgent')) );
401              
402             my $payload = XML::LibXML->load_xml( string => $request->decoded_content );
403             my $root = $payload->getDocumentElement;
404             my $prefix = $root->lookupNamespacePrefix( $self->get_lookup_ns() );
405             my $soap_header = $payload->createElement( $prefix . ':Header' );
406             my %auth;
407              
408             if ( $self->get_token() ne 'unset' ) {
409              
410             # how long the token is around plus the acceptable remaining seconds to be reused
411             my $token_age =
412             time() - $self->_get_token_birth() + $self->get_remain_ttl();
413             trace "token age is $token_age";
414              
415             if ( ( $token_age < $self->get_token_max_age() )
416             and ( $token_age < $self->get_session_timeout() )
417             and ( $token_age < $self->get_token_timeout() ) )
418             {
419              
420             %auth = (
421             SessionToken => $self->get_token(),
422             SessionType => $self->get_session_type()
423             );
424             trace 'using acquired session token';
425              
426             }
427             else {
428              
429             trace 'preparing to request a new session token';
430             %auth = (
431             SessionType => $self->get_session_type(),
432             UsernameToken => $self->get_user(),
433             PasswordText => $self->get_pass()
434             );
435             $self->_set_token('unset'); # sane setting
436             $self->_clear_token_birth();
437             trace 'cleaned up token and token_birth attributes';
438              
439             }
440              
441             }
442             else {
443              
444             %auth = (
445             SessionType => $self->get_session_type(),
446             UsernameToken => $self->get_user(),
447             PasswordText => $self->get_pass()
448             );
449              
450             }
451              
452             my $ns = $self->get_header_ns();
453              
454             # WORKAROUND: sort is used to make it easier to test the request assembly
455             foreach my $element_name ( sort( keys(%auth) ) ) {
456              
457             my $child = $payload->createElementNS( $ns, $element_name );
458             $child->appendText( $auth{$element_name} );
459             $soap_header->appendChild($child);
460              
461             }
462              
463             $root->insertBefore( $soap_header, $root->firstChild );
464              
465             my $new_content = encode( 'UTF-8', $root );
466             $request->header( Content_Length => length($new_content) );
467             $request->content($new_content);
468             return $request;
469              
470             }
471              
472             =head2 find_token
473              
474             Finds and set the token returned by the Siebel Server in the response of a request.
475              
476             It expects as parameter the hash reference returned by the execution of the code reference created by the
477             C<compileClient> method of L<XML::Compile::WSDL11>. This hash reference must have a key as defined by the
478             C<token_key> attribute of a instance of Siebel::SOAP::Auth.
479              
480             The method also will invoke C<check_fault> automatically to validate the answer.
481              
482             Once the token is found, the object updates itself internally. Otherwise an exception will be raised.
483              
484             =cut
485              
486             sub find_token {
487              
488             my ( $self, $answer ) = @_;
489              
490             croak "Expect as parameter a hash reference"
491             unless ( ( defined($answer) ) and ( ref($answer) eq 'HASH' ) );
492              
493             $self->check_fault($answer);
494             my $key = $self->get_token_key();
495              
496             if ( exists( $answer->{$key} ) ) {
497              
498             croak "Expect as parameter a XML::LibXML::Element instance"
499             unless ( ( defined( $answer->{$key} ) )
500             and ( defined( blessed( $answer->{$key} ) ) )
501             and ( $answer->{$key}->isa('XML::LibXML::Element') ) );
502              
503             $self->_set_token( $answer->{$key}->textContent );
504             $self->_set_token_birth( time() ) unless ( $self->_has_token_birth );
505             return 1;
506              
507             }
508             else {
509              
510             croak "could not find the key $key in the answer received as parameter";
511              
512             }
513              
514             }
515              
516             =head2 check_fault
517              
518             Verifies if an answer from the Siebel Server is a failure of not.
519              
520             Expects as parameter the answer.
521              
522             If the fault returned by the server is related to a token expiration, a exception
523             will be raised with the text "token expired".
524              
525             That means you should check for this exception with the C<try> function of L<Log::Report> and
526             provide a fallback routine (like resending your request). The instance of Siebel::SOAP::Auth will
527             also resets the token status internally to allow reuse.
528              
529             For other errors, an exception will be created with exactly the same message available in the fault
530             element of the SOAP envelope. You should evaluate the error message and take appropriate measures.
531              
532             =cut
533              
534             sub check_fault {
535              
536             my ( $self, $answer ) = @_;
537              
538             if ( exists( $answer->{Fault} ) ) {
539              
540             $self->_set_token('unset'); # sane setting
541             $self->_clear_token_birth();
542             trace 'cleaned up token and token_birth attributes';
543             if ( $answer->{Fault}->{faultstring} =~ $self->get_auth_fault() ) {
544              
545             croak 'token expired';
546              
547             }
548             else {
549              
550             croak $answer->{Fault}->{faultstring};
551              
552             }
553              
554             }
555              
556             }
557              
558             =head1 DEBUGGING
559              
560             You can enable debug messages for your code in the same way it is possible for L<XML::Compile::WSDL11>: by using L<Log::Report>. For example, the line:
561              
562             use Log::Report mode => 'DEBUG';
563              
564             Added to your code will print trace messages of Siebel::SOAP::Auth object.
565              
566             To disable just change the line to:
567              
568             use Log::Report mode => 'NORMAL';
569              
570             =head1 SEE ALSO
571              
572             =over
573              
574             =item *
575              
576             L<XML::Compile::WSDL11>
577              
578             =item *
579              
580             L<Log::Report>
581              
582             =item *
583              
584             L<Moo>
585              
586             =back
587              
588             =head1 AUTHOR
589              
590             Alceu Rodrigues de Freitas Junior, E<lt>arfreitas@cpan.orgE<gt>
591              
592             =head1 COPYRIGHT AND LICENSE
593              
594             This software is copyright (c) 2015 of Alceu Rodrigues de Freitas Junior, E<lt>arfreitas@cpan.orgE<gt>
595              
596             This file is part of Siebel-SOAP-Auth distribution.
597              
598             Siebel-SOAP-Auth is free software: you can redistribute it and/or modify
599             it under the terms of the GNU General Public License as published by
600             the Free Software Foundation, either version 3 of the License, or
601             (at your option) any later version.
602              
603             Siebel-SOAP-Auth is distributed in the hope that it will be useful,
604             but WITHOUT ANY WARRANTY; without even the implied warranty of
605             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
606             GNU General Public License for more details.
607              
608             You should have received a copy of the GNU General Public License
609             along with Term-YAP. If not, see <http://www.gnu.org/licenses/>.
610              
611             =cut
612              
613             1;