File Coverage

blib/lib/CGI/Application/Plugin/WSSE.pm
Criterion Covered Total %
statement 21 55 38.1
branch 0 14 0.0
condition n/a
subroutine 7 10 70.0
pod 2 2 100.0
total 30 81 37.0


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             CGI::Application::Plugin::WSSE - Simple WSSE Authentication for CGI applications
5              
6             =head1 SYNOPSIS
7              
8             use base 'CGI::Application';
9             use CGI::Application::Plugin::WSSE;
10            
11             sub setup {
12             ...
13            
14             $self->wsse_config(
15             callback => sub {
16             my ($self, $username) = @_;
17            
18             # get password associated with this username somehow
19              
20             return $password;
21             },
22             log => $self->log,
23             );
24             }
25            
26             sub runmode {
27              
28             if ( ! $self->wsse_authenticate( ) ) {
29             # authentication failed.
30             return $self->error( title => 'Error', msg => 'Access denied!');
31             }
32              
33             # authentication worked so do stuff.
34             }
35              
36             =head1 ABSTRACT
37              
38             This plugin enables WSSE Username Token authentication in web applications
39             using the CGI::Application framework.
40              
41             =cut
42              
43             package CGI::Application::Plugin::WSSE;
44              
45 1     1   26375 use warnings;
  1         2  
  1         36  
46 1     1   3 use strict;
  1         1  
  1         29  
47 1     1   4 use base qw( Exporter );
  1         2  
  1         71  
48 1     1   5 use Carp qw( croak );
  1         1  
  1         58  
49 1     1   556 use Digest::SHA1 qw( sha1 );
  1         610  
  1         83  
50 1     1   560 use MIME::Base64 qw( encode_base64 decode_base64 );
  1         545  
  1         79  
51 1     1   498 use Params::Validate qw( validate SCALAR CODEREF OBJECT );
  1         7861  
  1         597  
52              
53             =head1 VERSION
54              
55             This document describes CGI::Application::Plugin::WSSE Version 1.0
56              
57             =cut
58              
59             our $VERSION = '1.0';
60              
61             =head1 DESCRIPTION
62              
63             Use this module to implement WSSE Username Token authentication in your
64             CGI::Application based module. This protocol is transported over HTTP headers
65             and is safe to send as clear text while still providing security against
66             password sniffing and replay attacks.
67              
68             =head1 FUNCTIONS
69              
70             The following functions are imported into your L subclass.
71              
72             =cut
73              
74             our @EXPORT = qw( wsse_config wsse_authenticate );
75              
76             =head2 wsse_config(%options)
77              
78             This method is used to configure the way authentication is done. It takes a
79             hash of parameters. The following parameters are valid:
80              
81             =over 4
82              
83             =item C
84              
85             A code reference to a subroutine you provide in your app which sets the takes
86             two parameters, a reference to the calling object (your L
87             object, and a reference to a scalar containing a username. The subroutine
88             should use the username to look up its associated password in some
89             application-defined way and return that password as a scalar or undef if the
90             password is not found. This parameter is mandatory.
91              
92             =item C
93              
94             A reference to an object that supports C, and C methods. If
95             you are using L, set this to C<$self-Elog>. If this
96             parameter is not specified, no logging will be done.
97              
98             =back
99              
100             =cut
101              
102             sub wsse_config {
103 0     0 1   my $self = shift;
104              
105 0           my %args = validate(
106             @_,
107             {
108             callback => { type => CODEREF, },
109             log => {
110             type => OBJECT,
111             can => [qw/ debug error /],
112             optional => 1,
113             },
114             }
115             );
116              
117 0           $self->{WSSE_OPTS} = \%args;
118              
119 0           return;
120             }
121              
122             =head2 wsse_authenticate()
123              
124             Call this method to authenticate using the WSSE Username Token protocol. It
125             will return true if authentication was successful or false if it fails. In
126             the case of failure, a WWW-Authenticate HTTP header is added to the response
127             with the value C. The HTTP status is also
128             set to an appropriate value. See L.
129              
130             =cut
131              
132             sub wsse_authenticate {
133 0     0 1   my ($self) = @_;
134              
135 0 0         if ( !exists $self->{WSSE_OPTS} ) {
136 0           croak "Must call wsse_config first.\n";
137             }
138              
139 0           my $req = $self->query->http('X-WSSE');
140 0 0         if ( !$req ) {
141 0           return _wsse_auth_failure( $self, '401',
142             'X-WSSE authentication required' );
143             }
144              
145 0           $req =~ s/^(?:WSSE|UsernameToken)[ ]//msx;
146 0           my $auth;
147 0           for my $i ( split /,\s*/msx, $req ) {
148 0           my ( $k, $v ) = split /=/msx, $i, 2;
149 0           $v =~ s/^"//msx;
150 0           $v =~ s/"$//msx;
151 0           $auth->{$k} = $v;
152             }
153              
154 0           for my $f (qw( Username PasswordDigest Nonce Created )) {
155 0 0         if ( !defined $auth->{$f} ) {
156 0           return _wsse_auth_failure( $self, '400', "X-WSSE requires $f" );
157             }
158             }
159              
160 0           my $password = $self->{WSSE_OPTS}->{callback}->( $self, $auth->{Username} );
161 0 0         if ( !defined $password ) {
162 0           return _wsse_auth_failure( $self, '403', 'Invalid login' );
163             }
164              
165 0           my $expected = encode_base64(
166             sha1( decode_base64( $auth->{Nonce} ) . $auth->{Created} . $password ),
167             q{}
168             );
169 0 0         if ( $expected ne $auth->{PasswordDigest} ) {
170 0           return _wsse_auth_failure( $self, '403', 'Invalid login' );
171             }
172              
173 0 0         if ( exists $self->{WSSE_OPTS}->{log} ) {
174 0           $self->{WSSE_OPTS}->{log}->debug(
175             'Successfully authenticated user ' . $auth->{Username} . q{.} );
176             }
177              
178 0           return 1;
179             }
180              
181             sub _wsse_auth_failure {
182 0     0     my ( $self, $code, $msg ) = @_;
183              
184 0 0         if ( exists $self->{WSSE_OPTS}->{log} ) {
185 0           $self->{WSSE_OPTS}->{log}->error("$msg");
186             }
187             $self->header_add(
188 0           -WWW_Authenticate => 'WSSE profile="UsernameToken"',
189             -Status => "$code $msg",
190             );
191              
192 0           return;
193             }
194              
195             =head1 DIAGNOSTICS
196              
197             During the authentication process, errors can occur in certain circumstances.
198             If an error occurs the HTTP status is set to one of the following values.
199              
200             =over 4
201              
202             =item 400 X-WSSE requires $part
203              
204             The X-WSSE HTTP header consists of four values, Username, PasswordDigest,
205             Nonce, and Created. If one or more of these are missing, the header is
206             malformed and this error is returned. (C<$part> is the first missing part
207             encountered.)
208              
209             =item 401 X-WSSE authentication required
210              
211             This error is returned if the client request does not contain an X-WSSE HTTP
212             header. Often the client will not send X-WSSE initially but but will retry
213             with the correct header in response to a 401 status like this.
214              
215             =item 403 Invalid login
216              
217             This error is returned if either the username or password sent by the client
218             are invalid or missing.
219              
220             =back
221              
222             =head1 BUGS AND LIMITATIONS
223              
224             There are no known problems with this module.
225              
226             Please report any bugs or feature requests to
227             C, or through the web interface at
228             L.
229             I will be notified, and then you'll automatically be notified of progress on
230             your bug as I make changes.
231              
232             =head1 SEE ALSO
233              
234             L
235              
236             =head1 THANKS
237              
238             Some code lifted from L and L
239              
240             =head1 AUTHOR
241              
242             Jaldhar H. Vyas, C<< >>
243              
244             =head1 LICENSE AND COPYRIGHT
245              
246             Copyright (C) 2011, Consoliated Braincells Inc. All rights reserved.
247              
248             This distribution is free software; you can redistribute it and/or modify it
249             under the terms of either:
250              
251             a) the GNU General Public License as published by the Free Software
252             Foundation; either version 2, or (at your option) any later version, or
253              
254             b) the Artistic License version 2.0.
255              
256             The full text of the license can be found in the LICENSE file included
257             with this distribution.
258              
259             =cut
260              
261             1; # CGI::Application::Plugin::WSSE
262              
263             __END__