Apache-AppSamurai

 view release on metacpan or  search on metacpan

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

    my (%sess, $sid, $sessconfig, $kt);
    
    # Extract the session config
    ($sessconfig = $self->GetSessionConfig($r)) || (die "CreateSession(): Unable to get session configuration while creating new session");
    
    # Create a session auth key to send back to send back as the cookie
    # value, and to use the HMAC-SHA and optional session file encryptor.
    # FetchKeysource returns "" by default, resulting in a fully randomized
    # key.
    $kt = $self->FetchKeysource($r);
    if (defined($kt)) {
	$sessconfig->{key} = CreateSessionAuthKey($kt);
    } else {
	$self->Log($r, ('warn', "CreateSession(): Failed to generate session authentication key: Session creation denied"));
	return undef;
    }
    
    # Check for valid looking key
    unless (CheckSidFormat($sessconfig->{key})) {
	$self->Log($r, ('warn', "CreateSession(): Bad session authentication key returned!  Session creation denied"));
	return undef;
    }

    # Run against the unique session tracker if configured.  (*Don't make
    # the same session twice)
    if ($sessconfig->{Unique}) {
	unless ($self->CheckTracker($r, 'SessionUnique', $sessconfig->{key})) {
	    $self->Log($r, ('warn', "CreateSession(): SessionUnique detected duplicate session authentication key!  Session creation denied"));
	    return undef;
	}
    }

    # Wrapped this in an eval, since Apache:Session dies on failures
    eval { tie(%sess, 'Apache::AppSamurai::Session', undef, $sessconfig); };
    if ($@) {
	$self->Log($r, ('error', "CreateSession(): Unable to create new session: $@"));
	return undef;
    }
    $sid = $sess{_session_id};

    # Make sure we received a good session ID.
    (CheckSidFormat($sid)) || (($self->Log($r, ('error', 'CreateSession(): Invalid Session ID Format on new Session'))) && (return undef));
    $self->Log($r, ('notice', "LOGIN: username=\"$username\", session=\"$sid\""));
    
    # Store some basics
    $sess{'username'} = $username;
    $sess{'ctime'} = time();
    
    # Track last access time if Timeout is set
    if ($sessconfig->{Timeout}) {
	$sess{'atime'} = $sess{'ctime'};
	$sess{'Timeout'} = $sessconfig->{Timeout};
    }

    # 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
	# header from an auth module
	unshift(@{$alterlist->{header}}, 'delete:Authorization:');
    }

    # Save current alterlist to session
    $self->AlterlistSave($alterlist, \%sess);
    
    # Release session
    untie(%sess);
    
    # Return the session auth key
    return $sessconfig->{key};
}

# Destroy a session, rendering it forever useless.  Takes a request hash ref
# and a session hash ref as args.  (Session must be tied when DestroySession
# is called.)
sub DestroySession {
    my ($self, $r, $sess) = @_;

    # Call the delete method for the the tied hash.  Wrapped in eval goodness
    # since Apache::Session will die on error.
    eval { tied(%{$sess})->delete; };
    if ($@) {
        $self->Log($r, ('warn', "DestroySession(): Unable to destroy session: $@"));
        return undef;
    }

    return 1;
}


## TRACKER - A system to store persistant and shared data for various
## uses. This is yet more code that could be refactored and busted into
## external modules to allow for adding arbitrary stateful checks of
## all sorts of things, (like the authentication handlers).
## For now, only a small set of tracker types are provided,
## and all are defined in this module.

# Get Tracker config (Tracker being a special case Session type targetted
# at IPC tasks)  The tracker should never hold sensitive data since encryption
# support is not provided!  Make sure to hash sensitive info if you need to
# track old session authentication keys or other items.
sub GetTrackerConfig {
    my ($self, $r) = @_;
    my $auth_name = ($r->auth_name()) || (die("GetTrackerConfig(): No auth name defined!\n"));
    my $dirconfig = $r->dir_config;

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

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);
            }
	}

	# Set other options to match session cookie values (could be made a
	# configurable, and allow for maintaining the original options from the
	# 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
    # they follow this format.
    ($ecode =~ /^([\w\d_]+)$/) || (return undef);
    
    # Add the error code and return the URI in string form
    $uri->query_form($uri->query_form, 'ASERRCODE' => $ecode);
    return $uri->as_string;
}

# Log to configured log.  Always takes the request as the 1st arg.  Can
# take either a loglevel and a message as args 2 and 3, or an array
# of loglevel and message arrays as the 2nd arg.
sub Log {
    my $self = shift;
    my $r = shift;
    my $la = [];
    my $debug = $self->_debug($r);

    # Check if being called with a level and message, or with a log array
    if (ref($_[0]) eq "ARRAY") {
	$la = $_[0];
	(defined(@{$la}) && (scalar @{$la})) || (return 0);
    } else {
	(defined($_[0]) && defined($_[1])) || (return 0);
	# Set to a single child array of arrays
	$la = [[$_[0], $_[1]]];
    }

    # Collect a few tidbits (package name, client IP and URI?args)
    my $auth_name = ($r->auth_name || "");
    $auth_name .= ': ';
    my $info = ' <client=';
    if ($MP eq 1) {
        $info .= ($r->get_remote_host || "");
    } else {
        $info .= ($r->connection->get_remote_host || "");
    }
    $info .= ', uri="';
    $info .= ($r->uri() || "");
    (defined($r->args())) && ($info .= '?' . $r->args());
    $info .= '">';



( run in 1.055 second using v1.01-cache-2.11-cpan-df04353d9ac )