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