Apache-AuthCookie

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


3.22  2014-05-07

3.21  2014-05-07
   - Bad release - deleted

3.20  2013-12-09
   - login_form: return OK for mobile IE 10, which also ignores content for
     FORBIDDEN response.
   - test .pl registry scripts: do not try to load mod_perl.pm
   - escape html tags in destination.
   - fix abstract in FAQ pod.

3.19  2012-12-28
   - split out CGI data handling into ::AuthCookie::Params modules
   - use Apache::Request/Apache2::Request from libapreq if available. Otherwise,
     fall back to CGI.pm for handling CGI data.
   - improve "removed cookie" debug log message
   - add dependencies: autobox, Class::Load
   - allow username to be '0'
   - login_form: return OK for SymbianOS, which ignores content for FORBIDDEN responses.

Changes  view on Meta::CPAN

Version: 3.09
   - POD doc fixes.
   - MP2: remove _check_request_req() - this was only necessary when
     running under both MP1 and MP2.  Package name change eliminates the
     need for this.
   - test suite converted to Test::More style test suites.
   - descriptive test descriptions added
   - make login() stash credentials in $r->pnotes("${AuthName}Creds") so
     that the login form can access the user-supplied credentials if the
     login fails.
   - bug fix: use of Apache2::URI::unescape_url() does not handle
     '+' to ' ' conversion.  This caused problems for credentials
     that contain spaces.
   - MP2: remove mod_perl features from "use mod_perl2" line. This is 
     no longer supported by mod_perl2.
   - MP2: _get_form_data() - switch to CGI.pm to handle form data (fixes
     several form data handling bugs)
   - In a subrequest, copy $r->prev->user to $r->user (or r->connection->user 
     for MP1).
   - remove Apache2::AuthCookie::Util - no longer necessary
   - multi-valued form fields are now handled properly in POST -> GET conversion

Changes  view on Meta::CPAN

Version: 3.06
  ** BUG FIX: AuthNameSatisfy (Any|All) directives were broken. AuthCookie
     was using AuthCookieSatisfy rather than ${auth_name}Satisfy.  If you
     used this feature and had an "AuthCookieSatisfy" directive in your
     config file, you MUST change this to ${auth_name}Satisfy.
     E.g.: "WhateverSatisfy All"
   - created better test cases for AuthNameSatisfy directives.
   - when redirecting, set Location with headers_out() not err_headers_out().
     apache prefers Location in headers_out, even if the status code is not
     200.
   - MP2: Apache::unescape_url() -> Apache::URI::unescape_url()
   - check for mod_perl 1.9913 or later for Apache::URI (Frederick Moyer)
   - Remove set status in login.pl which caused malformed custom error
     document (Frederick Moyer)
   - Add support for ${auth_name}CookieName to change the name of the cookie
     used for each auth name.  Default remains ${auth_name}_${auth_type} if
     not set.
   - make some debug log_error() calls conditional on $debug

Version: 3.05
   - Fix POD documentation bug (thanks Steve van der Burg)

lib/Apache/AuthCookie.pm  view on Meta::CPAN

$Apache::AuthCookie::VERSION = '3.32';
# ABSTRACT: Perl Authentication and Authorization via cookies

use strict;

use Carp;
use mod_perl qw(1.07 StackedHandlers MethodHandlers Authen Authz);
use Apache::Constants qw(:common M_GET FORBIDDEN OK REDIRECT);
use Apache::AuthCookie::Params;
use Apache::AuthCookie::Util qw(is_blank is_local_destination);
use Apache::Util qw(escape_uri);
use Apache::URI;
use Encode ();


sub recognize_user ($$) {
    my ($self, $r) = @_;

    # only check if user is not already set
    return DECLINED unless is_blank($r->connection->user);

lib/Apache/AuthCookie.pm  view on Meta::CPAN

    my $args = $self->params($r);

    my @pairs = ();

    for my $name ($args->param) {
        # we dont want to copy login data, only extra data
        next if $name eq 'destination'
             or $name =~ /^credential_\d+$/;

        for my $v ($args->param($name)) {
            push @pairs, escape_uri($name) . '=' . escape_uri($v);
        }
    }

    $r->args(join '&', @pairs) if scalar(@pairs) > 0;

    $r->method('GET');
    $r->method_number(M_GET);
    $r->headers_in->unset('Content-Length');
}

lib/Apache/AuthCookie.pm  view on Meta::CPAN


    $r->header_out(Location => $self->untaint_destination($destination));

    return REDIRECT;
}


sub untaint_destination {
    my ($self, $dest) = @_;

    return Apache::AuthCookie::Util::escape_destination($dest);
}


sub logout($$) {
    my ($self, $r) = @_;
    my $debug = $r->dir_config("AuthCookieDebug") || 0;

    $self->remove_cookie;

    $self->handle_cache;

lib/Apache/AuthCookie.pm  view on Meta::CPAN

the C<authen_cred()> method, passing it C<$r> and all the submitted
data with names like C<"credential_#">, where # is a number.  These will
be passed in a simple array, so the prototype is
C<$self-E<gt>authen_cred($r, @credentials)>.  After calling
C<authen_cred()>, we set the user's cookie and redirect to the
URL contained in the C<"destination"> submitted form field.

=head2 untaint_destination($uri)

This method returns a modified version of the destination parameter
before embedding it into the response header. Per default it escapes
CR, LF and TAB characters of the uri to avoid certain types of
security attacks. You can override it to more limit the allowed
destinations, e.g., only allow relative uris, only special hosts or
only limited set of characters.

=head2 logout($r)

This is simply a convenience method that unsets the session key for
you.  You can call it in your logout scripts.  Usually this looks like
C<$r-E<gt>auth_type-E<gt>logout($r);>.

lib/Apache/AuthCookie.pm  view on Meta::CPAN

=item *

your L</authen_cred()> and L</authen_ses_key()> function is expected to return
a decoded username, either by passing it through L<Encode/decode()>, or, by
turning on the UTF8 flag if appropriate.

=item *

Due to the way HTTP works, cookies cannot contain non-ASCII characters.
Because of this, if you are including the username in your generated session
key, you will need to escape any non-ascii characters in the session key
returned by L</authen_cred()>.

=item *

Similarly, you must reverse this escaping process in L</authen_ses_key()> and
return a L<Encode/decode()> decoded username.  If your L</authen_cred()>
function already only generates ASCII-only session keys then you do not need to
worry about any of this.

=item *

lib/Apache/AuthCookie/Util.pm  view on Meta::CPAN

    } elsif ($time=~/^\d+/) {
        return $time;
    } elsif ($time=~/^([+-]?(?:\d+|\d*\.\d*))([mhdMy]?)/) {
        $offset = ($mult{$2} || 1)*$1;
    } else {
        return $time;
    }
    return (time+$offset);
}

# escape embedded CR, LF, TAB's to prevent possible XSS attacks.
# see http://www.securiteam.com/securityreviews/5WP0E2KFGK.html
sub escape_destination {
    my $text = shift;

    $text =~ s/([\r\n\t\>\<"])/sprintf("%%%02X", ord $1)/ge;

    return $text;
}

# return true if the given user agent understands a HTTP_FORBIDDEN response
# with custom content. Some agents (e.g.: Symbian OS browser), use their own
# HTML and completely ignore the HTTP content.

lib/Apache2/AuthCookie.pm  view on Meta::CPAN

=item *

your L<authen_cred()> and L<authen_ses_key()> function is expected to return
a decoded username, either by passing it through L<Encode/decode()>, or, by
turning on the UTF8 flag if appropriate.

=item *

Due to the way HTTP works, cookies cannot contain non-ASCII characters.
Because of this, if you are including the username in your generated session
key, you will need to escape any non-ascii characters in the session key
returned by L<authen_cred()>.

=item *

Similarly, you must reverse this escaping process in L<authen_ses_key()> and
return a L<Encode/decode()> decoded username.  If your L<authen_cred()>
function already only generates ASCII-only session keys then you do not need to
worry about any of this.

=item *

lib/Apache2/AuthCookie/Base.pm  view on Meta::CPAN


sub encoding {
    my ($self, $r) = @_;

    my $auth_name = $r->auth_name;

    return $r->dir_config("${auth_name}Encoding");
}


sub escape_uri {
    my ($r, $string) = @_;
    return Apache2::Util::escape_path($string, $r->pool);
}


sub get_cookie_path {
    my ($self, $r) = @_;

    my $auth_name = $r->auth_name;

    return $r->dir_config("${auth_name}Path");
}

lib/Apache2/AuthCookie/Base.pm  view on Meta::CPAN


    if (my $p3p = $r->dir_config("${auth_name}P3P")) {
        $r->err_headers_out->set(P3P => $p3p);
    }
}


sub untaint_destination {
    my ($self, $dest) = @_;

    return Apache::AuthCookie::Util::escape_destination($dest);
}

# convert current request to GET
sub _convert_to_get {
    my ($self, $r) = @_;

    return unless $r->method eq 'POST';

    my $debug = $r->dir_config("AuthCookieDebug") || 0;

lib/Apache2/AuthCookie/Base.pm  view on Meta::CPAN

    my $args = $self->params($r);

    my @pairs = ();

    for my $name ($args->param) {
        # we dont want to copy login data, only extra data
        next if $name eq 'destination'
             or $name =~ /^credential_\d+$/;

        for my $v ($args->param($name)) {
            push @pairs, escape_uri($r, $name) . '=' . escape_uri($r, $v);
        }
    }

    $r->args(join '&', @pairs) if scalar(@pairs) > 0;

    $r->method('GET');
    $r->method_number(M_GET);
    $r->headers_in->unset('Content-Length');
}

lib/Apache2/AuthCookie/Base.pm  view on Meta::CPAN


=head2 decoded_user($r): string

If you have set ${auth_name}Encoding, then this will return the decoded value of
C<< $r-E<gt>user >>.

=head2 encoding($r): string

Return the ${auth_name}Encoding setting that is in effect for this request.

=head2 escape_uri($r, $value): string

Escape the given string so it is suitable to be used in a URL.

=head2 get_cookie_path($r): string

Returns the value of C<PerlSetVar ${auth_name}Path>.

=head2 handle_cache($r): void

If C<${auth_name}Cache> is defined, this sets up the response so that the

lib/Apache2/AuthCookie/Base.pm  view on Meta::CPAN

you can override this method.

=head2 send_p3p($r): void

Set a P3P response header if C<${auth_name}P3P> is configured.  The value of
the header is whatever is in the C<${auth_name}P3P> setting.

=head2 untaint_destination($destination): string

This method returns a modified version of the destination parameter before
embedding it into the response header. Per default it escapes CR, LF and TAB
characters of the uri to avoid certain types of security attacks. You can
override it to more limit the allowed destinations, e.g., only allow relative
uris, only special hosts or only limited set of characters.

=for Pod::Coverage  OK
 DECLINED
 SERVER_ERROR
 M_GET
 HTTP_FORBIDDEN
 HTTP_MOVED_TEMPORARILY

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN

=item *

your L<authen_cred()> and L<authen_ses_key()> function is expected to return
a decoded username, either by passing it through L<Encode/decode()>, or, by
turning on the UTF8 flag if appropriate.

=item *

Due to the way HTTP works, cookies cannot contain non-ASCII characters.
Because of this, if you are including the username in your generated session
key, you will need to escape any non-ascii characters in the session key
returned by L<authen_cred()>.

=item *

Similarly, you must reverse this escaping process in L<authen_ses_key()> and
return a L<Encode/decode()> decoded username.  If your L<authen_cred()>
function already only generates ASCII-only session keys then you do not need to
worry about any of this.

=item *

t/lib/Sample/Apache/AuthCookieHandler.pm  view on Meta::CPAN

package Sample::Apache::AuthCookieHandler;

use strict;
use utf8;
use base 'Apache::AuthCookie';
use Apache;
use Apache::Constants qw(:common);
use Apache::AuthCookie;
use Apache::Util;
use URI::Escape qw(uri_escape_utf8 uri_unescape);
use Encode;

sub authen_cred ($$\@) {
    my $self = shift;
    my $r = shift;
    my @creds = @_;

    return if $creds[0] eq 'fail'; # simulate bad_credentials

    # This would really authenticate the credentials 
    # and return the session key.
    # Here I'm just using setting the session
    # key to the escaped credentials and delaying authentication.
    return join ':', map { uri_escape_utf8($_) } @creds;
}

sub authen_ses_key ($$$) {
    my ($self, $r, $ses_key) = @_;

    # NOTE: uri_escape_utf8() was used to encode this so we have to decode
    # using UTF-8.  We don't rely on $self->encoding($r) here because if an
    # encoding other than UTF-8 is configured in t/conf/extra.conf.in, then the
    # wrong encoding gets used here.
    my($user, $password) =
        map { decode('UTF-8', uri_unescape($_)) }
        split /:/, $ses_key, 2;

    if ($user eq 'programmer' && $password eq 'Hero') {
        return $user;
    }
    elsif ($user eq 'some-user') {
        return $user;
    }
    elsif ($user eq '0') {
        return $user;

t/lib/Sample/Apache2/AuthCookieHandler.pm  view on Meta::CPAN

package Sample::Apache2::AuthCookieHandler;

use strict;
use utf8;
use Class::Load 'load_class';
use Apache2::Const qw(:common HTTP_FORBIDDEN);
use Apache2::AuthCookie;
use Apache2::RequestRec;
use Apache2::RequestIO;
use Apache2::Util;
use URI::Escape qw(uri_escape_utf8 uri_unescape);
use Encode qw(decode);
use vars qw(@ISA);

use Apache::Test;
use Apache::TestUtil;

if (have_min_apache_version('2.4.0')) {
    load_class('Apache2_4::AuthCookie');
    @ISA = qw(Apache2_4::AuthCookie);
}

t/lib/Sample/Apache2/AuthCookieHandler.pm  view on Meta::CPAN

    my $r = shift;
    my @creds = @_;

    $r->server->log_error("authen_cred entry");

    return if $creds[0] eq 'fail'; # simulate bad_credentials

    # This would really authenticate the credentials 
    # and return the session key.
    # Here I'm just using setting the session
    # key to the escaped credentials and delaying authentication.
    return join ':', map { uri_escape_utf8($_) } @creds;
}

sub authen_ses_key ($$$) {
    my ($self, $r, $cookie) = @_;

    my ($user, $password) =
        map { decode('UTF-8', uri_unescape($_)) }
        split /:/, $cookie, 2;

    $r->server->log_error("authen_ses_key entry");

    $r->server->log_error("user=$user pass=$password cookie=$cookie");

    if ($user eq 'programmer' && $password eq 'Hero') {
        return $user;
    }
    elsif ($user eq 'some-user') {

t/real.t  view on Meta::CPAN


    my $r = POST('/LOGIN', [
        destination  => '/docs/protected/get_me.html',
        credential_0 => 'fail',
        credential_1 => 'Hero'
    ]);

    like($r->content, qr/creds: fail Hero/s, 'WhatEverCreds pnotes works');
};

# regression - Apache2::URI::unescape_url() does not handle '+' to ' '
# conversion.
subtest 'unescape URL with spaces' => sub {
    plan tests => 1;

    my $r = POST('/LOGIN', [
        destination  => '/docs/protected/get_me.html',
        credential_0 => 'fail',
        credential_1 => 'one two'
    ]);

    like($r->content, qr/creds: fail one two/,
         'read form data handles "+" conversion');

t/real.t  view on Meta::CPAN

    my $r = POST('/LOGIN', [
        destination  => '/docs/protected/get_me.html',
        credential_0 => 'fail',
        credential_1 => 'one+two'
    ]);

    like($r->content, qr/creds: fail one\+two/,
         'read form data handles "+" conversion with encoded +');
};

# XSS attack prevention.  make sure embedded \r, \n, \t is escaped in the destination.
subtest 'XSS: no newlines in destination' => sub {
    plan tests => 4;

    my $r = POST('/LOGIN', [
        destination  => "/docs/protected/get_me.html\r\nX-Test-Bar: True\r\nX-Test-Foo: True\r\n",
        credential_0 => 'programmer',
        credential_1 => 'Hero'
    ]);

    ok(!defined $r->header('X-Test-Foo'), 'anti XSS injection');
    ok(!defined $r->header('X-Test-Bar'), 'anti XSS injection');

    # try with escaped CRLF also.
    $r = POST('/LOGIN', [
        destination  => "/docs/protected/get_me.html%0d%0aX-Test-Foo: True%0d%0aX-Test-Bar: True\r\n",
        credential_0 => 'programmer',
        credential_1 => 'Hero'
    ]);

    ok(!defined $r->header('X-Test-Foo'), 'anti XSS injection with escaped CRLF');
    ok(!defined $r->header('X-Test-Bar'), 'anti XSS injection with escaped CRLF');
};

# embedded html tags in destination
subtest 'XSS: no embedded HTML in destination' => sub {
    plan tests => 1;

    my $r = POST('/LOGIN', [
        destination  => '/"><form method="post">Embedded Form</form>'
    ]);



( run in 0.351 second using v1.01-cache-2.11-cpan-c21f80fb71c )