# Copyright (c) vhffs project and its contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions 
# are met:
#
# 1. Redistributions of source code must retain the above copyright 
#   notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in 
#   the documentation and/or other materials provided with the 
#   distribution.
#3. Neither the name of vhffs nor the names of its contributors 
#   may be used to endorse or promote products derived from this 
#   software without specific prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
#FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
#COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
# POSSIBILITY OF SUCH DAMAGE.

#Vhffs::ACL written by sod` <dieu AT gunnm DOT org>

# This software is free software, please read LICENCE file

use Vhffs::Constants;

use strict;
use utf8;
use diagnostics;

package Vhffs::Acl;

=pod

=head1 NAME

Vhffs::Acl - Handle Access Control Lists in VHFFS.

=head1 METHODS

=cut

sub get_perm
{
	my $vhffs = shift;
	my $object = shift;
	my $user = shift;

	return Vhffs::Constants::ACL_DENIED unless( defined $vhffs && defined $object && defined $user );
	return Vhffs::Constants::ACL_DELETE if $user->is_admin;

	my $ooid = $object->get_oid;
	my $uoid = $user->get_oid;
	my $goid = $object->get_group->get_oid;

	my $perm;
	$perm = $vhffs->{'db'}->selectrow_array('SELECT perm FROM vhffs_acl WHERE granted_oid=? AND target_oid=?', undef, $uoid, $ooid);
	$perm = $vhffs->{'db'}->selectrow_array('SELECT perm FROM vhffs_acl WHERE granted_oid=? AND target_oid=? AND EXISTS ( SELECT * FROM vhffs_user_group ug INNER JOIN vhffs_users u ON ug.uid=u.uid INNER JOIN vhffs_groups g ON ug.gid=g.gid AND u.object_id=? AND g.object_id=? )', undef, $goid, $ooid, $uoid, $goid ) unless defined $perm;
	$perm = Vhffs::Constants::ACL_DENIED unless defined $perm;
	$perm = Vhffs::Constants::ACL_VIEW if( $perm < Vhffs::Constants::ACL_VIEW && $user->is_moderator );

	return $perm;	
}


=pod

=head2 add_acl

    die("Unable to add ACL\n") if( Vhffs::Acl::add_acl($granted_oid, $svc, $perm, $vhffs) );

Grant permission C<$perm> to user or group C<$granted_oid> on service C<$svc>.

Should be modified soon to use OIDs instead of heavy objects.

=cut

sub add_acl
{
	my ( $granted_oid , $target_oid , $perm , $vhffs ) = @_;

	return -1 unless(defined $granted_oid && defined $target_oid );
	return -2 unless( $granted_oid->get_type == Vhffs::Constants::TYPE_USER || $granted_oid->get_type == Vhffs::Constants::TYPE_GROUP );
	my $sql = 'INSERT INTO vhffs_acl(granted_oid, perm, target_oid) VALUES(?, ?, ?)';
	my $dbh = $vhffs->get_db_object;
	return -3 unless( $dbh->do($sql, undef, $granted_oid->get_oid, $perm, $target_oid->get_oid) );
	return 1;
}


sub add_acl_oid
{
	my ( $vhffs, $target_oid , $granted_oid , $perm ) = @_;
	return -1 unless(defined $vhffs && defined $target_oid && defined $granted_oid && defined $perm );
	return -3 unless $vhffs->get_db_object->do( 'INSERT INTO vhffs_acl(granted_oid, perm, target_oid) VALUES(?, ?, ?)' , undef, $granted_oid, $perm, $target_oid);
	return 1;
}


sub update_acl
{
	my ($vhffs, $target_oid, $granted_oid, $perm) = @_;

	return -1 unless(defined $vhffs && defined $target_oid && defined $granted_oid && defined $perm);

	# If no line was updated, ACL doesn't exists => error
	return -2 unless( $vhffs->get_db_object->do( 'UPDATE vhffs_acl SET perm = ? WHERE target_oid=? AND granted_oid=?', undef, $perm, $target_oid, $granted_oid) > 0 );
	return 1;
}


# return 1 if acl has been added, 2 if acl has been updated, 3 if acl has been deleted, -1 is something went wrong
sub add_update_or_del_acl
{
	my ($vhffs, $target_oid, $granted_oid, $perm) = @_;
	return -1 unless(defined $vhffs && defined $target_oid && defined $granted_oid && defined $perm);

	if( $perm == Vhffs::Constants::ACL_UNDEFINED )  {
		return 3 if( del_acl( $vhffs, $target_oid, $granted_oid ) > 0 );
		return -1;
	}

	return 2 if( update_acl( $vhffs, $target_oid, $granted_oid, $perm ) > 0 );
	return 1 if( add_acl_oid( $vhffs, $target_oid, $granted_oid, $perm ) > 0 );
	return -1;
}


sub del_acl
{
	my ( $vhffs, $target_oid , $granted_oid ) = @_;

	return -1 unless(defined $vhffs && defined $target_oid && defined $granted_oid);

	my $dbh = $vhffs->get_db_object();

	$dbh->begin_work();
	my $sql = 'DELETE FROM vhffs_acl WHERE granted_oid=? AND target_oid=?';
	my $sth = $dbh->prepare( $sql );
	$sth->execute( $granted_oid, $target_oid ) or return -1;
    
	$sql = 'SELECT COUNT(*) FROM vhffs_acl WHERE perm>=? AND target_oid=?';
	$sth = $dbh->prepare( $sql );
	$sth->execute(Vhffs::Constants::ACL_MANAGEACL, $target_oid) or return -1;
	my ($count) = $sth->fetchrow();

	# We want to have at least one person who can handle ACLs
	if( $count == 0 ) {
		$dbh->rollback();
		return -2;
	}

	$dbh->commit();
	return 1;
}


=pod

=head2 get_object_acl

    my $rights = Vhffs::Acl::get_object_acl($object, $vhffs);

Returns an array of hashref with keys 'granted_oid, name, perm'.
A NULL perm is a non existing ACL.
A NULL name is the default ACL.

Example:
 granted_oid |   name   | perm 
-------------+----------+------
           3 |          |    0   <== default ACL
           1 | user1    |   10
          25 | user2    |   10
          72 | user3    |        <== user without ACL
(4 rows)

=cut

sub get_object_acl
{
	my ( $vhffs , $object ) = @_;
	return undef unless( defined $vhffs && defined $object );

	my $sth = $vhffs->get_db_object->prepare( 'SELECT u.object_id AS granted_oid, u.username AS name, aclu.perm FROM vhffs_users u INNER JOIN vhffs_user_group ug ON ug.uid=u.uid INNER JOIN vhffs_object o ON o.owner_gid=ug.gid LEFT OUTER JOIN (SELECT acl.granted_oid, acl.perm FROM vhffs_acl acl WHERE acl.target_oid=?) AS aclu ON aclu.granted_oid=u.object_id WHERE o.object_id=? UNION SELECT g.object_id, NULL, aclg.perm FROM vhffs_groups g INNER JOIN vhffs_object o ON g.gid=o.owner_gid INNER JOIN vhffs_acl aclg ON aclg.granted_oid=g.object_id WHERE aclg.target_oid=? ORDER BY name ASC' );
	return undef unless $sth->execute( $object->get_oid, $object->get_oid, $object->get_oid );
	return $sth->fetchall_arrayref({});
}


1;
