# $Id: admin_mgr.pm,v 1.8 2004/07/24 18:23:34 gwolf Exp $
######################################
# Comas - Conference Management System
######################################
# Copyright 2003 CONSOL
# Congreso Nacional de Software Libre (http://www.consol.org.mx/)
#   Gunnar Wolf <gwolf@gwolf.cx>
#   Manuel Rabade <mig@mig-29.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
######################################

######################################
# Module: Comas::Admin::admin_mgr
# Manage Comas administrators
######################################
# Depends on:
#
# Comas::Common - Common functions for various Comas modules

# This module is not meant to be called on its own, it should be called from
# Comas::Admin.
package Comas::Admin::admin_mgr;

use strict;
use warnings;
use Carp;
use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(admin_list create_admin remove_adm set_adm_name 
		    set_adm_login set_adm_passwd query_admin_tasks 
		    grant_task_adm revoke_task_adm create_task delete_task);
our %EXPORT_TAGS = (adm => [qw(admin_list create_admin remove_adm 
				set_adm_name set_adm_login set_adm_passwd)], 
		    task => [qw(create_task delete_task query_admin_tasks)], 
		    priv => [qw(grant_task_adm revoke_task_adm)]);

=head1 NAME

Comas::Admin::admin_mgr - Manage Comas administrators

=head1 SYNOPSIS

This module is not meant to be used by itself, but as an auxiliary to
L<Comas::Admin>. Please check L<Comas::Admin>'s documentation, in the
B<Administrator Tasks> section, for further details on the usage for this
functions.

The functions in this module are exported in three categories:

=over 4

=item * b<adm>: Create, remove or modify an administrator (create_admin,
remove_adm, set_adm_name, set_adm_login, set_adm_passwd)

=item * b<task>: Create or delete administrative tasks (create_task,
delete_task)

=item * b<priv>: Manage the privileges of a particular administrator 
(grant_task_adm, revoke_task_adm)

=back

=head1 SEE ALSO

L<Comas::Admin> module documentation

=head1 AUTHOR

Gunnar Wolf, gwolf@gwolf.cx
Manuel Rabade, mig@mig-29.net

Comas has been developed for CONSOL, Congreso Nacional de Software Libre,
http://www.consol.org.mx/

=head1 COPYRIGHT

Copyright 2003 Gunnar Wolf and Manuel Rabade

This library is free software, you can redistribute it and/or modify it
under the terms of the GPL version 2 or later.

=cut

sub admin_list {
    my ($adm, $sth, @ids);
    $adm = shift;

    unless ($sth = $adm->{-db}->prepare('SELECT id, name, login FROM admin
            ORDER BY id') and $sth->execute) {
	carp 'Could not look up list of administrators';
	return undef;
    }

    return @{$sth->fetchall_arrayref};
}

sub create_admin {
    my ($adm, $login, $name, $passwd, $tasks, $sth, $id);
    $adm = shift;
    $login = shift;
    $name = shift;
    $passwd = shift;
    $tasks = shift;
    # Sometimes we will be called with only one parameter here. In order to
    # reduce confusion, we will accept a single scalar and turn it into the
    # corresponding array ref
    $tasks = [ $tasks ] unless ref $tasks;

    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Begin the transaction
    $adm->{-db}->begin_work;
    # Create the administrator
    unless ($sth = $adm->{-db}->prepare('INSERT INTO admin (login, name)
        VALUES (?, ?)') and $sth->execute($login, $name)) {
	carp 'Database error creating new administrative user';
	$adm->{-db}->rollback;
	return undef;
    }

    # Retreive the ID
    unless ($sth = $adm->{-db}->prepare("SELECT currval('admin_id_seq')") and
	    $sth->execute and ($id) = $sth->fetchrow_array) {
	carp 'Database error retreiving new administrative user ID';
	$adm->{-db}->rollback;
	return undef;
    }

    # Set the password
    unless ($sth = $adm->{-db}->prepare('SELECT set_admin_passwd (?, ?)') and
	    $sth->execute($login, $passwd)) {
	carp 'Database error setting the new administrative user password';
	$adm->{-db}->rollback;
	return undef;
    }

    # Register the requested tasks
    for my $task (@$tasks) {
	warn "Granting $task to $id";
	unless ($adm->grant_task_adm($id, $task)) {
	    carp "Error granting privileges for task $task";
	    $adm->{-db}->rollback;
	    return undef;
	}
    }

    $adm->{-db}->commit;
    return $id;
}

sub remove_adm {
    my ($adm, $id, $sth);
    $adm = shift;
    $id = shift;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Removing yourself is not nice!
    if ($id == $adm->get_id) {
	carp 'Cannot remove yourself';
	return undef;
    }

    # We should not be able to remove an inexistant administrator...
    unless ($sth = $adm->{-db}->prepare('SELECT count(id) FROM admin WHERE
        id=?') and $sth->execute($id)) {
	carp "Error checking for  administrator's existence";
	return undef;
    }
    unless ($sth->fetchrow_array) {
	carp 'Administrator does not exist - Has he already been removed?';
	return undef;
    }

    # If the administrator has any payments registered to his name, we 
    # should not delete him - Warn, return.
    if ($sth = $adm->{-db}->prepare('SELECT id FROM payment WHERE 
        admin_id = ?') and $sth->execute($id)) {
	if (my @payments = map {$_->[0]} @{$sth->fetchall_arrayref}) {
	    carp 'Administrator undeletable - Has registered payments ',
	    join(', ', @payments);
	    return undef;
	}
    } else {
	carp 'Error checking if selected administrator is deletable';
	return undef;
    }
    
    # Ok, proceed to delete administrator. Transaction begins here.
    $adm->{-db}->begin_work;

    # First revoke all of the administrators' tasks
    unless ($sth = $adm->{-db}->prepare('DELETE FROM task_admin WHERE 
        admin_id=?') and $sth->execute($id)) {
	carp "Error removing administrators' access";
	$adm->{-db}->rollback;
	return undef;
    }

    # Now remove the administrators' account
    unless ($sth = $adm->{-db}->prepare('DELETE FROM admin WHERE id=?') and
	    $sth->execute($id)) {
	carp 'Error removing administrator';
	$adm->{-db}->rollback;
	return undef;
    }

    # Everything went fine? Commit the transaction, return a true value.
    $adm->{-db}->commit;
    return 1;
}

sub set_adm_name {
    my ($adm, $id, $name, $sth);
    $adm = shift;
    $id = shift;
    $name = shift;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }
    
    unless ($sth = $adm->{-db}->prepare('UPDATE admin SET name=? WHERE id=?')
	    and $sth->execute($name, $id)) {
	carp 'Unable to change name';
	return undef;
    }
    return 1;
}

sub set_adm_login {
    my ($adm, $id, $login, $sth);
    $adm = shift;
    $id = shift;
    $login = shift;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    unless ($sth = $adm->{-db}->prepare('UPDATE admin SET login=? WHERE id=?')
	    and $sth->execute($login, $id)) {
	carp 'Unable to change login';
	return undef;
    }
    return 1;
}

sub set_adm_passwd {
    my ($adm, $login, $passwd, $sth);
    $adm = shift;
    $login = $adm->get_login(-id=>shift);
    $passwd = shift;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    unless ($sth = $adm->{-db}->prepare('SELECT set_admin_passwd(?, ?)')
	    and $sth->execute($login, $passwd)) {
	carp 'Unable to change login';
	return undef;
    }
    return 1;
}

sub query_admin_tasks {
    my ($adm, $qry_adm_id, %tasks, @res);
    $adm = shift;
    $qry_adm_id = shift;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied';
	return undef;
    }

    @res = ();
    %tasks = $adm->get_tasks;
    for my $task (%tasks) {
	push @res, $task if ($adm->ck_admin_task(-task=>$task, 
						 -id=>$qry_adm_id));
    }

    return @res;
}

sub grant_task_adm {
    my ($adm, $id, $task, %tasks, $sth);
    $adm = shift;
    $id = shift;
    $task = shift;
    %tasks = $adm->get_tasks;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Does the requested task exist?
    unless (exists $tasks{$task}) {
	carp 'Requested task does not exist';
	return undef
    }

    # The database will take care of checking whether the task exists...
    unless ($sth = $adm->{-db}->prepare('INSERT INTO task_admin (admin_id, 
        task_id) VALUES (?, ?)') and $sth->execute($id, $tasks{$task})) {
	carp 'Unable to grant privileges';
	return undef;
    }
    return 1;
}

sub revoke_task_adm {
    my ($adm, $id, $task, %tasks, $sth);
    $adm = shift;
    $id = shift;
    $task = shift;
    %tasks = $adm->get_tasks;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Does the admin have this task assigned? If not, just exit successfully
    return 1 unless $adm->ck_admin_task(-task=>$task, -id=>$id);

    unless ($sth = $adm->{-db}->prepare('DELETE FROM task_admin WHERE 
        admin_id = ? AND task_id = ?') and $sth->execute($id, $tasks{$task})) {
	carp 'Unable to revoke privileges';
	return undef;
    }
    return 1;
}

sub create_task {
    my ($adm, $task, %tasks, $sth);
    $adm = shift;
    $task = shift;
    %tasks = $adm->get_tasks;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Does the task already exist? If so, just exit successfully
    return $tasks{$task} if exists $tasks{$task};

    $adm->{-db}->begin_work;
    unless ($sth = $adm->{-db}->prepare('INSERT INTO task (descr) VALUES (?)')
	    and $sth->execute($task)) {
	carp 'Could not create task';
	$adm->{-db}->rollback;
	return undef;
    }

    # Easiest way to get the new task ID is to ask again for get_tasks
    %tasks = $adm->get_tasks;
    # Now... What if the task is not yet registered?
    unless (exists $tasks{$task}) {
	carp "Could not retreive tasks' information";
	$adm->{-db}->rollback;
	return undef;
    }

    # Ok, everything is set
    $adm->{-db}->commit;
    return $tasks{$task};
}

sub delete_task {
    my ($adm, $task, %tasks, $sth);
    $adm = shift;
    $task = shift;
    %tasks = $adm->get_tasks;
    unless ($adm->ck_admin_task(-task=>'admin_mgr')) {
	carp 'Access denied.';
	return undef;
    }

    # Does the task exist? If not, just exit successfully
    return 1 unless $tasks{$task};

    $adm->{-db}->begin_work;
    # Remove the tasks' privilege from every admin who has it
    unless ($sth = $adm->{-db}->prepare('DELETE FROM task_admin WHERE 
        task_id = ?') and $sth->execute($tasks{$task})) {
	carp 'Could not remove task privilege from other administrators';
	$adm->{-db}->rollback;
	return undef;
    }

    # Now remove the task
    unless ($sth = $adm->{-db}->prepare('DELETE FROM task WHERE descr=?') and
	    $sth->execute($task)) {
	carp 'Could not remove task';
	$adm->{-db}->rollback;
	return undef;
    }

    $adm->{-db}->commit;
    return 1;
}

1;

# $Log: admin_mgr.pm,v $
# Revision 1.8  2004/07/24 18:23:34  gwolf
# Some minor fixes, one extra function...
#
# Revision 1.7  2004/04/23 19:48:44  gwolf
# Added an 'admin_list' method for getting the list of registered administrators
#
# Revision 1.6  2003/12/20 04:14:51  mig
# - Agrego tags Id y Log que expanda el CVS
#
