Apache-AppSamurai

 view release on metacpan or  search on metacpan

Build.PL  view on Meta::CPAN

EOF

my $builder = $class->new(
			  module_name         => 'Apache::AppSamurai',
			  license             => 'perl',
			  dist_author         => 'Paul M. Hirsch <paul@voltagenoir.org>',
			  dist_version_from   => 'lib/Apache/AppSamurai.pm',
			  dist_abstract       => 'Web application/reverse proxy authenticating front end',
			  requires => {
			      'Test::More'   => 0,
			      'CGI::Cookie'  => 0,
			      'URI'          => 0,
			      'Time::HiRes'  => 0,
			      'MIME::Base64' => 0,
			      'Carp'         => 0,
			      'Apache::Session' => 0,
			      'Digest::SHA'  => 0,
			      'Storable'     => 0,
			      'Crypt::CBC' => 2.17,
			      @extrareq
			  },

META.yml  view on Meta::CPAN

abstract: Web application/reverse proxy authenticating front end
license: perl
resources:
  MailingList: mailto:appsamurai-misc@lists.sourceforge.net
  bugtracker: http://rt.cpan.org/Public/Dist/Display.html?Name=Apache-AppSamurai
  homepage: http://appsamurai.sourceforge.net
  license: http://dev.perl.org/licenses/
requires:
  Apache::Request: 0
  Apache::Session: 0
  CGI::Cookie: 0
  Carp: 0
  Crypt::CBC: 2.17
  Digest::SHA: 0
  MIME::Base64: 0
  Storable: 0
  Test::More: 0
  Time::HiRes: 0
  URI: 0
  mod_perl: 1.07
recommends:

examples/conf/appsamurai-owa.conf  view on Meta::CPAN

# We with use the auth_name "Owa" for this sample.  If you prefer
# "TheMagnificentRonnieWilson" instead, just replace "Owa" with
# that in each PerlSetVar line.
#
# Set to 1 for debugging (only for troubleshooting or non-production testing,
# as this produces a TON of noise, and leaks some semi-sensitive info,
# into the Apache error logs)  (Default: 0)
PerlSetVar OwaDebug 0

# Name of authentication cookie
PerlSetVar OwaCookieName ChocholateChipOfDoom

# Path to set on authentication cookie  (Default: /)
PerlSetVar OwaPath /

# Point to the form login page/script
PerlSetVar OwaLoginScript /AppSamurai/login.pl

# Must satisfy all authentication checks (Default: All)
PerlSetVar OwaSatisfy All

examples/conf/appsamurai-owa.conf  view on Meta::CPAN

# (Default: undef)
PerlSetVar OwaAuthBasicRequireRealm "__OWA_SERVER_LOGIN_REALM__"

# Continue to send the same Authorization: header to the backend server
# after login.  (Only use this when the AuthBasic check is run against
# the backend server you are protecting)  (Default: 1)
PerlSetVar OwaAuthBasicKeepAuth 1

# Collect cookes from AuthBasic check and send back to the user's browser
# on login  (Default: 1)
PerlSetVar OwaAuthBasicPassBackCookies 1

## AppSamurai::AuthRadius options
#
# Set the IP and port to send Radius requests to
PerlSetVar OwaAuthRadiusConnect "__RADIUS_SERVER_IP__:__RADIUS_PORT__"

# Set the RADIUS key to use
PerlSetVar OwaAuthRadiusSecret "__RADIUS_PASSWORD__"


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

	Apache::Constants->import(qw(OK DECLINED REDIRECT HTTP_FORBIDDEN
				     HTTP_INTERNAL_SERVER_ERROR
				     HTTP_MOVED_TEMPORARILY HTTP_UNAUTHORIZED
				     M_GET));
	require Apache::Request;
	$MP = 1;
    }
}

# Non-mod_perl includes
use CGI::Cookie;
use URI;
use Time::HiRes qw(usleep);

use Apache::AppSamurai::Util qw(CreateSessionAuthKey CheckSidFormat
				HashPass HashAny ComputeSessionId
				CheckUrlFormat CheckHostName
				CheckHostIP XHalf);

# Apache::AppSamurai::Session is a replacement for Apache::Session::Flex
# It provides normal Apache::Session::Flex features, plus optional extras
# like alternate session key generators/sizes and record level encryption
use Apache::AppSamurai::Session;

# Apache::AppSamurai::Tracker is a special instance of Session meant to
# be shared between all processes serving an auth_name
use Apache::AppSamurai::Tracker;

### START Apache::AuthSession based methods

# The following lower case methods are directly based on Apache::AuthCookie, or
# are required AuthCookie methods (like authen_cred() and authen_ses_key())

# Note - ($$) syntax, used in mod_perl 1 to induce calling the handler as
# an object, has been eliminated in mod_perl 2.  Each handler method called
# directly from Apache must be wrapped to support mod_perl 1 and mod_perl 2
# calls.  (Just explaining the mess before you have to read it.)

# Identify the username for the session and set for the request
sub recognize_user_mp1 ($$) { &recognize_user_real }
sub recognize_user_mp2 : method { &recognize_user_real }
*recognize_user = ($MP eq 1) ? \&recognize_user_mp1 : \&recognize_user_mp2;

sub recognize_user_real {
    my ($self, $r) = @_;
    my ($auth_type, $auth_name) = ($r->auth_type, $r->auth_name);
    
    return DECLINED unless $auth_type and $auth_name;    

    my $cookie_name = $self->cookie_name($r);
    
    my ($cookie) = $r->headers_in->{'Cookie'} =~ /$cookie_name=([^;]+)/;
    if (!$cookie && $r->dir_config("${auth_name}Keysource")) {
	# Try to get key text using alternate method then compute the key.
	# FetchKeysource returns '' if no custom source is configured, in
	# which case the cookie should have been previously set, so non-zero
	# output is required.
	$cookie = $self->FetchKeysource($r);
	if ($cookie) {
	    $cookie = CreateSessionAuthKey($cookie);
	}
    }    

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

    
    return OK;
}

# Get the cookie name for this protected area
sub cookie_name {
    my ($self, $r) = @_;

    my $auth_type = $r->auth_type;
    my $auth_name = $r->auth_name;
    my $cookie_name = $r->dir_config("${auth_name}CookieName") ||
	"${auth_type}_${auth_name}";
    return $cookie_name;
}

# Set request cache options (no-cache unless specifically told to cache)
sub handle_cache {
    my ($self, $r) = @_;
    
    my $auth_name = $r->auth_name;
    return unless $auth_name;

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

# Backdate cookie to attempt to clear from web browser cookie store
sub remove_cookie {
    my ($self, $r) = @_;
    
    my $cookie_name = $self->cookie_name($r);
    my $str = $self->cookie_string( request => $r,
				    key     => $cookie_name,
				    value   => '',
				    expires => 'Mon, 21-May-1971 00:00:00 GMT' );
    
    $r->err_headers_out->add("Set-Cookie" => "$str");
    
    $self->Log($r, ('debug', "remove_cookie(): removed_cookie \"$cookie_name\""));
}

# Convert current POST request to GET
# Note - The use of this is questionable now that Apache::Request is being
# used.  May go away in the future.
sub _convert_to_get {
    my ($self, $r) = @_;
    return unless $r->method eq 'POST';

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


    # Get the hard set destination, or setup to just reload
    if ($r->dir_config("${auth_name}LoginDestination")) {
	$destination = $r->dir_config("${auth_name}LoginDestination");
    } elsif ($ar->param("destination")) {
	$destination = $ar->param("destination");
    } else {
	# Someday something slick could hold the URL, then cut through
	# to it.  Someday.  Today we die.
        $self->Log($r, ('warn', "No key 'destination' found in form data"));
        $r->subprocess_env('AuthCookieReason', 'no_cookie');
        return $auth_type->login_form($r);
    }  

    # Check form nonce and signature
    if (defined($ar->param("nonce")) and defined($ar->param("sig"))) {
	unless (($nonce = CheckSidFormat($ar->param("nonce"))) and
		($sig = CheckSidFormat($ar->param("sig")))) {
	    
	    $self->Log($r, ('warn', "Missing/invalid form nonce or sig"));
	    $r->subprocess_env('AuthCookieReason', 'no_cookie');
	    $r->err_headers_out->{'Location'} = $self->URLErrorCode($destination, 'bad_credentials');
	    $r->status(REDIRECT);
	    return REDIRECT;
	}
	$serverkey = $self->GetServerKey($r) or die("FATAL: Could not fetch valid server key\n");

	# Now check!
	unless ($sig eq ComputeSessionId($nonce, $serverkey)) {
	    # Failed!
	    $self->Log($r, ('warn', "Bad signature on posted form (Possible scripted attack)"));
	    $r->subprocess_env('AuthCookieReason', 'no_cookie');
	    $r->err_headers_out->{'Location'} = $self->URLErrorCode($destination, 'bad_credentials');
	    $r->status(REDIRECT);
	    return REDIRECT;
	}
    } else {
	# Failed!
	$self->Log($r, ('warn', "Missing NONCE and/or SIG in posted form (Possible scripted attack)"));
	$r->subprocess_env('AuthCookieReason', 'no_cookie');
	$r->err_headers_out->{'Location'} = $self->URLErrorCode($destination, 'bad_credentials');
	$r->status(REDIRECT);
	return REDIRECT;
    }

    # Get the credentials from the data posted by the client
    while ($tc = $ar->param("credential_" . scalar(@credentials))) {
	push(@credentials, $tc);
	
	($tc) ? ($tc =~ s/^(.).*$/$1/s) : ($tc = ''); # Only pull first char

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

            $self->CheckTracker($r, 'IPFailures', $r->dir_config("${auth_name}IPFailures"), $r->connection->get_remote_host);
        }
    }

    # Append special error message code and try to redirect to the entry
    # point. (Avoids having the LOGIN URL show up in the browser window)
    $r->err_headers_out->{'Location'} = $self->URLErrorCode($destination, 'bad_credentials');
    $r->status(REDIRECT);
    return REDIRECT;
    # Handle this ol' style - XXX remove?
    #$r->subprocess_env('AuthCookieReason', 'bad_credentials');
    #$r->uri($destination);
    #return $auth_type->login_form($r);
}

# Special version of login that handles Basic Auth login instead of form
# Can be called by authenticate() if there is no valid session but a
# Authorization: Basic header is detected.  Can also be called directly,
# just like login() for targeted triggering
sub loginBasic_mp1 ($$) { &loginBasic_real }
sub loginBasic_mp2 : method { &loginBasic_real }

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

sub logout_mp1 ($$) { &logout_real }
sub logout_mp2 : method { &logout_real }
*logout = ($MP eq 1) ? \&logout_mp1 : \&logout_mp2;
sub logout_real {
    my $self = shift;
    my $r = shift;
    my $auth_name = $r->auth_name;
    my $redirect = shift || "";
    my ($sid, %sess, $sessconfig, $username, $alterlist);
    
    # Get the Cookie header. If there is a session key for this realm, strip
    # off everything but the value of the cookie.
    my $cookie_name = $self->cookie_name($r);
    my ($key) = $r->headers_in->{'Cookie'} =~ /$cookie_name=([^;]+)/;
    
    # Try custom keysource if no cookie is present and Keysource is configured
    if (!$key && $auth_name && $r->dir_config("${auth_name}Keysource")) {
	# Pull in key text
	$key = $self->FetchKeysource($r);
	# Non-empty, so use to generate the real session auth key
	if ($key) {
	    $key = CreateSessionAuthKey($key);
	}
    }

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

	    if ($@) {
		$self->Log($r, ('debug', "logout(): Unable to open session \"$sid\": $@"));
	    } else {
		$username = $sess{'username'};
		
                # Load alterlist
		$alterlist = $self->AlterlistLoad(\%sess);
		# Re-apply passback cookies to which were cleared and backdated
		# after session creation.  (This clears the passback cookies)
		if (defined($alterlist->{cookie})) {
		    $self->AlterlistPassBackCookie($alterlist, $r);
		}

		$self->DestroySession($r, \%sess);
        	untie(%sess);
		$self->Log($r, ('notice', "LOGOUT: username=\"$username\", session=\"$sid\", reason=logout"));
	    }
	} else {
	    $self->Log($r, ('error', 'logout(): Invalid Session ID Format'));
	}
    } else {

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

    }

    # AuthType is $auth_type which we handle, Check the authentication realm
    my $auth_name = $r->auth_name;
    $self->Log($r, ('debug', "authenticate(): auth_name " . $auth_name));
    unless ($auth_name) {
	$r->log_reason("AuthName not set, AuthType=$self", $r->uri);
	return HTTP_INTERNAL_SERVER_ERROR;
    }
    
    # Get the Cookie header. If there is a session key for this realm, strip
    # off everything but the value of the cookie.
    my $cookie_name = $self->cookie_name($r);
    my ($ses_key_cookie) = ($r->headers_in->{"Cookie"} || "") =~ /$cookie_name=([^;]+)/;
    
    $foundcookie = 0;
    if ($ses_key_cookie) {
	# If cookie found and not "", set $foundcookie to note auth key source
	$foundcookie = 1;
    } elsif ($r->dir_config("${auth_name}Keysource")) {
	# Try custom keysource if no cookie is present and Keysource is configured
	# Pull in key text
	$ses_key_cookie = $self->FetchKeysource($r);

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

				       key     => $cookie_name,
				       value   => $ses_key,
				       %$cookie_args );
    
    # add P3P header if user has configured it.
    my $auth_name = $r->auth_name;
    if (my $p3p = $r->dir_config("${auth_name}P3P")) {
	$r->err_headers_out->{'P3P'} = $p3p;
    }
    
    $r->err_headers_out->add("Set-Cookie" => $cookie);
}

# Convert cookie store to header ready string
sub cookie_string {
    my $self = shift;
    
    # if passed 3 args, we have old-style call.
    if (scalar(@_) == 3) {
	carp "cookie_string(): deprecated old style call to ".__PACKAGE__."::cookie_string()";
	my ($r, $key, $value) = @_;

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

    return $string;
}

# Retrieve session cookie value 
sub key {
    my ($self, $r) = @_;

    my $auth_name = $r->auth_name;
    my $key = "";

    my $allcook = ($r->headers_in->{"Cookie"} || "");
    my $cookie_name = $self->cookie_name($r);
    ($key) = $allcook =~ /(?:^|\s)$cookie_name=([^;]*)/;

    # Try custom keysource if no cookie is present and Keysource is configured
    if (!$key && $auth_name && $r->dir_config("${auth_name}Keysource")) {
	# Pull in key text
	$key = $self->FetchKeysource($r);
	# Non-empty, so use to generate the real session auth key
	if ($key) {
	    $key = CreateSessionAuthKey($key);

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

	#    server sent a 401 to the client.  We need to kill the session to
	#    get things in line again.
	$reason = "basic_auth_change";
    }

    if ($reason) {
	# Oh no!  They gave us a reason... It's ON!  (well, off)
	
	# Remove passback and session cookies first
	if (defined($alterlist->{cookie})) {
	    $self->AlterlistPassBackCookie($alterlist, $r);
	}
	
	$self->remove_cookie($r);
	$self->handle_cache($r);
	
	# Wake up.  Time to die.
        $self->DestroySession($r, \%sess);
	untie(%sess);
	$self->Log($r, ('notice', "LOGOUT: username=\"$username\", session=\"$sid\", reason=$reason"));
	
	# If serving basic auth, return undef instead of triggering login form
	if ($r->auth_type =~ /^basic$/i) {
	    return undef;
	} else {
	    # Use Apache::AuthCookie based custom_errors feature, which will
	    # call back into our custom_errors() method. (expired_cookie
	    # applies as an acceptable error for all of these cases.)
	    return('login', 'expired_cookie');
	}
    }

    # Apply header and cookie alterations to request headed for backend server
    $self->AlterlistApply($alterlist, $r);
    $self->Log($r, ('debug', "authen_ses_key(): Loaded and applied alterlist groups " . join(",", keys %{$alterlist})));

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

	# Codes in all caps with an underscore are assumed to be Apache
        # response codes
	($message) && ($r->custom_response($code, $message));
	return $code;
    } else {
	# What was that?  Die out.
	die "custom_errors(): Invalid code passed to custom_errors: \"$code\"";
    }
}
		       
## END Apache::AuthCookie based methods

# Everything past this point is not an overridden/modified Apache::AuthCookie
# function.

# Taking a request, try to get the <AuthName>AuthMethods list for the resource
sub GetAuthMethods {
    my ($self, $r) = @_;
    my ($authname, $authmethlist);
    my @authmethods = ();

    # Get the auth name
    ($authname = $r->auth_name()) || (die("GetAuthMethods(): No auth name set for this request!\n"));

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

    }

    # Set hard expiration time if Expire is set
    if ($sessconfig->{Expire}) {
	$sess{'etime'} = $sess{'ctime'} + $sessconfig->{Expire};
	$sess{'Expire'} = $sessconfig->{Expire};
    }

    # Apply passback cookies to response, and pull in updated alterlist
    if (defined($alterlist->{cookie})) {
	$alterlist = $self->AlterlistPassBackCookie($alterlist, $r);
    }

    # If present, save Authorization header to detect future changes,
    # then prepend an alterlist rule to delete the header to prevent
    # pass though to the backend server.  (If needed, a separate
    # alterlist rule to add an Authorization header should be set
    # by a auth module.)
    if ($r->headers_in->{"Authorization"}) {
	$sess{'Authorization'} = $r->headers_in->{"Authorization"};
	# Stick it in front in case we have an existing add

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

#  NAME - Header name (or regex match for delete)
#  VALUE - New value of header for add or replace, else optional regex filter
#          for delete  (Prefix pattern with ! for negation)
#
# cookie
# ------
# @{$self->{alterlist}->{cookie}} - One or more cookie transforms, with the
#  syntax:
#                  ACTION:NAME:VALUE
#  ACTION - add, replace, delete, or passback
#  NAME - Cookie name (or regex match for delete)
#  VALUE - New value of cookie, or regex filter for delete action (Prefix
#          pattern with ! for negation)
# 
# Note - delete rules with optional value match pattern will delete only values
#        of a multi-value cookie that match the value pattern
#
# The special "passback" action passes cookies back to the web browser on
# login, This allows us to gather cookies from backend servers on login, but
# have the web browser maintain them.
#

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

    (defined($alterlist)) || (return 0);

    if (defined($alterlist->{header})) {
	# Run through headers (saving off alter count)
	$self->AlterlistApplyHeader($alterlist, $r);
	$self->Log($r, ('debug', "AlterlistApply(): Applied alterlist for header"));
    }

    if (defined($alterlist->{cookie})) {
	# Run through cookies (saving off alter count)
	$self->AlterlistApplyCookie($alterlist, $r);
	$self->Log($r, ('debug', "AlterlistApply(): Applied alterlist for cookie"));
    }

    return $alterlist;
}

# Apply alterlist rules to request headers.
sub AlterlistApplyHeader {
    my ($self, $alterlist, $r) = @_;
    (defined($alterlist->{header})) || (return 0);

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

		}
	    }
	}
    }
    
    return $alterlist;
}


# Apply alterlist rules to request cookies.
# Note - Does not handle "passback" cookie.  Use AlterlistPassBackCookie() to
# retrieve and clear passback cookies)
sub AlterlistApplyCookie {
    my ($self, $alterlist, $r) = @_;

    (defined($alterlist->{cookie})) || (return 0);
    my ($t, %c, $cl, $act, $key, $val, $tk, $tv, @ta, @td);
    my $alterred = 0;

    # Grab any cookies any put into a hash of CGI::Cookies, or just make an
    # empty cookie hash for now.
    %c = CGI::Cookie->fetch($r);
    (%c) || (%c = ());
    # Build \n deliminated lookup string to fast match against
    $cl = "\n" . join("\n", keys(%c)) . "\n";
    
    foreach $t (@{$alterlist->{cookie}}) {
	# Note - : or = allowed between NAME and VALUE to make life easier
	($t =~ /^(add|replace|rep|delete|del|passback|set):([\w\d\-]+)(?:\:|\=)(.*?)$/i) || (($self->Log($r, ('debug', "AlterlistApplyCookie(): Skipping illegal cookie transform \"$t\""))) && (next));
	$act = $1;
	$key = $2;
	$val = $3;
	
	if ($act =~ /^passback|set$/) {
	    # passback not handled in this method
	    next;
	} elsif ($act =~ /^add$/) {
	    # Blindly add the cookie
	    @ta = split('&', $val);
	    # Add a new CGI::Cookie to the hash
	    $c{$key} = new CGI::Cookie(-name => $key, -value => \@ta);

	    # Log obscured value
	    $self->Log($r, ('debug', "AlterlistApplyCookie(): COOKIE ADD: $key=" . XHalf($val)));
	    $alterred++;
	} else {
	    # Replace and delete allow for regex cookie name matches
	    while ($cl =~ /($key)/igm) {
		# Update 
		$tk = $1;
		if ($act =~ /^replace|rep$/) {
		    # Blindly delete then add the cookie back with new value
		    # Save old value for log
		    $tv = join('&', $c{$tk}->value);;
		    delete($c{$tk});
		    @ta = split('&', $val);
		    $c{$tk} = new CGI::Cookie(-name => $tk, -value => \@ta);
		    
		    # Log obscured values
		    $self->Log($r, ('debug', "AlterlistApplyCookie(): COOKIE REPLACE: $tk: " . XHalf($tv) . " -> " . XHalf($val)));
		    $alterred++;
		} elsif ($act =~ /^delete|del$/) {
		    # Check for extra content match
		    if ($val) {
			@ta = ();
			@td = (); 
			# Cycle through multi-values
			foreach $tv ($c{$tk}->value) {
			    # Handle negation
			    if ($val =~ s/^\!//) {

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

			    push(@td, $tv);
			    $alterred++;
			}
			# Kill!
			if (scalar @ta) {
			    # Some values left not deleted, so set those back
			    $c{$tk}->value(\@ta);
			    $tv = join('&', @td);

			    # Log obscured value
			    $self->Log($r, ('debug', "AlterlistApplyCookie(): COOKIE DELETE PARTIAL: $tk=" . XHalf($tv)));
			} else {
			    # Nothing left inside. KILL!
			    delete($c{$tk});
			    $tv = join('&', @td);

			    # Obscure values for logging
			    $tv =~ s/([^X])[\w\d]/${1}X/gs;
			    $self->Log($r, ('debug', "AlterlistApplyCookie(): COOKIE DELETE FULL: $tk=$tv"));
			}
		    } else {
			# Kill Em All
			$tv = $c{$key}->value;
			delete($c{$key});
			    
                        # Obscure values for logging
			$tv =~ s/([^X])[\w\d]/${1}X/gs;
			$self->Log($r, ('debug', "AlterlistApplyCookie(): COOKIE DELETE FULL: $key=$tv"));

			$alterred++;
		    }
		}
	    }
	}
    }
    
    # Unset, then add cookies to header if changes were made
    if ($alterred) {
	$r->headers_in->unset('Cookie');
	$t = '';
	foreach $tk (keys %c) {
	    # Cookie to list in string form.
	    $t .= $c{$tk}->name . "=" . join('&', $c{$tk}->value) . "; ";
	}
	# Kill trailing '; '
	$t =~ s/\; $//s;
	# Ship it
	$r->headers_in->add('Cookie' => $t);
    }

    return $alterlist;
}

# Add a Set-cookie: header to r for all alterlist "passback" cookies and return
# a modified alterlist with the passback cookie values cleared and expired.
#
# Unlike normal alterlist rules, passback cookies are sent BACK to the client.
# The only time this can occur is upon login/redirect. The purpose of passback
# cookies is to set the same cookies in the browser as they would have set
# if they were connecting directly to the backend server(s).
#
# The return should be used to update the alterlist.  When
# AlterlistPassBackCookie is applied again, it will UNSET the passback cookies.
# This should be done on logout.
sub AlterlistPassBackCookie() {
    my ($self, $alterlist, $r) = @_;

    (defined($alterlist->{cookie})) || (return 0);
    my ($t, $key, $val, $opt, $tdomain, $tpath, $texpire);
    my @ct = ();
    my %c = ();

    foreach $t (@{$alterlist->{cookie}}) {
	# Note - : or = allowed between NAME and VALUE to make life easier
	($t =~ /^(passback|set):([\w\d\-]+)(?:\:|\=)([^;]*)(;.*)?$/i) || ((push(@ct, $t)) && (next));
	$key = $2;
	$val = $3;
	$opt = $4;
	$tdomain = $tpath = $texpire = '';

	# Unlike AlterlistApplyCookie which just needs to parse name and
	# value, the PassBack cookies are Set-Cookie items which may
	# have options.  Also, only process the last cookie value if
	# a multi-value cookie is passed

	# Add a new CGI::Cookie to the hash
	$c{$key} = new CGI::Cookie(-name => $key, 
				   -value => $val,
				   );
	# Set further options (only Expires and Path currently passed through)
	foreach $t (split(';', $opt)) {
	    if ($t =~ /^\s*expires=([\w\d \:\;\-,]+)\s*$/) {
		$c{$key}->expires($1);
	    } elsif ($t =~ /^\s*path=(\/.*?)\s*$/) {
                $c{$key}->path($1);
            }
	}

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

	# cookie.  I don't see a need.)
	my $auth_name = $r->auth_name;
	    
	if ($r->dir_config("${auth_name}Domain")) {
	    $c{$key}->domain($r->dir_config("${auth_name}Domain"));
	}
	if (!$r->dir_config("${auth_name}Secure") || ($r->dir_config("${auth_name}Secure") == 1)) {
	    $c{$key}->secure(1);
	}
	
	$r->err_headers_out->add('Set-Cookie' => $c{$key});

	# Clean up and log
	$t = $c{$key};
	$t =~ /($key\s*\=\s*)(.*?)(;|$)/;
	$self->Log($r, ('debug', "AlterlistPassBackCookie(): COOKIE PASSBACK: " . $1 . XHalf($2) . $3));

	# Save an empty/expired cookie so next call to AlterlistPassBackCookie
	# with this alterlist will unset the cookie
	$c{$key}->value('');
	$c{$key}->expires('Thu, 1-Jan-1970 00:00:00 GMT');
	push(@ct, "passback:" . $c{$key});
    }

    # Save updated cookie array
    @{$alterlist->{cookie}} = @ct; 

    return $alterlist;
}


# Append an error code to the list of query args in a given URL.  (Used to
# pass friendly error messages to users in external redirects.  (Note that
# AuthCookie used subprocess_env() to pass that info, but since that will only
# work in the same main request, it won't pass into an external redirect.)
sub URLErrorCode {
    my $self = shift;
    my $uri = (shift) || (return undef);
    my $ecode = (shift) || ('');
    
    ($uri = new URI($uri)) || (return undef);
    
    # Error codes must contain only letters, numbers, and/or _ chars.
    # Your login.pl script should read them in CAREFULLY and make sure

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

to local or proxied applications with limited authentication options.

Unauthenticated users are presented with either a login form, or a basic
authentication popup (depending on configuration.)  User supplied credentials
are checked against one or more authentication systems before the user's
session is created and a session authentication cookie is passed back to the
browser.  Only authenticated and authorized requests are proxied through
to the backend server.

Apache::AppSamurai is based on, and includes some code from,
L<Apache::AuthCookie|Apache::AuthCookie>.
Upon that core is added a full authentication and session handling framework.
(No coding required.)  Features include:

=over 4

=item *

B<Modular authentication> - Uses authentication sub-modules for the easy
addition custom authentication methods

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


=head2 GENERAL CONFIGURATION

=head3 I<Debug> C<0|1>

(Default: 0)
Set to 1 to send debugging output to the Apache logs.  (Note - you must have
a log configured to catch errors, including debug level errors, to see the
output.)

=head3 I<CookieName> C<NAME>

(Default:AUTHTYPE_AUTHNAME)
The name of the session cookie to send to the browser.

=head3 I<LoginScript> C<PATH>

(Default: undef)
The URL path (location) of the proxy's login page for form based login.
(Sample script provided with the Apache::AppSamurai distribution.)

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

 PerlTaintCheck On
 PerlModule Apache::Registry
 #*FOR MODPERL2 USE:
 # PerlSwitches -wT
 # PerlModule ModPerl::Registry

 # Load the main module and define configuration options for the 
 # "Example" auth_name
 PerlModule Apache::AppSamurai
 PerlSetVar ExampleDebug 0
 PerlSetVar ExampleCookieName MmmmCookies
 PerlSetVar ExamplePath /
 PerlSetVar ExampleLoginScript /login.pl

 # Defaults to All by may also be Any
 #PerlSetVar ExampleSatisty All
 
 # Optional session cookie domain (Avoid unless absolutely needed.)
 #PerlSetVar ExampleDomain ".thing.er"

 # Require secure sessions (default: 1)

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

 ## Apache::AppSamurai::AuthBasic options.##
 # (Note - See L<Apache::AppSamurai::AuthBasic> for more info)
 
 # Set the URL to send Basic auth checks to
 PerlSetVar ExampleAuthBasicLoginUrl "https://ex.amp.le/thing/login"
 
 # Always send Basic authentication header to backend server
 PerlSetVar ExampleAuthBasicKeepAuth 1
 
 # Capture cookies from AuthBasic login and set in client browser
 PerlSetVar ExampleAuthBasicPassBackCookies 1
 
 # Abort the check unless the "realm" returned by the server matches
 PerlSetVar ExampleAuthBasicRequireRealm "blah.bleh.blech"
 
 # Pass the named header directly through to the AuthBasic server 
 PerlSetVar ExampleAuthBasicUserAgent "header:User-Agent"

 
 ## Session storage options ##
 # (Note - See L<Apache::AppSamurai::Session> and L<Apache::Session> for

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


Additional authentication modules, tracking features, and other options
can be added to Apache::AppSamurai.  In the case of authentication modules,
all that is required is creating a new module that inherits from
L<Apache::AppSamurai::AuthBase|Apache::AppSamurai::AuthBase>.

Other features may be more difficult to add.  (Apache::AppSamurai could
use some refactoring.)

Interface and utility methods are not documented at this time.  Please
consult the code, and also the L<Apache::AuthCookie|Apache::AuthCookie>
documentation.

=head1 FILES

=over 4

=item F<APPSAMURAI_CONTENT/>

Directory that holds Apache::AppSamurai login/logout pages and related
content.  This must be served by Apache and reachable.  (This is

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

L<http://www.voltagenoir.org/AppSamurai/>

=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Apache-AppSamurai>

=back

=head1 ACKNOWLEDGEMENTS

AppSamurai.pm (the main Apache::AppSamurai module), contains some code
from Apache::AuthCookie, which was developed by Ken Williams and others.
The included Apache::AuthCookie code is under the same licenses as Perl
and under the following copyright:

Copyright (c) 2000 Ken Williams. All rights reserved.

=head1 COPYRIGHT & LICENSE

Copyright 2008 Paul M. Hirsch, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

lib/Apache/AppSamurai/AuthBasic.pm  view on Meta::CPAN

    my $conft = $self->{conf};

    # Initial configuration.  Put defaults here before the @_ args are
    # pulled in.
    $self->{conf} = { %{$conft},
		      LoginUrl => 'https://127.0.0.1', # URL to authenticate
		                                       # aginst
		      KeepAuth => 0, # Keep Authorization: Basic XXX header 
		                     # and continue to send to the proxied
                                     # servers. BE CAREFUL!
		      PassBackCookies => 0, # Pass all Set-Cookies back to
                                            # client browser
		      AllowRedirect => 0, # Follow redirects (Keep off and get
		                          # the URL right!)
		      UserAgent => '', # The User-Agent: header to report
		      RequireRealm => '', # If set, this realm must match that
		                          # returned by the backend server
		      SuccessCode => 200, # Auth considered a failure unless
                                          # this code is returned after login
		      Timeout => 10, # Timeout for connecting to auth server
		      PassMin => 3,

lib/Apache/AppSamurai/AuthBasic.pm  view on Meta::CPAN

    }
    
    # Set credentials in request
    $self->{request}->authorization_basic($user, $pass);

    # Connect with beans and check return
    $response = $self->{client}->request($self->{request});

    if ($response->code() == $self->{conf}{SuccessCode}) {
	# YAY!  Now collect and store cookies and headers as directed
	if (($self->{conf}{PassBackCookies}) && (@tmp = $response->header('set-cookie'))) {
	    foreach (@tmp) {
		# Trim whitespace
		s/^\s*(.*?)\s*$/$1/;
		# Add to cookie alterlist for instance (will be sent back to web browser)
		push(@{$self->{alterlist}->{cookie}}, "passback:$1");
	    }
	}

	if ($self->{conf}{KeepAuth}) {
	    push(@{$self->{alterlist}->{header}}, "add:Authorization:Basic " . encode_base64($user . ":" . $pass, ''));

lib/Apache/AppSamurai/AuthBasic.pm  view on Meta::CPAN

 # this string. (Optional)
 PerlSetVar fredAuthBasicRequireRealm "Fred World Login"

 # Continue to send the same Authorization: header to the backend server
 # after login.  (Only use this when the AuthBasic check is run against
 # the backend server you are protecting)
 PerlSetVar fredAuthBasicKeepAuth 1

 # Collect cookies from AuthBasic check and send back to the user's browser
 # on login  (This is the default behaviour)
 PerlSetVar fredAuthBasicPassBackCookies 1


=head1 DESCRIPTION

This L<Apache::AppSamurai|Apache::AppSamurai> authentication module checks a
username and password against a backend webserver, (referred to as the "auth
server" below), using HTTP basic authentication (as defined in
L<RFC 2617|http://www.faqs.org/rfcs/rfc2617.html>).  In general, the
auth server is the same as the server Apache::AppSamurai is protecting,
though it does not have to be.

lib/Apache/AppSamurai/AuthBasic.pm  view on Meta::CPAN

enable this feature unless you are certain all the servers and applications
being protected by this Apache::AppSapurai instance should be receiving users'
usernames and passwords!>

B<Session Storage Warning:>
By default Apache::AppSamurai uses AES (Rijndael) to encrypt session data before storing it to disk, greatly reducing the risk of keeping
the basic auth header,  If you use this feature, please leave the
L<Apache::AppSamurai::Session::Serialize::CryptBase64|Apache::AppSamurai::Session::Serialize::CryptBase64> module configured as the session serialization
module.

=head2 I<PassBackCookies> C<0|1>

(Default: 0)
If 1, collects set cookies from the auth server and, upon successful login,
set them in the client web browser.

Even when using basic auth, many apps set cookies for various reasons.
This feature is most useful then the auth server and the protected
backend webserver are the same.  It may also be useful in the case of
using a ticket issuer of some sort as the auth server.  

lib/Apache/AppSamurai/AuthBasic.pm  view on Meta::CPAN


A second request is sent, this time with the username and password (credential)
included.

=item *

The return code is checked against C<SuccessCode>

=item *

If C<PassBackCookies> is 1, the cookies set by the auth server are saved
in the alterlist cookie hash with "passback" rules.

=item *

If C<KeepAuth> is 1, the authorization header (containing the username and
password) are saved in the alterlist header hash with an "add" rule.

=item *

If all checks have succeeded, 1 is returned.

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

@EXPORT_OK = qw(expires CreateSessionAuthKey CheckSidFormat
		HashPass HashAny ComputeSessionId CheckUrlFormat CheckHostName
		CheckHostIP XHalf);

# $IDLEN defines the byte length for all IDs (Session IDs, Keys, etc).
# This should be the byte length of the main digest function used.
# (Provided in case something other than SHA256 is used.)
$IDLEN = 32;

# -- expires() shamelessly taken from CGI::Util
## -- And this expires shamelessly taken from Apache::AuthCookie::Util ;)
sub expires {
    my($time,$format) = @_;
    $format ||= 'http';

    my(@MON) = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
    my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;

    # pass through preformatted dates for the sake of expire_calc()
    $time = _expire_calc($time);
    return $time unless $time =~ /^\d+$/;

t/conf/extra.conf.in  view on Meta::CPAN

  PerlSwitches -I@ServerRoot@/lib
</IfDefine>

PerlRequire @ServerRoot@/startup.pl
PerlModule Apache::AppSamurai

PerlSetVar WhatEverPath /
PerlSetVar WhatEverLoginScript /docs/login.pl
PerlSetVar WhatEverDebug 3

PerlSetVar WhatEverCookieName CakeNotCookie
PerlSetVar WhatEverSecure 1

# Map Basic auth password into 3 credentials, separated by
# semicolons and reverse mapped for fun!
PerlSetVar WhatEverBasicAuthMap "3,2,1=(.+);([^;]+);([^;]+)"

# Please, don't use these auth modules... they kinda suck.
# (Note that the names hint to the static password for each.
# You know that ain't cool.)
PerlSetVar WhatEverAuthMethods "AuthTestFLUFFY,AuthTestPASSWORD,AuthTest123456"



( run in 1.671 second using v1.01-cache-2.11-cpan-e9199f4ba4c )