File Coverage

blib/lib/Lemonldap/NG/Handler/Specific/SecureToken.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             ##@file
2             # Secure Token
3              
4             ##@class
5             # Secure Token
6             #
7             # Create a secure token used to resolve user identity by a protected application
8              
9             # This specific handler is intended to be called directly by Apache
10              
11             package Lemonldap::NG::Handler::Specific::SecureToken;
12              
13 1     1   119712 use strict;
  1         2  
  1         32  
14 1     1   529 use Lemonldap::NG::Handler::SharedConf qw(:all);
  0            
  0            
15             use base qw(Lemonldap::NG::Handler::SharedConf);
16             use Cache::Memcached;
17             use Apache::Session::Generate::MD5;
18             use Lemonldap::NG::Handler::Main::Headers;
19             use Lemonldap::NG::Handler::Main::Logger;
20              
21             our $VERSION = '1.1.2';
22              
23             # Shared variables
24             our (
25             $secureTokenMemcachedServers, $secureTokenExpiration,
26             $secureTokenAttribute, $secureTokenUrls,
27             $secureTokenHeader, $datas,
28             $secureTokenMemcachedConnection, $secureTokenAllowOnError,
29             );
30              
31             BEGIN {
32             eval {
33             require threads::shared;
34             threads::share($secureTokenMemcachedConnection);
35             };
36             }
37              
38             ## @imethod protected void globalInit(hashRef args)
39             # Overload globalInit to launch this class defaultValuesInit
40             # @param $args reference to the configuration hash
41             sub globalInit {
42             my $class = shift;
43             __PACKAGE__->defaultValuesInit(@_);
44             $class->SUPER::globalInit(@_);
45             }
46              
47             ## @imethod protected void defaultValuesInit(hashRef args)
48             # Overload defaultValuesInit
49             # @param $args reference to the configuration hash
50             sub defaultValuesInit {
51             my ( $class, $args ) = splice @_;
52              
53             # Catch Secure Token parameters
54             $secureTokenMemcachedServers =
55             $args->{'secureTokenMemcachedServers'}
56             || $secureTokenMemcachedServers
57             || ['127.0.0.1:11211'];
58             $secureTokenExpiration =
59             $args->{'secureTokenExpiration'}
60             || $secureTokenExpiration
61             || '60';
62             $secureTokenAttribute =
63             $args->{'secureTokenAttribute'}
64             || $secureTokenAttribute
65             || 'uid';
66             $secureTokenUrls = $args->{'secureTokenUrls'} || $secureTokenUrls || ['.*'];
67             $secureTokenHeader =
68             $args->{'secureTokenHeader'}
69             || $secureTokenHeader
70             || 'Auth-Token';
71             $args->{'secureTokenAllowOnError'} = 1
72             unless defined $args->{'secureTokenAllowOnError'};
73             $secureTokenAllowOnError =
74             defined $secureTokenAllowOnError
75             ? $secureTokenAllowOnError
76             : $args->{'secureTokenAllowOnError'};
77              
78             # Force some parameters to be array references
79             foreach (qw/secureTokenMemcachedServers secureTokenUrls/) {
80             no strict 'refs';
81             unless ( ref ${$_} eq "ARRAY" ) {
82             Lemonldap::NG::Handler::Main::Logger->lmLog(
83             "Transform $_ value into an array reference", 'debug' );
84             my @array = split( /\s+/, ${$_} );
85             ${$_} = \@array;
86             }
87             }
88              
89             # Display found values in debug mode
90             Lemonldap::NG::Handler::Main::Logger->lmLog(
91             "secureTokenMemcachedServers: @$secureTokenMemcachedServers", 'debug' );
92             Lemonldap::NG::Handler::Main::Logger->lmLog(
93             "secureTokenExpiration: $secureTokenExpiration", 'debug' );
94             Lemonldap::NG::Handler::Main::Logger->lmLog(
95             "secureTokenAttribute: $secureTokenAttribute", 'debug' );
96             Lemonldap::NG::Handler::Main::Logger->lmLog(
97             "secureTokenUrls: @$secureTokenUrls", 'debug' );
98             Lemonldap::NG::Handler::Main::Logger->lmLog(
99             "secureTokenHeader: $secureTokenHeader", 'debug' );
100             Lemonldap::NG::Handler::Main::Logger->lmLog(
101             "secureTokenAllowOnError: $secureTokenAllowOnError", 'debug' );
102              
103             # Delete Secure Token parameters
104             delete $args->{'secureTokenMemcachedServers'};
105             delete $args->{'secureTokenExpiration'};
106             delete $args->{'secureTokenAttribute'};
107             delete $args->{'secureTokenUrls'};
108             delete $args->{'secureTokenHeader'};
109             delete $args->{'secureTokenAllowOnError'};
110              
111             # Call main subroutine
112             return $class->SUPER::defaultValuesInit($args);
113             }
114              
115             ## @rmethod Apache2::Const run(Apache2::RequestRec r)
116             # Overload main run method
117             # @param r Current request
118             # @return Apache2::Const value (OK, FORBIDDEN, REDIRECT or SERVER_ERROR)
119             sub run {
120             my $class = shift;
121             my $r = $_[0];
122             my $ret = $class->SUPER::run(@_);
123              
124             # Continue only if user is authorized
125             return $ret unless ( $ret == OK );
126              
127             # Get current URI
128             my $args = $r->args;
129             my $uri = $r->uri . ( $args ? "?$args" : "" );
130              
131             # Return if we are not on a secure token URL
132             my $checkurl = 0;
133             foreach (@$secureTokenUrls) {
134             if ( $uri =~ m#$_# ) {
135             $checkurl = 1;
136             Lemonldap::NG::Handler::Main::Logger->lmLog(
137             "URL $uri detected as an Secure Token URL (rule $_)", 'debug' );
138             last;
139             }
140             }
141             return OK unless ($checkurl);
142              
143             # Test Memcached connection
144             unless ( $class->_isAlive() ) {
145             $secureTokenMemcachedConnection = $class->_createMemcachedConnection();
146             }
147              
148             # Exit if no connection
149             return $class->_returnError() unless $class->_isAlive();
150              
151             # Value to store
152             my $value = $datas->{$secureTokenAttribute};
153              
154             # Set token
155             my $key = $class->_setToken($value);
156             return $class->_returnError() unless $key;
157              
158             # Header location
159             Lemonldap::NG::Handler::Main::Headers->lmSetHeaderIn( $r,
160             $secureTokenHeader => $key );
161              
162             # Remove token
163             eval 'use Apache2::Filter' unless ( $INC{"Apache2/Filter.pm"} );
164              
165             $r->add_output_filter(
166             sub {
167             my $f = shift;
168             while ( $f->read( my $buffer, 1024 ) ) {
169             $f->print($buffer);
170             }
171             if ( $f->seen_eos ) {
172             $class->_deleteToken($key);
173             }
174             return OK;
175             }
176             );
177              
178             # Return OK
179             return OK;
180             }
181              
182             ## @method private Cache::Memcached _createMemcachedConnection
183             # Create Memcached connexion
184             # @return Cache::Memcached object
185             sub _createMemcachedConnection {
186             my ($class) = splice @_;
187              
188             # Open memcached connexion
189             my $memd = new Cache::Memcached {
190             'servers' => $secureTokenMemcachedServers,
191             'debug' => 0,
192             };
193              
194             Lemonldap::NG::Handler::Main::Logger->lmLog( "Memcached connection created",
195             'debug' );
196              
197             return $memd;
198             }
199              
200             ## @method private string _setToken(string value)
201             # Set token value
202             # @param value Value
203             # @return Token key
204             sub _setToken {
205             my ( $class, $value ) = splice @_;
206              
207             my $key = Apache::Session::Generate::MD5::generate();
208              
209             my $res =
210             $secureTokenMemcachedConnection->set( $key, $value,
211             $secureTokenExpiration );
212              
213             unless ($res) {
214             Lemonldap::NG::Handler::Main::Logger->lmLog(
215             "Unable to store secure token $key", 'error' );
216             return;
217             }
218              
219             Lemonldap::NG::Handler::Main::Logger->lmLog( "Set $value in token $key",
220             'info' );
221              
222             return $key;
223             }
224              
225             ## @method private boolean _deleteToken(string key)
226             # Delete token
227             # @param key Key
228             # @return result
229             sub _deleteToken {
230             my ( $class, $key ) = splice @_;
231              
232             my $res = $secureTokenMemcachedConnection->delete($key);
233              
234             unless ($res) {
235             Lemonldap::NG::Handler::Main::Logger->lmLog(
236             "Unable to delete secure token $key", 'error' );
237             }
238             else {
239             Lemonldap::NG::Handler::Main::Logger->lmLog( "Token $key deleted",
240             'info' );
241             }
242              
243             return $res;
244             }
245              
246             ## @method private boolean _isAlive()
247             # Run a STATS command to see if Memcached connection is alive
248             # @param connection Cache::Memcached object
249             # @return result
250             sub _isAlive {
251             my ($class) = splice @_;
252              
253             return 0 unless defined $secureTokenMemcachedConnection;
254              
255             my $stats = $secureTokenMemcachedConnection->stats();
256              
257             if ( $stats and defined $stats->{'total'} ) {
258             my $total_c = $stats->{'total'}->{'connection_structures'};
259             my $total_i = $stats->{'total'}->{'total_items'};
260              
261             Lemonldap::NG::Handler::Main::Logger->lmLog(
262             "Memcached connection is alive ($total_c connections / $total_i items)",
263             'debug'
264             );
265              
266             return 1;
267             }
268              
269             Lemonldap::NG::Handler::Main::Logger->lmLog(
270             "Memcached connection is not alive", 'error' );
271              
272             return 0;
273             }
274              
275             ## @method private int _returnError()
276             # Give hand back to Apache
277             # @return Apache2::Const value
278             sub _returnError {
279             my ($class) = splice @_;
280              
281             if ($secureTokenAllowOnError) {
282             Lemonldap::NG::Handler::Main::Logger->lmLog(
283             "Allow request without secure token", 'debug' );
284             return OK;
285             }
286              
287             # Redirect or Forbidden?
288             if ( $tsv->{useRedirectOnError} ) {
289             Lemonldap::NG::Handler::Main::Logger->lmLog( "Use redirect for error",
290             'debug' );
291             return $class->goToPortal( '/', 'lmError=500' );
292             }
293              
294             else {
295             Lemonldap::NG::Handler::Main::Logger->lmLog( "Return error", 'debug' );
296             return SERVER_ERROR;
297             }
298             }
299              
300             __PACKAGE__->init( {} );
301              
302             1;
303              
304             __END__