<?php
/**
* CalDAV Server - handle ACL method
*
* @package   davical
* @subpackage   caldav
* @author    Andrew McMillan <andrew@morphoss.com>
* @copyright Morphoss Ltd
* @license   http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
dbg_error_log("ACL", "method handler");

require_once('DAVResource.php');

$request->NeedPrivilege('DAV::write-acl');

if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || (isset($c->dbg['put']) && $c->dbg['put'])) ) {
  $fh = fopen('/var/log/davical/MOVE.debug','w');
  if ( $fh ) {
    fwrite($fh,$request->raw_post);
    fclose($fh);
  }
}

$resource = new DAVResource( $request->path );

/**
* Preconditions
   (DAV:no-ace-conflict): The ACEs submitted in the ACL request MUST NOT
   conflict with each other.  This is a catchall error code indicating
   that an implementation-specific ACL restriction has been violated.

   (DAV:no-protected-ace-conflict): The ACEs submitted in the ACL
   request MUST NOT conflict with the protected ACEs on the resource.
   For example, if the resource has a protected ACE granting DAV:write
   to a given principal, then it would not be consistent if the ACL
   request submitted an ACE denying DAV:write to the same principal.

   (DAV:no-inherited-ace-conflict): The ACEs submitted in the ACL
   request MUST NOT conflict with the inherited ACEs on the resource.
   For example, if the resource inherits an ACE from its parent
   collection granting DAV:write to a given principal, then it would not
   be consistent if the ACL request submitted an ACE denying DAV:write
   to the same principal.  Note that reporting of this error will be
   implementation-dependent.  Implementations MUST either report this
   error or allow the ACE to be set, and then let normal ACE evaluation
   rules determine whether the new ACE has any impact on the privileges
   available to a specific principal.

   (DAV:limited-number-of-aces): The number of ACEs submitted in the ACL
   request MUST NOT exceed the number of ACEs allowed on that resource.
   However, ACL-compliant servers MUST support at least one ACE granting
   privileges to a single principal, and one ACE granting privileges to
   a group.

   (DAV:deny-before-grant): All non-inherited deny ACEs MUST precede all
   non-inherited grant ACEs.

   (DAV:grant-only): The ACEs submitted in the ACL request MUST NOT
   include a deny ACE.  This precondition applies only when the ACL
   restrictions of the resource include the DAV:grant-only constraint
   (defined in Section 5.6.1).

   (DAV:no-invert):  The ACL request MUST NOT include a DAV:invert
   element.  This precondition applies only when the ACL semantics of
   the resource includes the DAV:no-invert constraint (defined in
   Section 5.6.2).

   (DAV:no-abstract): The ACL request MUST NOT attempt to grant or deny
   an abstract privilege (see Section 5.3).

   (DAV:not-supported-privilege): The ACEs submitted in the ACL request
   MUST be supported by the resource.

   (DAV:missing-required-principal): The result of the ACL request MUST
   have at least one ACE for each principal identified in a
   DAV:required-principal XML element in the ACL semantics of that
   resource (see Section 5.5).

   (DAV:recognized-principal): Every principal URL in the ACL request
   MUST identify a principal resource.

   (DAV:allowed-principal): The principals specified in the ACEs
   submitted in the ACL request MUST be allowed as principals for the
   resource.  For example, a server where only authenticated principals
   can access resources would not allow the DAV:all or
   DAV:unauthenticated principals to be used in an ACE, since these
   would allow unauthenticated access to resources.
*/

$position = 0;
$xmltree = BuildXMLTree( $request->xml_tags, $position);
$aces = $xmltree->GetPath("/DAV::acl/*");

$grantor = new DAVResource($request->path);
if ( ! $grantor->Exists() ) $request->DoResponse( 404 );
if ( ! $grantor->IsCollection() )
  $request->PreconditionFailed(403,'not-supported-privilege','ACLs are only supported on Principals or Collections');

$grantor->NeedPrivilege('write-acl');

$cache_delete_list = array();

$qry = new AwlQuery('BEGIN');
$qry->Exec('ACL',__LINE__,__FILE__);

function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
  global $cache_delete_list, $request;

  $elements = $ace->GetContent();
  $principal_node = $elements[0];
  $grant = $elements[1];
  if ( $principal_node->GetNSTag() != 'DAV::principal' ) $request->MalformedRequest('ACL request must contain a principal, not '.$principal->GetNSTag());
  $grant_tag = $grant->GetNSTag();
  if ( $grant_tag == 'DAV::deny' )   $request->PreconditionFailed(403,'grant-only');
  if ( $grant_tag == 'DAV::invert' ) $request->PreconditionFailed(403,'no-invert');
  if ( $grant->GetNSTag() != 'DAV::grant' ) $request->MalformedRequest('ACL request must contain a principal for each ACE');

  $privilege_names = array();
  $xml_privs = $grant->GetPath("/DAV::grant/DAV::privilege/*");
  foreach( $xml_privs AS $k => $priv ) {
    $privilege_names[] = $priv->GetNSTag();
  }
  $privileges = privilege_to_bits($privilege_names);

  $principal_content = $principal_node->GetContent();
  if ( count($principal_content) != 1 ) $request->MalformedRequest('ACL request must contain exactly one principal per ACE');
  $principal_content = $principal_content[0];
  switch( $principal_content->GetNSTag() ) {
    case 'DAV::property':
      $principal_property = $principal_content->GetContent();
      if ( $principal_property[0]->GetNSTag() != 'DAV::owner' ) $request->PreconditionFailed(403, 'recognized-principal' );
      if ( privilege_to_bits('all') != $privileges ) {
        $request->PreconditionFailed(403, 'no-protected-ace-conflict', 'Owner must always have all permissions' );
      }
      break;  // and then we ignore it, since it's protected

    case 'DAV::unauthenticated':
      $request->PreconditionFailed(403, 'allowed-principal', 'May not set privileges for unauthenticated users' );
      break;

    case 'DAV::href':
      $principal_type = 'href';
      $grantee = new DAVResource( DeconstructURL($principal_content->GetContent()) );
      $grantee_id = $grantee->getProperty('principal_id');
      if ( !$grantee->Exists() || !$grantee->IsPrincipal() )
        $request->PreconditionFailed(403,'recognized-principal', 'Principal "' + $principal_content->GetContent() + '" not found.');
      $sqlparms = array( ':to_principal' => $grantee_id);
      $where = 'WHERE to_principal=:to_principal AND ';
      if ( isset($by_principal) ) {
        $sqlparms[':by_principal'] = $by_principal;
        $where .= 'by_principal = :by_principal';
      }
      else {
        $sqlparms[':by_collection'] = $by_collection;
        $where .= 'by_collection = :by_collection';
      }
      $qry = new AwlQuery('SELECT privileges FROM grants '.$where, $sqlparms);
      if ( $qry->Exec('ACL',__LINE__,__FILE__) && $qry->rows() == 1 && $current = $qry->Fetch() ) {
        $sql = 'UPDATE grants SET privileges=:privileges::INT::BIT(24) '.$where;
      }
      else {
        $sqlparms[':by_principal'] = $by_principal;
        $sqlparms[':by_collection'] = $by_collection;
        $sql = 'INSERT INTO grants (by_principal, by_collection, to_principal, privileges) VALUES(:by_principal, :by_collection, :to_principal, :privileges::INT::BIT(24))';
      }
      $sqlparms[':privileges'] = $privileges;
      $qry = new AwlQuery($sql, $sqlparms);
      if ( $qry->Exec('ACL',__LINE__,__FILE__) ) {
        Principal::cacheDelete('dav_name',$grantee->dav_name());
        Principal::cacheFlush('principal_id IN (SELECT member_id FROM group_member WHERE group_id = ?)', array($grantee_id));
      }
      break;

    case 'DAV::authenticated':
      $principal_type = 'authenticated';
      $grant_default_privs = $grantor->GetProperty('default_privileges');
      if ( isset($grant_default_privs) && bindec($grant_default_privs) == $privileges ) break; // There is no change, so skip it
      $sqlparms = array( ':privileges' => $privileges );
      if ( isset($by_collection) ) {
        $sql = 'UPDATE collection SET default_privileges=:privileges::INT::BIT(24) WHERE collection_id=:by_collection';
        $sqlparms[':by_collection'] = $by_collection;
      }
      else {
        $sql = 'UPDATE principal SET default_privileges=:privileges::INT::BIT(24) WHERE principal_id=:by_principal';
        $sqlparms[':by_principal'] = $by_principal;
      }
      $qry = new AwlQuery($sql, $sqlparms);
      if ( $qry->Exec('ACL',__LINE__,__FILE__) ) {
        /**
         *  Basically this has changed everyone's permissions now, so...
         */
        Principal::cacheFlush('TRUE');
      }
      break;

    case 'DAV::all':
//      $principal_type = 'all';
      $request->PreconditionFailed(403, 'allowed-principal', 'May not set privileges for unauthenticated users' );
      break;

    default:
      $request->PreconditionFailed(403, 'recognized-principal' );
      break;
  }

}

$by_principal  = ($grantor->IsPrincipal() ? $grantor->GetProperty('principal_id') : null);
$by_collection = ($grantor->IsPrincipal() ? null : $grantor->GetProperty('collection_id'));

foreach( $aces AS $k => $ace ) {
  process_ace($grantor, $by_principal, $by_collection, $ace);
}

$qry = new AwlQuery('COMMIT');
$qry->Exec('ACL',__LINE__,__FILE__);


$request->DoResponse( 200 );
