<?php
	/**
	* Access Control List - Security scheme based on ACL design
	* @author Dan Kuykendall <seek3r@phpgroupware.org>
	* @copyright Copyright (C) 2000-2004 Free Software Foundation, Inc. http://www.fsf.org/
	* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
	* @package phpgwapi
	* @subpackage accounts
	* @version $Id: class.acl.inc.php,v 1.65.4.14 2004/06/07 14:02:28 skwashd Exp $
	*/

	/**
	* Access Control List - Security scheme based on ACL design
	*
	* This can manage rights to 'run' applications, and limit certain features within an application.
	* It is also used for granting a user "membership" to a group, or making a user have the security 
	* equivilance of another user. It is also used for granting a user or group rights to various records,
	* such as todo or calendar items of another user.
	* @package phpgwapi
	* @subpackage accounts
	* @internal syntax: CreateObject('phpgwapi.acl',int account_id);
	* @internal example: $acl = CreateObject('phpgwapi.acl',5);  // 5 is the user id
	* @internal example: $acl = CreateObject('phpgwapi.acl',10);  // 10 is the user id
	*/
	class acl
	{
		/**
		* Account id
		* @var integer Account id
		*/
		var $account_id;
		/**
		* Account type
		* @var string Account type
		*/
		var $account_type;
		/**
		* Array with ACL records
		* @var array Array with ACL records
		*/
		var $data = Array();
		/**
		* Database connection
		* @var object Database connection
		*/
		var $db;

		/**
		* ACL constructor for setting account id
		*
		* Sets the ID for $account_id. Can be used to change a current instances id as well.
		* Some functions are specific to this account, and others are generic.
		* @param integer $account_id Account id
		*/
		function acl($account_id = '')
		{
			$this->db	= $GLOBALS['phpgw']->db;

			if (!($this->account_id = intval($account_id)))
			{
				$this->account_id = get_account_id($account_id,@$GLOBALS['phpgw_info']['user']['account_id']);
			}
		}

		/**
		* Get list of xmlrpc or soap functions
		*
		* @param string|array $_type Type of methods to list. Could be xmlrpc or soap
		* @return array Array with xmlrpc or soap functions. Might also be empty.
		* This handles introspection or discovery by the logged in client,
		* in which case the input might be an array.  The server always calls
		* this function to fill the server dispatch map using a string.
		*/
		function list_methods($_type='xmlrpc')
		{
			if (is_array($_type))
			{
				$_type = $_type['type'] ? $_type['type'] : $_type[0];
			}

			switch($_type)
			{
				case 'xmlrpc':
				$xml_functions = array(
						'read_repository' => array(
							'function'  => 'read_repository',
							'signature' => array(array(xmlrpcStruct)),
							'docstring' => lang('FIXME!')
						),
						'get_rights' => array(
							'function'  => 'get_rights',
							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
							'docstring' => lang('FIXME!')

						),
						'list_methods' => array(
							'function'  => 'list_methods',
							'signature' => array(array(xmlrpcStruct,xmlrpcString)),
							'docstring' => lang('Read this list of methods.')
						)
					);
					return $xml_functions;
				case 'soap':
					return $this->soap_functions;
				default:
					return array();
			}
 		}


		// These are the standard $account_id specific functions


		/**
		* Reads ACL records from database and return array along with storing it
		*
		* @return array Array with ACL records
		* @access private
		*/
		function read_repository()
		{
			// For some reason, calling this via XML-RPC doesn't call the constructor.
			// Here is yet another work around(tm) (jengo)
			if (! $this->account_id)
			{
				$this->acl();
			}

			$sql = 'select * from phpgw_acl where (acl_account in ('.$this->account_id.', 0'; 

			$groups = $this->get_location_list_for_id('phpgw_group', 1, $this->account_id);
			while($groups && (list($key,$value) = each($groups)))
			{
				$sql .= ','.$value;
			}
			$sql .= '))';
			$this->db->query($sql ,__LINE__,__FILE__);
			$count = $this->db->num_rows();
			$this->data = Array();
			for ($idx = 0; $idx < $count; ++$idx)
			{
				//reset ($this->data);
				//while(list($idx,$value) = each($this->data)){
				$this->db->next_record();
				$this->data[] = array(
					'appname' => $this->db->f('acl_appname'),
					'location' => $this->db->f('acl_location'), 
					'account' => $this->db->f('acl_account'),
					'rights' => $this->db->f('acl_rights')
				);
			}
			reset ($this->data);
			return $this->data;
		}

		/**
		* Get acl records
		*
		* @return array Array with ACL records
		*/
		function read()
		{
			if (count($this->data) == 0)
			{
				$this->read_repository();
			}
			reset ($this->data);
			return $this->data;
		}

		/**
		* Add ACL record
		*
		* @param string|boolean $appname Application name. Default: false derives value from $phpgw_info['flags']['currentapp']
		* @param string $location Application location
		* @param integer $rights Access rights in bitmask form
		* @return array Array with ACL records
		*/
		function add($appname = False, $location, $rights)
		{
			if ($appname == False)
			{
				settype($appname,'string');
				$appname = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}
			$this->data[] = array('appname' => $appname, 'location' => $location, 'account' => $this->account_id, 'rights' => $rights);
			reset($this->data);
			return $this->data;
		}

		/**
		* Delete ACL records
		*
		* @param string|boolean $appname Application name, defaults to false which means $phpgw_info['flags']['currentapp']
		* @param string $location Application location
		* @return array Array with ACL records
		*/
		function delete($appname = False, $location)
		{
			if ($appname == False)
			{
				settype($appname,'string');
				$appname = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}
			$count = count($this->data);
			reset ($this->data);
			while(list($idx,$value) = each($this->data))
			{
				if ($this->data[$idx]['appname'] == $appname && $this->data[$idx]['location'] == $location && $this->data[$idx]['account'] == $this->account_id)
				{
					$this->data[$idx] = Array();
				}
			}
			reset($this->data);
			return $this->data;
		}

		/**
		* Save repository in database
		*
		* @return array Array with ACL records
		*/
		
		function save_repository()
		{
			reset($this->data);

			$sql = 'delete from phpgw_acl where acl_account = '. intval($this->account_id);
			$this->db->query($sql ,__LINE__,__FILE__);

			$count = count($this->data);
			reset ($this->data);
			while(list($idx,$value) = each($this->data))
			{
				if ($this->data[$idx]['account'] == $this->account_id)
				{
					$sql = 'insert into phpgw_acl (acl_appname, acl_location, acl_account, acl_rights)';
					$sql .= " values('".$this->data[$idx]['appname']."', '"
						. $this->data[$idx]['location']."', ".$this->account_id.', '. intval($this->data[$idx]['rights']).')';
					$this->db->query($sql ,__LINE__,__FILE__);
				}
			}
			reset($this->data);
			return $this->data;
		}


		// These are the non-standard $account_id specific functions


		/**
		* Get rights from the repository not specific to this object
		*
		* @param $location Application location
		* @param string|boolean $appname Application name, defaults to false which means $phpgw_info['flags']['currentapp']
		* @return integer Access rights in bitmask form
		*/
		function get_rights($location,$appname = False)
		{
			// For XML-RPC, change this once its working correctly for passing parameters (jengo)
			if (is_array($location))
			{
				$a        = $location;
				$location = $a['location'];
				$appname  = $a['appname'];
			}

			if (count($this->data) == 0)
			{
				$this->read_repository();
			}
			reset ($this->data);
			if ($appname == False)
			{
				settype($appname,'string');
				$appname = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}
			$count = count($this->data);
			if ($count == 0 && $GLOBALS['phpgw_info']['server']['acl_default'] != 'deny')
			{
				return True;
			}
			$rights = 0;
			//for ($idx = 0; $idx < $count; ++$idx){
			reset ($this->data);
			while(list($idx,$value) = each($this->data))
			{
				if ($this->data[$idx]['appname'] == $appname)
				{
					if ($this->data[$idx]['location'] == $location || $this->data[$idx]['location'] == 'everywhere')
					{
						if ($this->data[$idx]['rights'] == 0)
						{
							return False;
						}

						$rights |= $this->data[$idx]['rights'];
					}
				}
			}
			return $rights;
		}
		/**
		* Check required rights (not specific to this object)
		*
		* @param string $location Application location
		* @param integer $required Required right (bitmask) to check against
		* @param string|boolean $appname Application name, defaults to false which means $phpgw_info['flags']['currentapp']
		* @return boolean True when $required bitmap matched otherwise false
		*/
		function check($location, $required, $appname = False)
		{
			$rights = $this->get_rights($location,$appname);
			return !!($rights & $required);
		}
		
		/**
		* Get specific rights
		*
		* @param string $location Application location
		* @param string|boolean $appname Application name, defaults to false which means $phpgw_info['flags']['currentapp']
		* @return integer Access rights in bitmask form
		*/
		function get_specific_rights($location, $appname = False)
		{
			if ($appname == False)
			{
				settype($appname,'string');
				$appname = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}

			$count = count($this->data);
			if ($count == 0 && $GLOBALS['phpgw_info']['server']['acl_default'] != 'deny')
			{
				return True;
			}
			$rights = 0;

			reset ($this->data);
			while(list($idx,$value) = each($this->data))
			{
				if ($this->data[$idx]['appname'] == $appname && 
					($this->data[$idx]['location'] == $location ||
					$this->data[$idx]['location'] == 'everywhere') &&
					$this->data[$idx]['account'] == $this->account_id)
				{
					if ($this->data[$idx]['rights'] == 0)
					{
						return False;
					}
					$rights |= $this->data[$idx]['rights'];
				}
			}
			return $rights;
		}
		
		/**
		* Check specific rights
		*
		* @param string $location Application location
		* @param integer $required Required rights as bitmap
		* @param string|boolean $appname Application name, defaults to false which means $phpgw_info['flags']['currentapp']
		* @return boolean True when $required bitmap matched otherwise false
		*/
		function check_specific($location, $required, $appname = False)
		{
			$rights = $this->get_specific_rights($location,$appname);
			return !!($rights & $required);
		}
		
		/**
		* Get location list for an application with specific access rights
		*
		* @param $app Application name
		* @param integer $required Required rights as bitmap
		* @return boolean|array Array with location list or false
		*/
		function get_location_list($app, $required)
		{
			// User piece
			$sql = "select acl_location, acl_rights from phpgw_acl where acl_appname = '$app' ";
			$sql .= " and (acl_account in ('".$this->account_id."', 0"; // group 0 covers all users
			$equalto = $GLOBALS['phpgw']->accounts->security_equals($this->account_id);
			if (is_array($equalto) && count($equalto) > 0)
			{
				for ($idx = 0; $idx < count($equalto); ++$idx)
				{
					$sql .= ','.$equalto[$idx][0];
				}
			}
			$sql .= ')))';

			$this->db->query($sql ,__LINE__,__FILE__);
			$rights = 0;
			if ($this->db->num_rows() == 0 )
			{
				return False;
			}
			while ($this->db->next_record())
			{
				if ($this->db->f('acl_rights') == 0)
				{
					return False;
				}
				$rights |= $this->db->f('acl_rights');
				if (!!($rights & $required) == True)
				{
					$locations[] = $this->db->f('acl_location');
				}
				else
				{
					return False;
				}
			}
			return $locations;
		}

/*
		This is kinda how the function SHOULD work, so that it doesnt need to do its own sql query. 
		It should use the values in the $this->data

		function get_location_list($app, $required)
		{
			if ($appname == False)
			{
				$appname = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}

			$count = count($this->data);
			if ($count == 0 && $GLOBALS['phpgw_info']['server']['acl_default'] != 'deny'){ return True; }
			$rights = 0;

			reset ($this->data);
			while(list($idx,$value) = each($this->data))
			{
				if ($this->data[$idx]['appname'] == $appname && $this->data[$idx]['rights'] != 0)
				{
					$location_rights[$this->data[$idx]['location']] |= $this->data[$idx]['rights'];
				}
			}
			reset($location_rights);
			for ($idx = 0; $idx < count($location_rights); ++$idx)
			{
				if (!!($location_rights[$idx] & $required) == True)
				{
					$location_rights[] = $this->data[$idx]['location'];
				}
			}
			return $locations;
		}
*/

		// These are the generic functions. Not specific to $account_id


		/**
		* Add repository information for an application
		*
		* @param string $app Application name
		* @param string $location Application location
		* @param integer $account_id Account id
		* @param integer $rights Access rights in bitmap form
		* @return boolean Always true
		*/
		function add_repository($app, $location, $account_id, $rights)
		{
			$this->delete_repository($app, $location, $account_id);
			$sql = 'insert into phpgw_acl (acl_appname, acl_location, acl_account, acl_rights)';
			$sql .= " values ('" . $app . "','" . $location . "','" . $account_id . "','" . $rights . "')";
			$this->db->query($sql ,__LINE__,__FILE__);
			return True;
		}

		/**
		* Delete repository information for an application
		*
		* @param string $app Application name
		* @param string $location Application location
		* @param integer $account_id Account id
		* @return integer Number of deleted entries
		*/
		function delete_repository($app, $location, $accountid = '')
		{
			static $cache_accountid;
			
			$accountid = intval($accountid);
			if ($accountid > 0)
			{
				if(isset($cache_accountid[$accountid]) && $cache_accountid[$accountid])
				{
					$account_id = $cache_accountid[$accountid];
				}
				else
				{
					$account_id = get_account_id($accountid,$this->account_id);
					$cache_accountid[$accountid] = $account_id;
				}
				$account_sel = ' and acl_account=' . $account_id;
			}

			$sql = "delete from phpgw_acl where acl_appname like '" . $app . "' and acl_location like '" . $location . "'" . $account_sel; 
			$this->db->query($sql ,__LINE__,__FILE__);
			return $this->db->num_rows();
		}

		/**
		* Get application list for an account id
		*
		* @param string $location Application location
		* @param integer $required Access rights as bitmap
		* @param integer $account_id Account id defaults to $phpgw_info['user']['account_id'];
		* @return boolean|array Array with list of applications or false
		*/
		function get_app_list_for_id($location, $required, $accountid = '')
		{
			static $cache_accountid;

			if($cache_accountid[$accountid])
			{
				$account_id = $cache_accountid[$accountid];
			}
			else
			{
				$account_id = get_account_id($accountid,$this->account_id);
				$cache_accountid[$accountid] = $account_id;
			}
			$sql  = 'SELECT acl_appname, acl_rights from phpgw_acl ';
			$sql .= "where acl_location = '" . $this->db->db_addslashes($location) . "' ";
			$sql .= 'AND acl_account = ' . intval($account_id);
			$this->db->query($sql ,__LINE__,__FILE__);
			$rights = 0;
			if ($this->db->num_rows() == 0 )
			{
				return False;
			}
			while ($this->db->next_record())
			{
				if ($this->db->f('acl_rights') == 0)
				{
					return False;
				}
				$rights |= $this->db->f('acl_rights');
				if (!!($rights & $required) == True)
				{
					$apps[] = $this->db->f('acl_appname');
				}
			}
			return $apps;
		}

		/**
		* Get location list for id
		*
		* @param string $app Application name
		* @param integer $required Required access rights in bitmap form
		* @param integer $account_id Account id defaults to $phpgw_info['user']['account_id'];
		* @return array|boolean Array with location list or false
		*/
		function get_location_list_for_id($app, $required, $accountid = '')
		{
			static $cache_accountid;

			if($cache_accountid[$accountid])
			{
				$account_id = $cache_accountid[$accountid];
			}
			else
			{
				$account_id = get_account_id($accountid,$this->account_id);
				$cache_accountid[$accountid] = $account_id;
			}
			$sql  = 'SELECT acl_location, acl_rights ';
			$sql .= "FROM phpgw_acl where acl_appname = '" . $this->db->db_addslashes($app) . "' ";
			$sql .= 'AND acl_account =' . intval($account_id);
			
			$this->db->query($sql ,__LINE__,__FILE__);
			$rights = 0;
			if ($this->db->num_rows() == 0 )
			{
				return False;
			}
			while ($this->db->next_record())
			{
				if ($this->db->f('acl_rights'))
				{
					$rights |= $this->db->f('acl_rights');
					if (!!($rights & $required) == True)
					{
						$locations[] = $this->db->f('acl_location');
					}
				}
			}
			return $locations;
		}
		
		/**
		* Get ids for location
		*
		* @param string $location Application location
		* @param integer $required Required access rights in bitmap format
		* @param string $app Application name, defaults to $phpgw_info['flags']['currentapp'];
		* @return array|boolean Array with account ids or false
		*/
		function get_ids_for_location($location, $required, $app = False)
		{
			if ($app == False)
			{
				$app = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}
			$sql = "select acl_account, acl_rights from phpgw_acl where acl_appname = '$app' and ";
			$sql .= "acl_location = '".$location."'";
			$this->db->query($sql ,__LINE__,__FILE__);
			$rights = 0;
			if ($this->db->num_rows() == 0 )
			{
				return False;
			}
			while ($this->db->next_record())
			{
				$rights = 0;
				$rights |= $this->db->f('acl_rights');
				if (!!($rights & $required) == True)
				{
					$accounts[] = intval($this->db->f('acl_account'));
				}
			}
			@reset($accounts);
			return $accounts;
		}

		/**
		* Get a list of applications a user has rights to
		*
		* @param integer $account_id Account id, defaults to $phpgw_info['user']['account_id']
		* @return array|boolean Associativ array containing list of application rights in bitmap form or false
		*/
		function get_user_applications($accountid = '')
		{
			static $cache_accountid;

			if($cache_accountid[$accountid])
			{
				$account_id = $cache_accountid[$accountid];
			}
			else
			{
				$account_id = get_account_id($accountid,$this->account_id);
				$cache_accountid[$accountid] = $account_id;
			}
			$db2 = $this->db;
			$memberships = $GLOBALS['phpgw']->accounts->membership($account_id);
			$sql = "select acl_appname, acl_rights from phpgw_acl where acl_location = 'run' and "
				. 'acl_account in ';
			$security = '('.$account_id;
			while($groups = @each($memberships))
			{
				$group = each($groups);
				$security .= ','.$group[1]['account_id'];
			}
			$security .= ')';
			$db2->query($sql . $security ,__LINE__,__FILE__);

			if ($db2->num_rows() == 0)
			{
				return False;
			}
			while ($db2->next_record())
			{
				if(isset($apps[$db2->f('acl_appname')]))
				{
					$rights = $apps[$db2->f('acl_appname')];
				}
				else
				{
					$rights = 0;
					$apps[$db2->f('acl_appname')] = 0;
				}
				$rights |= $db2->f('acl_rights');
				$apps[$db2->f('acl_appname')] |= $rights;
			}
			return $apps;
		}

		/**
		* Get application specific account based granted rights list
		*
		* @param string $app Application name, defaults to $phpgw_info['flags']['currentapp']
		* @return array Assoziativ array with granted access rights for accounts
		*/
		function get_grants($app='')
		{
			$db2 = $this->db;

			if ($app=='')
			{
				$app = $GLOBALS['phpgw_info']['flags']['currentapp'];
			}

			$sql = "select acl_account, acl_rights from phpgw_acl where acl_appname = '$app' and "
				. "acl_location in ";
			$security = "('". $this->account_id ."'";
			$myaccounts = CreateObject('phpgwapi.accounts');
			$my_memberships = $myaccounts->membership($this->account_id);
			unset($myaccounts);
			@reset($my_memberships);
			while($my_memberships && (list($key,$group) = each($my_memberships)))
			{
				$security .= ",'" . $group['account_id'] . "'";
			}
			$security .= ')';
			$db2->query($sql . $security ,__LINE__,__FILE__);
			$rights = 0;
			$accounts = Array();
			if ($db2->num_rows() == 0)
			{
				$grants[$GLOBALS['phpgw_info']['user']['account_id']] = 31;
				return $grants;
			}
			while ($db2->next_record())
			{
				$grantor = $db2->f('acl_account');
				$rights = $db2->f('acl_rights');

				if(!isset($accounts[$grantor]))
				// cache the group-members for performance
				{
					// if $grantor is a group, get its members
					$members = $this->get_ids_for_location($grantor,1,'phpgw_group');
					if(!$members)
					{
						$accounts[$grantor] = Array($grantor);
						$is_group[$grantor] = False;
					}
					else
					{
						$accounts[$grantor] = $members;
						$is_group[$grantor] = True;
					}
				}
				if(@$is_group[$grantor])
				{
					// Don't allow to override private!
					$rights &= (~ PHPGW_ACL_PRIVATE);
					if(!isset($grants[$grantor]))
					{
						$grants[$grantor] = 0;
					}
					$grants[$grantor] |= $rights;
					if(!!($rights & PHPGW_ACL_READ))
					{
						$grants[$grantor] |= PHPGW_ACL_READ;
					}
				}
				while(list($nul,$grantors) = each($accounts[$grantor]))
				{
					if(!isset($grants[$grantors]))
					{
						$grants[$grantors] = 0;
					}
					$grants[$grantors] |= $rights;
				}
				reset($accounts[$grantor]);
			}
			$grants[$GLOBALS['phpgw_info']['user']['account_id']] = 31;
			return $grants;
		}
	}
?>
