Apache-AuthTicket

 view release on metacpan or  search on metacpan

lib/Apache/AuthTicket/Base.pm  view on Meta::CPAN

                $CONFIG{$auth_name}{$name},
                $DEFAULTS{$name});
    }

    return $self->{config}{$name}
}

sub login_screen ($$) {
    my ($class, $r) = @_;

    my $self = $class->new($r);

    my $action = $self->get_config('TicketLoginHandler');

    my $destination = $r->prev->uri;
    my $args = $r->prev->args;
    if ($args) {
        $destination .= "?$args";
    }

    $class->make_login_screen($r, $action, $destination);

    return $class->apache_const('OK');
}

sub make_login_screen {
    my ($self, $r, $action, $destination) = @_;

    if (DEBUGGING) {
        # log what we think is wrong.
        my $reason = $r->prev->subprocess_env("AuthCookieReason");
        $r->log_error("REASON FOR AUTH NEEDED: $reason");
        $reason = $r->prev->subprocess_env("AuthTicketReason");
        $r->log_error("AUTHTICKET REASON: $reason");
    }

    $r->content_type('text/html');

    $r->send_http_header if ModPerl::VersionUtil->is_mp1;

    $r->print(
        q{<!DOCTYPE HTML PUBLIC  "-//W3C//DTD HTML 3.2//EN">},
        q{<HTML>},
        q{<HEAD>},
        q{<TITLE>Log in</TITLE>},
        q{</HEAD>},
        q{<BODY bgcolor="#ffffff">},
        q{<H1>Please Log In</H1>}
    );

    $r->print(
        qq{<form method="post" action="$action">},
        qq{<input type="hidden" name="destination" value="$destination">},
        q{<table>},
        q{<tr>},
        q{<td>Name</td>},
        q{<td><input type="text" name="credential_0"></td>},
        q{</tr>},
        q{<tr>},
        q{<td>Password</td>},
        q{<td><input type="password" name="credential_1"></td>},
        q{</tr>},
        q{</table>},
        q{<input type="submit" value="Log In">},
        q{<p>},
        q{</form>},
        q{<EM>Note: </EM>},
        q{Set your browser to accept cookies in order for login to succeed.},
        q{You will be asked to log in again after some period of time.},
        q{</body></html>}
    );

    return $self->apache_const('OK');
}

sub logout ($$) {
    my ($class, $r) = @_;

    my $self = $class->new($r);

    $self->delete_ticket($r);
    $self->next::method($r); # AuthCookie logout

    $r->headers_out->add(Location => $self->get_config('TicketLogoutURI'));

    return $class->apache_const('REDIRECT');
}

##################### END STATIC METHODS ###########################3
sub new {
    my ($class, $r) = @_;

    return $class->SUPER::new({request => $r});
}

sub dbh {
    my $self = shift;

    unless (defined $self->_dbh) {
        $self->_dbh($self->dbi_connect);
    }

    $self->_dbh;
}

sub dbi_connect {
    my $self = shift;

    my $r         = $self->request;
    my $auth_name = $r->auth_name;

    my ($db, $user, $pass) = map {
        $self->get_config($_)
    } qw/TicketDB TicketDBUser TicketDBPassword/;

    my $dbh = DBI->connect_cached($db, $user, $pass)
        or die "DBI Connect failure: ", DBI->errstr, "\n";

    return $dbh;
}

sub check_credentials {
    my ($self, $user, $password) = @_;

    my ($table, $user_field, $pass_field) = $self->user_table;

    my ($stmt, @bind) =
        $self->sql->select($table, $pass_field, {$user_field => $user});

    my ($db_pass) = eval {
        $self->dbh->selectrow_array($stmt, undef, @bind);
    };
    if ($@) {
        $self->dbh->rollback;
        return 0;
    }

    unless (defined $db_pass) {
        # user not in database
        return 0;
    }

    my $style = $self->get_config('TicketPasswordStyle');

    if ($self->compare_password($style, $password, $db_pass)) {
        return 1;
    }
    else {
        return 0;
    }
}

sub fetch_secret {
    my ($self, $version) = @_;

    my $dbh = $self->dbh;

    my ($secret_table, $secret_field, $secret_version_field) = $self->secret_table;

    # generate SQL
    my @fields = ($secret_field, $secret_version_field);
    my %where = ( $secret_version_field => $version ) if defined $version;
    my $order = " $secret_version_field DESC LIMIT 1 ";
    my ($stmt, @bind) = $self->sql->select($secret_table, \@fields, \%where, $order);

    return eval {
        $dbh->selectrow_array($stmt, undef, @bind);
    };
    if ($@) {
        $dbh->rollback;
        die $@;
    }
}

sub secret_version {
    my $self = shift;

    unless (defined $self->_secret_version) {
        $self->_secret_version( ($self->fetch_secret)[1] );
    }

    return $self->_secret_version;
}

sub make_ticket {
    my ($self, $user_name) = @_;

    my $ticket = $self->new_ticket_for($user_name);

    my ($secret) = $self->fetch_secret($$ticket{version});

    my $data = $self->serialize_ticket($ticket);

    my @fields = ($secret, $data);

    # only add ip if TicketCheckIP is on.
    if ($self->get_config('TicketCheckIP')) {
        push @fields, $self->_client_ip;
    }

    if ($self->get_config('TicketCheckBrowser')) {
        push @fields, $self->user_agent;
    }

    my $hash = $self->hash_for(@fields);

lib/Apache/AuthTicket/Base.pm  view on Meta::CPAN

        die $@;
    }
}

sub delete_hash {
    my ($self, $hash) = @_;

    my ($table, $tick_field) = $self->ticket_table;

    my ($query, @bind) = $self->sql->delete($table, { $tick_field => $hash });

    my $dbh = $self->dbh;

    eval {
        my $sth = $dbh->do($query, undef, @bind);
        $dbh->commit unless $dbh->{AutoCommit} || 0;
    };
    if ($@) {
        $dbh->rollback;
        die $@;
    }
}

sub is_hash_valid {
    my ($self, $hash) = @_;

    my ($table, $tick_field, $ts_field) = $self->ticket_table;

    my ($query, @bind) = $self->sql->select($table, [$tick_field, $ts_field], 
        { $tick_field => $hash });

    my $dbh = $self->dbh;

    my ($db_hash, $ts) = (undef, undef);
    eval {
        ($db_hash, $ts) = $dbh->selectrow_array($query, undef, @bind);
        $self->{DBTicketTimeStamp} = $ts;   # cache for later use.
    };
    if ($@) {
        $dbh->rollback;
        die $@;
    }

    return (defined $db_hash and $db_hash eq $hash) ? 1 : 0;
}

sub hash_for {
    my $self = shift;

    return Digest::MD5::md5_hex(@_);
}

sub user_agent {
    my $self = shift;

    return $ENV{HTTP_USER_AGENT}
        || $self->request->headers_in->get('User-Agent')
        || '';
}

sub compare_password {
    my ($self, $style, $check, $expected) = @_;

    if ($style eq 'crypt') {
        return crypt($check, $expected) eq $expected;
    }
    elsif ($style eq 'cleartext') {
        return $check eq $expected;
    }
    elsif ($style eq 'md5') {
        return Digest::MD5::md5_hex($check) eq $expected;
    }
    else {
        die "unrecognized password style '$style'";
    }

    return 0;
}

sub str_config_value {
    my $self = shift;

    for my $value (@_) {
        next unless defined $value;

        my $test = lc $value;

        # convert booleans to 1/0
        if ($test =~ /^(?:1|on|yes|true)$/) {
            return 1;
        }
        elsif ($test =~ /^(?:0|off|no|false)$/) {
            return 0;
        }
        else {
            # return value unchanged.
            return $value;
        }
    }

    return;
}

sub ticket_table {
    my $self = shift;

    return split ':', $self->get_config('TicketTable');
}

sub user_table {
    my $self = shift;

    return split ':', $self->get_config('TicketUserTable');
}

sub secret_table {
    my $self = shift;

    return split ':', $self->get_config('TicketSecretTable');
}

sub push_handler { die "unimplemented" }

sub set_user { die "unimplemented" }

sub apache_const { die "unimplemented" }

1;

__END__

=pod

=head1 NAME

lib/Apache/AuthTicket/Base.pm  view on Meta::CPAN

invalid_hash

Ticket hash is not found in the database

=item *

expired_ticket

Ticket has expired

=item *

missing_secret

Secret that signed this ticket was not found

=item *

idle_timeout

Ticket idle timeout exceeded

=item *

tampered_hash

Ticket has been tampered with.  The checksum does not match the checksum in the
ticket

=back

=head2 sql

Get the C<SQL::Abstract> object.

=head2 get_config

 my $value = $self->get_config($name)

Get a configuration value, or its default value if the setting is not
configured.

=head2 make_login_screen

 my $result = $self->make_login_screen($r, $action, $destination)

Print out the login screen html, and return an Apache status code.

=head2 dbh

Get the database handle

=head2 dbi_connect

 my $dbh = $self->dbi_connect

Returns a new connection to the database

=head2 check_credentials

 my $ok = $self->check_credentials($username, $password)

Return C<true> if the credentials are valid

=head2 fetch_secret

 my ($value, $version) = $self->fetch_secret;
 my ($value) = $self->fetch_secret($version)

Return the secret and version of the secret.  if the C<version> argument is
present, return that specific version of the secret instead of the most recent
one.

=head2 secret_version

Returns the version of the current (most-recent) secret

=head2 make_ticket

 my $string = $self->make_ticket($username)

Creates a ticket string for the given username

=head2 serialize_ticket

 my $data = $self->serialize_ticket($hashref)

Encode the hashref in a format suitable for sending in a HTTP cookie

=head2 unserialize_ticket

 my $hashref = $self->unserialize_ticket($data)

Decode cookie data into hashref.  This is the opposite of serialize_ticket()

=head2 new_ticket_for

 my $hashref = $self->new_ticket_for($username)

Creates new ticket hashref for the given username.  You could overload this to
append extra fields to the ticket.

=head2 delete_ticket

 $self->delete_ticket($r)

Invalidates the ticket by expiring the cookie and deletes the hash from the database

=head2 save_hash

 $self->save_hash($hash)

save the hash value/checksum in the database

=head2 delete_hash

 $self->delete_hash($hash)

Remove the given hash from the database.

=head2 is_hash_valid

 my $ok = $self->is_hash_valid($hash)

Return C<true> if the given hash is in the local database

=head2 hash_for

 my $hash = $self->hash_for(@values)

Compute a hash for the given values

=head2 user_agent

 my $agent = $self->user_agent

Get the request client's user agent string

=head2 compare_password

 my $ok = $self->compare_password($style, $entered, $actual)

Check a password and return C<true> if C<entered> matches C<actual>.  C<style> specifys what type of password is in C<actual>, and is one of the following:

=over 4

=item *

crypt

standard UNIX C<crypt()> value

=item *

cleartext

plain text password

=item *

md5

MD5 hash of password

=back

=head2 str_config_value

 my $val = $self->str_config_value($name)

Get a configuration value.  This converts things like yes,on,true to C<1>, and
no,off,false to C<0>.  Multiple C<name> values may be given and the first
defined value will be returned.  If no config value is defined matching any of
the given C<name>'s, then C<undef> is returned.

=head2 ticket_table

 my ($name, $hash_col, $timestamp_col) = $self->ticket_table

Unpacks the config value C<TicketTable> into its components.

=head2 user_table

 my ($name, $hash_col, $timestamp_col) = $self->ticket_table

Unpacks the config value C<TicketUserTable> into its components.

=head2 secret_table

 my ($name, $hash_col, $timestamp_col) = $self->ticket_table

Unpacks the config value C<TicketSecretTable> into its components.

=head2 push_handler

 $class->push_handler($name => sub { ... });

B<Subclass Must Implement This>.  Push the given subroutine as a mod_perl
handler

=head2 set_user

 $self->set_user($username)

B<Subclass Must Implement This>.  Set the username for this request.

=head2 apache_const

 my $const = $self->apache_const($name)

B<Subclass Must Implement This>.  Return the given apache constant.

=head1 SOURCE

The development version is on github at L<http://github.com/mschout/apache-authticket>
and may be cloned from L<git://github.com/mschout/apache-authticket.git>

=head1 BUGS

Please report any bugs or feature requests to bug-apache-authticket@rt.cpan.org or through the web interface at:
 http://rt.cpan.org/Public/Dist/Display.html?Name=Apache-AuthTicket

=head1 AUTHOR



( run in 1.660 second using v1.01-cache-2.11-cpan-39bf76dae61 )