File Coverage

blib/lib/Atompub/Server.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Atompub::Server;
2              
3 1     1   30241 use strict;
  1         3  
  1         39  
4 1     1   6 use warnings;
  1         2  
  1         30  
5              
6 1     1   877 use Atompub;
  0            
  0            
7             use Digest::SHA qw(sha1);
8             use MIME::Base64 qw(encode_base64 decode_base64);
9             use HTTP::Status;
10             use XML::Atom;
11              
12             use base qw(XML::Atom::Server);
13              
14             sub send_http_header {
15             my($server) = @_;
16             my $type = $server->response_content_type || 'application/atom+xml';
17             if ($ENV{MOD_PERL}) {
18             $server->{apache}->status($server->response_code || RC_OK);
19             $server->{apache}->send_http_header($type);
20             }
21             else {
22             $server->{cgi_headers}{-status} = $server->response_code || RC_OK;
23             $server->{cgi_headers}{-type} = $type;
24             print $server->{cgi}->header(%{ $server->{cgi_headers} });
25             }
26             }
27              
28             sub realm {
29             my($server, $realm) = @_;
30             $server->{realm} = $realm if $realm;
31             $server->{realm};
32             }
33              
34             sub get_auth_info {
35             my($server) = @_;
36             my %param;
37              
38             # Basic Authentication
39             if (my $auth = $server->request_header('Authorization')) {
40             return unless $auth =~ s/^\s*Basic\s+//;
41             require MIME::Base64;
42             my $val = MIME::Base64::decode($auth);
43             my($userid, $password) = split /:/, $val, 2;
44             %param = (userid => $userid, password => $password);
45             }
46             # WSSE Authentication
47             elsif (my $req = $server->request_header('X-WSSE')) {
48             $req =~ s/^(?:WSSE|UsernameToken) //;
49             for my $i (split /,\s*/, $req) {
50             my($k, $v) = split /=/, $i, 2;
51             $v =~ s/^"//;
52             $v =~ s/"$//;
53             $param{$k} = $v;
54             }
55             }
56             else {
57             return $server->auth_failure(RC_UNAUTHORIZED, 'Basic or WSSE authentication required');
58             }
59              
60             \%param;
61             }
62              
63             sub authenticate {
64             my($server) = @_;
65              
66             my $auth = $server->get_auth_info || return;
67              
68             # Basic Authentication
69             if (defined $auth->{userid}) {
70             my $password = $server->password_for_user($auth->{userid});
71             return $server->auth_failure(RC_FORBIDDEN, 'Invalid login')
72             if !defined $password || $password ne $auth->{password};
73             }
74             # WSSE Authentication
75             else {
76             for my $f (qw(Username PasswordDigest Nonce Created)) {
77             return $server->auth_failure(RC_BAD_REQUEST, "X-WSSE requires $f")
78             unless $auth->{$f};
79             }
80             my $password = $server->password_for_user($auth->{Username});
81             return $server->auth_failure(RC_FORBIDDEN, 'Invalid login')
82             unless defined $password;
83             my $expected
84             = encode_base64(sha1(decode_base64($auth->{Nonce}).$auth->{Created}.$password), '');
85             return $server->auth_failure(RC_FORBIDDEN, 'Invalid login')
86             unless $expected eq $auth->{PasswordDigest};
87             }
88              
89             1;
90             }
91              
92             sub auth_failure {
93             my($server) = @_;
94             my $realm = $server->realm || 'Atompub';
95             $server->response_header(
96             'WWW-Authenticate',
97             qq{Basic realm="$realm", WSSE profile="UsernameToken"},
98             );
99             $server->error(@_);
100             }
101              
102             1;
103             __END__
104              
105             =head1 NAME
106              
107             Atompub::Server - A server for the Atom Publishing Protocol
108              
109              
110             =head1 SYNOPSIS
111              
112             package My::Server;
113             use base qw(Atompub::Server);
114              
115             sub handle_request {
116             my($server) = @_;
117             $server->authenticate or return;
118             my $method = $server->request_method;
119             if ($method eq 'POST') {
120             return $server->new_post;
121             }
122             ...
123             }
124              
125             my %Passwords;
126             sub password_for_user {
127             my($server, $username) = @_;
128             $Passwords{$username};
129             }
130              
131             sub new_post {
132             my($server) = @_;
133             my $entry = $server->atom_body or return;
134             # $entry is an XML::Atom::Entry object.
135             # ... Save the new entry ...
136             }
137              
138             package main;
139             my $server = My::Server->new;
140             $server->run;
141              
142             =head1 DESCRIPTION
143              
144             L<Atompub::Server> provides a base class for Atom Publishing Protocol servers.
145             It handles all core server processing, and Basic and WSSE authentication.
146             It can also run as either a mod_perl handler or as part of a CGI program.
147              
148             It does not provide functions specific to any particular implementation,
149             such as creating an entry, retrieving a list of entries, deleting an entry, etc.
150             Implementations should subclass L<Atompub::Server>, overriding the
151             C<handle_request> method, and handle all functions such as this themselves.
152              
153             L<Atompub::Server> extends L<XML::Atom::Server>, and basically provides same functions.
154             However, this module has been fixed based on the Atom Publishing Protocol
155             described at L<http://www.ietf.org/rfc/rfc5023.txt>,
156             and supports Basic authentication rather than WSSE.
157              
158              
159             =head1 SUBCLASSING
160              
161             =head2 Request Handling
162              
163             Subclasses of L<Atompub::Server> must override the C<handle_request>
164             method to perform all request processing.
165             The implementation must set all response headers, including the response
166             code and any relevant HTTP headers, and should return a scalar representing
167             the response body to be sent back to the client.
168              
169             For example:
170              
171             sub handle_request {
172             my($server) = @_;
173             my $method = $server->request_method;
174             if ($method eq 'POST') {
175             return $server->new_post;
176             }
177             # ... handle GET, PUT, etc
178             }
179              
180             sub new_post {
181             my($server) = @_;
182             my $entry = $server->atom_body or return;
183              
184             # Implementation-specific
185             my $id = save_this_entry($entry);
186             my $location = join '/', $server->uri, $id;
187             my $etag = calc_etag($entry);
188              
189             $server->response_header(Location => $location);
190             $server->response_header(ETag => $etag );
191             $server->response_code(RC_CREATED);
192             $server->response_content_type('application/atom+xml;type=entry');
193              
194             # Implementation-specific
195             return serialize_entry($entry);
196             }
197              
198             =head2 Authentication
199              
200             Servers that require authentication should override the C<password_for_user> method.
201             Given a username (from the Authorization or WSSE header),
202             C<password_for_user> should return that user's password in plaintext.
203             If the supplied username doesn't exist in your user database or alike,
204             just return C<undef>.
205              
206             For example:
207              
208             my %Passwords = (foo => 'bar'); # The password for "foo" is "bar".
209             sub password_for_user {
210             my($server, $username) = @_;
211             $Passwords{$username};
212             }
213              
214             =over 2
215              
216             =item * Basic Authentication
217              
218             I<realm> must be assigned before authentication for Basic authentication.
219              
220             $server->realm('MySite');
221              
222             If your server runs as a CGI program and authenticates by Basic authenticate,
223             you should use authentication mechanism of the http server, like C<.htaccess>.
224              
225             =item * WSSE Authentication
226              
227             Any pre-configuration is not required for WSSE.
228             The password returned from C<password_for_user> will be combined with the nonce
229             and the creation time to generate the digest, which will be compared
230             with the digest sent in the WSSE header.
231              
232             =back
233              
234              
235             =head1 METHODS
236              
237             L<Atompub::Server> provides a variety of methods to be used by subclasses
238             for retrieving headers, content, and other request information, and for
239             setting the same on the response.
240              
241             =head2 $server->realm
242              
243             If called with an argument, sets the I<realm> for Basic authentication.
244              
245             Returns the current I<realm> that will be used when receiving requests.
246              
247             =head2 $server->send_http_header($content_type)
248              
249             =head2 $server->get_auth_info
250              
251             =head2 $server->authenticate
252              
253             =head2 $server->auth_failure($status, $message)
254              
255             =head2 oether methods
256              
257             Descriptions are found in L<XML::Atom::Server>.
258              
259              
260             =head1 USAGE
261              
262             Once you have defined your server subclass, you can set it up either as a
263             CGI program or as a mod_perl handler.
264              
265             See L<XML::Atom::Server> in details.
266              
267              
268             =head1 SEE ALSO
269              
270             L<XML::Atom>
271             L<XML::Atom::Service>
272             L<Atompub>
273             L<Catalyst::Controller::Atompub>
274              
275              
276             =head1 AUTHOR
277              
278             Takeru INOUE, E<lt>takeru.inoue _ gmail.comE<gt>
279              
280              
281             =head1 LICENCE AND COPYRIGHT
282              
283             Copyright (c) 2007, Takeru INOUE C<< <takeru.inoue _ gmail.com> >>. All rights reserved.
284              
285             This module is free software; you can redistribute it and/or
286             modify it under the same terms as Perl itself. See L<perlartistic>.
287              
288              
289             =head1 DISCLAIMER OF WARRANTY
290              
291             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
292             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
293             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
294             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
295             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
296             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
297             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
298             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
299             NECESSARY SERVICING, REPAIR, OR CORRECTION.
300              
301             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
302             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
303             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
304             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
305             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
306             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
307             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
308             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
309             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
310             SUCH DAMAGES.
311              
312             =cut