File Coverage

blib/lib/App/MFILE/HTTP.pm
Criterion Covered Total %
statement 38 89 42.7
branch 0 22 0.0
condition 0 21 0.0
subroutine 13 18 72.2
pod 1 1 100.0
total 52 151 34.4


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32             #
33             # HTTP module
34             #
35             package App::MFILE::HTTP;
36              
37 1     1   800 use 5.012;
  1         3  
38 1     1   4 use strict;
  1         1  
  1         16  
39 1     1   4 use warnings;
  1         2  
  1         22  
40              
41 1     1   264 use App::CELL qw( $CELL $log $site $meta );
  1         69582  
  1         155  
42 1     1   13 use Data::Dumper;
  1         4  
  1         69  
43 1     1   569 use Encode qw( encode_utf8 );
  1         12030  
  1         92  
44 1     1   11 use Exporter qw( import );
  1         2  
  1         45  
45 1     1   410 use HTTP::Request::Common qw( GET PUT POST DELETE );
  1         14676  
  1         71  
46 1     1   388 use JSON;
  1         6687  
  1         5  
47 1     1   487 use LWP::UserAgent;
  1         15776  
  1         35  
48 1     1   8 use Params::Validate qw( :all );
  1         2  
  1         210  
49 1     1   10 use Try::Tiny;
  1         2  
  1         190  
50              
51              
52              
53             =head1 NAME
54              
55             App::MFILE::HTTP - general REST request forwarder for MFILE-based clients
56              
57              
58              
59             =head1 SYNOPSIS
60              
61             use App::MFILE::HTTP qw( rest_req );
62              
63              
64              
65             =head1 DESCRIPTION
66              
67             Module where C and other shared code resides.
68              
69              
70              
71             =head1 EXPORTS
72              
73             =cut
74              
75             our @EXPORT_OK = qw(
76             rest_req
77             _is_authorized
78             );
79              
80              
81              
82             =head1 FUNCTIONS
83              
84              
85             =head2 rest_req
86              
87             Algorithm: send request to REST server, get JSON response, decode it, return it.
88              
89             Takes a single _mandatory_ parameter: a LWP::UserAgent object
90              
91             Optionally takes PARAMHASH:
92              
93             server => [URI OF REST SERVER] default is 'http://0:5000'
94             method => [HTTP METHOD TO USE] default is 'GET'
95             nick => [NICK FOR BASIC AUTH] optional
96             password => [PASSWORD FOR BASIC AUTH] optional
97             path => [PATH OF REST RESOURCE] default is '/'
98             req_body => [HASHREF] optional
99              
100             Returns HASHREF containing:
101              
102             hr => HTTP::Response object (stripped of the body)
103             body => [BODY OF HTTP RESPONSE, IF ANY]
104              
105             =cut
106              
107             sub rest_req {
108              
109             # process arguments
110 0     0 1   my $ua = shift;
111 0 0         die "Bad user agent object" unless ref( $ua ) eq 'LWP::UserAgent';
112 0           my %ARGS = validate( @_, {
113             server => { type => SCALAR, default => 'http://localhost:5000' },
114             method => { type => SCALAR, default => 'GET', regex => qr/^(GET|POST|PUT|DELETE)$/ },
115             nick => { type => SCALAR, optional => 1 },
116             password => { type => SCALAR, default => '' },
117             path => { type => SCALAR, default => '/' },
118             req_body => { type => HASHREF, optional => 1 },
119             } );
120 0           $ARGS{'path'} =~ s/^\/*/\//;
121              
122 0           my $r;
123             {
124 1     1   7 no strict 'refs';
  1         2  
  1         561  
  0            
125 0           $r = &{ $ARGS{'method'} }( $ARGS{'server'} . encode_utf8( $ARGS{'path'} ),
  0            
126             Accept => 'application/json' );
127             }
128              
129 0 0         if ( $ARGS{'nick'} ) {
130 0           $r->authorization_basic( $ARGS{'nick'}, $ARGS{'password'} );
131             }
132              
133 0 0         if ( $ARGS{'method'} =~ m/^(POST|PUT)$/ ) {
134 0           $r->header( 'Content-Type' => 'application/json' );
135 0 0         if ( my $body = $ARGS{'req_body'} ) {
136 0           my $tmpvar = JSON->new->utf8(0)->encode( $body );
137 0           $r->content( encode_utf8( $tmpvar ) );
138             }
139             }
140              
141             # request is ready - send it and get response
142 0           my $response = $ua->request( $r );
143              
144             # process response
145 0           my $body_json = $response->decoded_content;
146 0           $log->debug( "rest_req: decoded JSON body " . Dumper $body_json );
147 0           $response->content('');
148 0           my $body;
149             try {
150 0     0     $body = JSON->new->decode( $body_json );
151             } catch {
152 0     0     $body = { 'code' => $body, 'text' => $body };
153 0           };
154              
155             return {
156 0           hr => $response,
157             body => $body
158             };
159             }
160              
161              
162             =head2 _is_authorized
163              
164             This function does the actual work for C in the Dispatch.pm
165             module of an C-based application.
166              
167             This function belongs in Dispatch.pm - it is here only to prevent code
168             duplication.
169              
170             =cut
171              
172             sub _is_authorized {
173 0     0     my ( $self ) = @_;
174              
175 0           $log->debug( "Entering " . __PACKAGE__ . "::_is_authorized()" );
176              
177 0           my $r = $self->request;
178             #my $session = $r->{'env'}->{'psgix.session'};
179 0           my $session = $self->session;
180             #my $remote_addr = $r->{'env'}->{'REMOTE_ADDR'};
181 0           my $remote_addr = $self->remote_addr;
182 0           my $ce;
183              
184             #$log->debug( "Environment is " . Dumper( $r->{'env'} ) );
185 0           $log->debug( "Session is " . Dumper( $session ) );
186              
187             # authorized session
188 0 0 0       if ( $ce = $session->{'currentUser'} and
      0        
      0        
189             $session->{'ip_addr'} and
190             $session->{'ip_addr'} eq $remote_addr and
191             _is_fresh( $session ) )
192             {
193 0           $log->debug( "is_authorized: Authorized session, employee " . $ce->{'nick'} );
194 0           $session->{'last_seen'} = time;
195 0           return 1;
196             }
197              
198             # login attempt
199 0 0 0       if ( $r->method eq 'POST' and
      0        
      0        
200             $self->context->{'request_body'} and
201             $self->context->{'request_body'}->{'method'} and
202             $self->context->{'request_body'}->{'method'} =~ m/^LOGIN/i ) {
203 0           $log->debug( "is_authorized: Login attempt - pass it on" );
204 0           return 1;
205             }
206              
207             # login bypass
208 0 0         $meta->set('META_LOGIN_BYPASS_STATE', 0) if not defined $meta->META_LOGIN_BYPASS_STATE;
209 0 0 0       if ( $site->MFILE_WWW_BYPASS_LOGIN_DIALOG and not $meta->META_LOGIN_BYPASS_STATE ) {
210 0           $log->notice("Bypassing login dialog! Using default credentials");
211 0           $session->{'ip_addr'} = $remote_addr;
212 0           $session->{'last_seen'} = time;
213             my $bypass_result = $self->_login_dialog( {
214             'nam' => $site->MFILE_WWW_DEFAULT_LOGIN_CREDENTIALS->{'nam'},
215 0           'pwd' => $site->MFILE_WWW_DEFAULT_LOGIN_CREDENTIALS->{'pwd'},
216             } );
217 0           $meta->set('META_LOGIN_BYPASS_STATE', 1);
218 0           return $bypass_result;
219             }
220              
221             # unauthorized session
222 0           $log->debug( "is_authorized fall-through: " . $r->method . " " . $self->request->path_info );
223 0 0         return ( $r->method eq 'GET' ) ? 1 : 0;
224             }
225              
226              
227             =head2 _is_fresh
228              
229             Takes a single argument, the PSGI session, which is assumed to contain a
230             C attribute containing the number of seconds since epoch when the
231             session was last seen.
232              
233             =cut
234              
235             sub _is_fresh {
236 0     0     my ( $session ) = validate_pos( @_, { type => HASHREF } );
237              
238 0 0         return 0 unless my $last_seen = $session->{'last_seen'};
239              
240 0 0         return ( time - $last_seen > $site->MFILE_WWW_SESSION_EXPIRATION_TIME )
241             ? 0
242             : 1;
243             }
244              
245              
246             1;