Add a body to the 401 status, add more docs!
[catagits/Catalyst-Authentication-Credential-HTTP.git] / lib / Catalyst / Plugin / Authentication / Credential / HTTP.pm
index b14e575..9bf79a0 100644 (file)
@@ -11,7 +11,7 @@ use URI::Escape    ();
 use Catalyst       ();
 use Digest::MD5    ();
 
-our $VERSION = "0.05";
+our $VERSION = "0.08";
 
 sub authenticate_http {
     my ( $c, @args ) = @_;
@@ -22,7 +22,12 @@ sub authenticate_http {
 
 sub get_http_auth_store {
     my ( $c, %opts ) = @_;
-    $opts{store} || $c->config->{authentication}{http}{store};
+
+    my $store = $opts{store} || $c->config->{authentication}{http}{store} || return;
+
+    return ref $store
+        ? $store
+        : $c->get_auth_store($store);
 }
 
 sub authenticate_basic {
@@ -68,7 +73,7 @@ sub authenticate_digest {
         } split /,\s?/, substr( $authorization, 7 );    #7 == length "Digest "
 
         my $opaque = $res{opaque};
-        my $nonce  = $c->_get_digest_authorization_nonce( __PACKAGE__ . '::opaque:' . $opaque );
+        my $nonce  = $c->get_digest_authorization_nonce( __PACKAGE__ . '::opaque:' . $opaque );
         next unless $nonce;
 
         $c->log->debug('Checking authentication parameters.')
@@ -101,11 +106,12 @@ sub authenticate_digest {
         my $realm    = $res{realm};
 
         my $user;
-        my $store = $opts{store}
-          || $c->config->{authentication}{http}{store}
-          || $c->default_auth_store;
 
-        $user = $store->get_user($username) if $store;
+        unless ( $user = $opts{user} ) {
+            if ( my $store = $c->get_http_auth_store(%opts) || $c->default_auth_store ) {
+                $user = $store->get_user($username);
+            }
+        }
 
         unless ($user) {    # no user, no authentication
             $c->log->debug('Unknown user: $user.') if $c->debug;
@@ -189,6 +195,10 @@ sub authorization_required_response {
     my ( $c, %opts ) = @_;
 
     $c->res->status(401);
+    $c->res->content_type('text/plain');
+    $c->res->body($c->config->{authentication}{http}{authorization_required_message} || 
+                  $opts{authorization_required_message} || 
+                  'Authorization required.');
 
     # *DONT* short circuit
     my $ok;
@@ -247,7 +257,7 @@ sub _build_auth_header_domain {
     my ( $c, $opts ) = @_;
 
     if ( my $domain = $opts->{domain} ) {
-        Catalyst::Excpetion->throw("domain must be an array reference")
+        Catalyst::Exception->throw("domain must be an array reference")
           unless ref($domain) && ref($domain) eq "ARRAY";
 
         my @uris =
@@ -272,7 +282,7 @@ sub _build_auth_header_common {
 
 sub _build_basic_auth_header {
     my ( $c, $opts ) = @_;
-    return $c->_join_auth_header_parts( Basic => $c->_build_auth_header_common );
+    return $c->_join_auth_header_parts( Basic => $c->_build_auth_header_common( $opts ) );
 }
 
 sub _build_digest_auth_header {
@@ -282,7 +292,7 @@ sub _build_digest_auth_header {
 
     my $key = __PACKAGE__ . '::opaque:' . $nonce->opaque;
    
-    $c->_store_digest_authorization_nonce( $key, $nonce );
+    $c->store_digest_authorization_nonce( $key, $nonce );
 
     return $c->_join_auth_header_parts( Digest =>
         $c->_build_auth_header_common($opts),
@@ -302,11 +312,9 @@ sub _digest_auth_nonce {
 
     my $nonce   = $package->new;
 
-    my $algorithm = $opts->{algorithm}
-      || $c->config->{authentication}{http}{algorithm}
-      || $nonce->algorithm;
-
-    $nonce->algorithm( $algorithm );
+    if ( my $algorithm = $opts->{algorithm} || $c->config->{authentication}{http}{algorithm}) { 
+        $nonce->algorithm( $algorithm );
+    }
 
     return $nonce;
 }
@@ -316,14 +324,14 @@ sub _join_auth_header_parts {
     return "$type " . join(", ", @parts );
 }
 
-sub _get_digest_authorization_nonce {
+sub get_digest_authorization_nonce {
     my ( $c, $key ) = @_;
 
     $c->_check_cache;
     $c->cache->get( $key );
 }
 
-sub _store_digest_authorization_nonce {
+sub store_digest_authorization_nonce {
     my ( $c, $key, $nonce ) = @_;
 
     $c->_check_cache;
@@ -362,13 +370,13 @@ __END__
 =head1 NAME
 
 Catalyst::Plugin::Authentication::Credential::HTTP - HTTP Basic and Digest authentication
-for Catlayst.
+for Catalyst.
 
 =head1 SYNOPSIS
 
     use Catalyst qw/
         Authentication
-        Authentication::Store::Moose
+        Authentication::Store::Minimal
         Authentication::Credential::HTTP
     /;
 
@@ -403,27 +411,112 @@ This moduule lets you use HTTP authentication with
 L<Catalyst::Plugin::Authentication>. Both basic and digest authentication
 are currently supported.
 
+When authentication is required, this module sets a status of 401, and
+the body of the response to 'Authorization required.'. To override
+this and set your own content, check for the C<< $c->res->status ==
+401 >> in your C<end> action, and change the body accordingly.
+
+=head2 TERMS
+
+=over 4
+
+=item Nonce
+
+A nonce is a one-time value sent with each digest authentication
+request header. The value must always be unique, so per default the
+last value of the nonce is kept using L<Catalyst::Plugin::Cache>. To
+change this behaviour, override the
+C<store_digest_authorization_nonce> and
+C<get_digest_authorization_nonce> methods as shown below.
+
+=back
+
 =head1 METHODS
 
 =over 4
 
-=item authorization_required
+=item authorization_required %opts
 
 Tries to C<authenticate_http>, and if that fails calls
 C<authorization_required_response> and detaches the current action call stack.
 
-=item authenticate_http
+This method just passes the options through untouched.
+
+=item authenticate_http %opts
 
 Looks inside C<< $c->request->headers >> and processes the digest and basic
 (badly named) authorization header.
 
-=item authorization_required_response
+This will only try the methods set in the configuration. First digest, then basic.
+
+See the next two methods for what %opts can contain.
+
+=item authenticate_basic %opts
+
+=item authenticate_digest %opts
+
+Try to authenticate one of the methods without checking if the method is
+allowed in the configuration.
+
+%opts can contain C<store> (either an object or a name), C<user> (to disregard
+%the username from the header altogether, overriding it with a username or user
+%object).
+
+=item authorization_required_response %opts
 
 Sets C<< $c->response >> to the correct status code, and adds the correct
 header to demand authentication data from the user agent.
 
+Typically used by C<authorization_required>, but may be invoked manually.
+
+%opts can contain C<realm>, C<domain> and C<algorithm>, which are used to build
+%the digest header.
+
+=item store_digest_authorization_nonce $key, $nonce
+
+=item get_digest_authorization_nonce $key
+
+Set or get the C<$nonce> object used by the digest auth mode.
+
+You may override these methods. By default they will call C<get> and C<set> on
+C<< $c->cache >>.
+
 =back
 
+=head1 CONFIGURATION
+
+All configuration is stored in C<< YourApp->config->{authentication}{http} >>.
+
+This should be a hash, and it can contain the following entries:
+
+=over 4
+
+=item store
+
+Either a name or an object -- the default store to use for HTTP authentication.
+
+=item type
+
+Can be either C<any> (the default), C<basic> or C<digest>.
+
+This controls C<authorization_required_response> and C<authenticate_http>, but
+not the "manual" methods.
+
+=item authorization_required_message
+
+Set this to a string to override the default body content "Authorization required."
+
+=back
+
+=head1 RESTRICTIONS
+
+When using digest authentication, this module will only work together
+with authentication stores whose User objects have a C<password>
+method that returns the plain-text password. It will not work together
+with L<Catalyst::Authentication::Store::Htpasswd>, or
+L<Catalyst::Plugin::Authentication::Store::DBIC> stores whose
+C<password> methods return a hashed or salted version of the password.
+
 =head1 AUTHORS
 
 Yuval Kogman, C<nothingmuch@woobling.org>
@@ -432,6 +525,10 @@ Jess Robinson
 
 Sascha Kiefer C<esskar@cpan.org>
 
+=head1 SEE ALSO
+
+RFC 2617 (or its successors), L<Catalyst::Plugin::Cache>, L<Catalyst::Plugin::Authentication>
+
 =head1 COPYRIGHT & LICENSE
 
         Copyright (c) 2005-2006 the aforementioned authors. All rights