# ==================================================================
# Gossamer Links - enhanced directory management system
#
#   Website  : http://gossamer-threads.com/
#   Support  : http://gossamer-threads.com/scripts/support/
#   CVS Info : 087,071,086,086,085      
#   Revision : $Id: Authenticate.pm,v 1.34 2008/10/06 17:41:18 brewt Exp $
#
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================

package Links::Authenticate;
# ==================================================================
use strict;
use Links qw/:objects/;
use GT::Session::SQL;

# This package lets you integrate Gossamer Links into another authentication
# system. You can do this by replacing the functions with your own
# code. Note: to return error results, simply set error => message in
# the passed in hash. 

sub auth {
# -----------------------------------------------------------------------------
# Runs the request auth function through the plugin system.
#
    shift if UNIVERSAL::isa($_[0], 'Links::Authenticate');
    my ($auth, $args) = @_;
    my $code = exists $Links::Authenticate::{"auth_$auth"}
        ? $Links::Authenticate::{"auth_$auth"}
        : die "Invalid Authenticate method 'auth_$auth' called";
    $PLG->dispatch("auth_$auth", $code, $args);
}

sub auth_init {
# -----------------------------------------------------------------------------
# This function is guaranteed to be called before any other authentication
# function, but may be called multiple times during one request. 
#
    return 1;
}

sub auth_add_user {
# -----------------------------------------------------------------------------
# This function is called whenever a user is added to the database. It takes a
# hash reference with Username and Password as input. If there is an error, set
# $args->{error} to the message.
#
    my $args = shift;
    return { Username => $args->{Username}, Password => $args->{Password} };
}

sub auth_del_user {
# -----------------------------------------------------------------------------
# This function is called whenever a user is trying to be deleted.  It returns
# the username on success, or undef on failure.
#
    my $args = shift;
    return $args->{Username};
}

sub auth_valid_user {
# -----------------------------------------------------------------------------
# This function returns true if the user/pass combo is valid, 0/undef
# otherwise.
#
    my $args = shift;
    return int $DB->table('Users')->count({ Username => $args->{Username}, Password => $args->{Password} });
}

sub auth_valid_format {
# -----------------------------------------------------------------------------
# This function returns 1 if the user format is valid, undef otherwise.
#
    my $args = shift;
    my $user = $args->{Username};
    return if length $user > 50 or $user !~ /^[\w\s\-\@\.]+$/;
    return 1;
}

sub auth_change_pass {
# -----------------------------------------------------------------------------
# This function takes the username, old pass and new pass and returns 1 if
# successful, false otherwise.
#
    my $args = shift;
    return 1;
}

sub auth_get_pass {
# -----------------------------------------------------------------------------
# This function returns the password (if available) of a given user, undef
# otherwise.
#
    my $args = shift;
    my $user = $args->{Username};
    my $pass = $DB->table('Users')->select(Password => { Username => $user })->fetchrow;
    return $pass;
}

sub auth_get_user {
# -----------------------------------------------------------------------------
# This function returns user information for a given user, auto creating if it
# doesn't exist.
#
    my $args = shift;
    my $user = $args->{Username};
    my $pass = $args->{Password};
    my $db = $DB->table('Users');
    my $user_r = $db->get($user);
    if (!$user_r and $args->{auto_create}) {
        $user_r->{Username} = $user;
        $user_r->{Password} = defined $pass ? $pass : Links::Authenticate::auth('get_pass', { Username => $user });
        $user_r->{Email}    = $user . '@noemail.nodomain';
        $user_r->{ReceiveMail} = 'No';
        $user_r->{Password} = '' unless defined $user_r->{Password};
        my $defaults = $db->default();
        for (keys %$defaults) {
            $user_r->{$_} = $defaults->{$_} unless exists $user_r->{$_};
        }
        $db->insert($user_r) or die "Unable to auto-create user: $user. Reason: $GT::SQL::error";
    }
    return $user_r;
}

sub auth_valid_session {
# -----------------------------------------------------------------------------
# This functions checks to see if the session is valid, and returns the
# username.
#
    my $args = shift;
    my $session_id = $IN->param('s') || $IN->cookie($CFG->{user_cookie_prefix} . 's') || return;
    my $session;
    unless ($session = GT::Session::SQL->new({
        _debug       => $CFG->{debug_level},
        tb           => $DB->table('Sessions'),
        session_id   => $session_id,
        expires      => $CFG->{user_session_length},
        session_data => { sessions => $CFG->{user_sessions}, d => scalar($IN->param('d')) },
    })) { # Possibly an expired session
        GT::Session::SQL->new({
            tb => $DB->table('Sessions'),
            expires => $CFG->{user_session_length}
        })->cleanup; # Clear out old sessions
        return;
    }

    return $session->{info}->{session_user_id};
}

sub auth_create_session {
# -----------------------------------------------------------------------------
# This function creates a session, and prints the header and returns a hash
# reference with session => $id, and redirect => 0/1.
#
    my $args = shift;
    my $user = $args->{Username};
    my $remember = ($CFG->{user_sessions} eq 'Cookies' and ($args->{Remember} or $IN->param('Remember')));

# Create a new session.
    my $session = GT::Session::SQL->new({
        _debug          => $CFG->{debug_level},
        tb              => $DB->table('Sessions'),
        session_user_id => $user,
        session_data    => { sessions => $CFG->{user_sessions}, d => scalar($IN->param('d')) },
        expires         => ($remember ? 0 : $CFG->{user_session_length}),
    });

# Clear out old sessions.
    $session->cleanup;

# Get session id
    my $session_id = $session->{info}->{session_id};

# Now redirect to another URL and set cookies, or set URL string.
    my $url = $IN->param('url');
    my $redirect = 0;
    if ($CFG->{user_sessions} eq 'Cookies') {
        my $session_cookie = $IN->cookie(
            -name    => $CFG->{user_cookie_prefix} . 's',
            -value   => $session_id,
            -path    => '/',
            -domain  => $CFG->{user_cookie_domain},
            -expires => ($remember ? '+10y' : '')
        );
        if ($url) {
            print $IN->redirect(-force => 1, -cookie => [$session_cookie], -url => $url);
            $redirect = 1;
        }
        else {
            print $IN->header(-force => 1, -cookie => [$session_cookie]);
        }
    }
    else {
# If URL sessions are used, then the user will be forced into dynamic mode
# since there's no way to pass around the session id with the static URLs.
        if ($url) {
            unless ($url =~ s/([;&\?]s=)([^&;]+)/$1$session_id/) {
                $url .= ($url =~ /\?/ ? ';' : '?') . "s=$session_id";
            }
            unless ($url =~ /([;&\?]d=)([^&;]+)/) {
                $url .= ($url =~ /\?/ ? ';' : '?') . "d=1";
            }
            print $IN->redirect($url);
            $redirect = 1;
        }
        else {
            $IN->param(s => $session_id);
            $IN->param(d => 1);
            print $IN->header();
        }
    }
    return { session => $session_id, redirect => $redirect };
}

sub auth_delete_session {
# -----------------------------------------------------------------------------
# This function removes a session, returns 1 on success, undef on failure.
#
    print $IN->header(
        -cookie => $IN->cookie(
            -name    => $CFG->{user_cookie_prefix} . 's',
            -value   => '',
            -path    => '/',
            -domain  => $CFG->{user_cookie_domain},
            -expires => '-1y'
        )
    );
    my $session_id = $IN->cookie($CFG->{user_cookie_prefix} . 's') || $IN->param('s') || return;

    my $session = GT::Session::SQL->new({
        _debug     => $CFG->{debug_level},
        tb         => $DB->table('Sessions'),
        session_id => $session_id
    }) or return;

# Delete the cookie
    $session->delete or return;
    1;
}

1;
