diff --git a/ChangeLog b/ChangeLog index f9765ad3..59bf442c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ 2025-01-22 Andrew Ruthven * Using a Ticket ID requires public.php +2024-12-13 Andrew Ruthven + * Fix iPhone's accessing other principal's collections. + 2024-04-15 Andrew Ruthven * Add caching of user credential success/failure diff --git a/inc/DAVResource.php b/inc/DAVResource.php index f15bfc6f..dcb45078 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -363,13 +363,19 @@ class DAVResource $this->FetchCollection(); if ( $this->_is_collection ) { - if ( $this->_is_principal || $this->collection->type == 'principal' ) $this->FetchPrincipal(); + if ( $this->_is_principal || $this->collection->type == 'principal' ) + $this->FetchPrincipal(); } else { $this->FetchResource(); } dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.', - $this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') ); + $this->dav_name, + ($this->_is_collection ? ' ' . $this->resourcetypes + : ' not'), + ($this->_is_principal ? ' and a principal' + : '') + ); } @@ -569,29 +575,34 @@ EOSQL; $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name); $cache_key = 'dav_resource'.$session->user_no; $this->collection = $cache->get( $cache_ns, $cache_key ); + if ( $this->collection === false ) { $this->ReadCollectionFromDatabase(); + if ( $this->collection->type != 'principal' && $this->_collection_is_cacheable ) { $cache_ns = 'collection-'.$this->collection->dav_name; @dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type ); $cache->set( $cache_ns, $cache_key, $this->collection ); } + @dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type ); } else { - @dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type ); - if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches) - || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) { + @dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". dav_name: "%s", Type: %s', $cache_ns, $cache_key, $this->collection->dav_name, $this->collection->type ); + + if ($this->collection->type == 'principal') { $this->_is_principal = true; $this->FetchPrincipal(); $this->collection->is_principal = true; - $this->collection->type = 'principal'; - } else { - if ($this->collection->type == 'proxy') { - $this->_is_proxy_resource = true; - $this->proxy_type = $this->collection->proxy_type; - } + + } else if ($this->collection->type == 'calendar') { + $this->_is_calendar = true; + + } else if ($this->collection->type == 'proxy') { + $this->_is_proxy_resource = true; + $this->proxy_type = $this->collection->proxy_type; } + @dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type ); } @@ -1881,8 +1892,15 @@ EOQRY; $prop->NewElement( 'principal-collection-set', $reply->href( ConstructURL('/') ) ); break; + # iPhone devices incorrectly implement DAV:current-user-principal from + # RFC 5397. They assume that current-user-principal is the href for the + # resource being queried. The RFC says it should be the current resource. + # See: https://gitlab.com/davical-project/davical/-/issues/335 case 'DAV::current-user-principal': - $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) ); + if ( preg_match('/iPhone/', $_SERVER['HTTP_USER_AGENT']) ) + $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($request->principal->url())) ) ); + else + $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) ); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ diff --git a/inc/caldav-ACL.php b/inc/caldav-ACL.php index cb0fa213..93b90c66 100644 --- a/inc/caldav-ACL.php +++ b/inc/caldav-ACL.php @@ -125,6 +125,9 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) { $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]; + + dbg_error_log( 'ACE', 'NSTag: "%s", by_collection: %s, by_principal: %s', $principal_content->GetNSTag(), $by_collection ?? 'Null', $by_principal ?? 'Null'); + switch( $principal_content->GetNSTag() ) { case 'DAV::property': $principal_property = $principal_content->GetContent(); @@ -142,8 +145,10 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) { $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) ) { @@ -154,6 +159,7 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) { $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; @@ -169,6 +175,15 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) { Principal::cacheDelete('dav_name',$grantee->dav_name()); Principal::cacheFlush('principal_id IN (SELECT member_id FROM group_member WHERE group_id = ?)', array($grantee_id)); } + + /** + * Basically this has changed everyone's permissions now, so... + */ + $cache = getCacheInstance(); + $cache->flush(); + + #Principal::cacheFlush('TRUE'); + break; case 'DAV::authenticated': diff --git a/inc/caldav-client-v2.php b/inc/caldav-client-v2.php index d280eb09..8488b418 100644 --- a/inc/caldav-client-v2.php +++ b/inc/caldav-client-v2.php @@ -70,7 +70,7 @@ class CalDAVClient { protected $calendar_urls; /** - * The useragent which is send to the caldav server + * The useragent which is sent to the caldav server * * @var string */ diff --git a/inc/caldav-client.php b/inc/caldav-client.php index b32c5c91..31e1ec03 100644 --- a/inc/caldav-client.php +++ b/inc/caldav-client.php @@ -26,7 +26,7 @@ class CalDAVClient { var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port; /** - * The useragent which is send to the caldav server + * The useragent which is sent to the caldav server * * @var string */ diff --git a/testing/tests/regression-suite/2800-iPhone-shared-addresses.result b/testing/tests/regression-suite/2800-iPhone-shared-addresses.result new file mode 100644 index 00000000..46bb27ea --- /dev/null +++ b/testing/tests/regression-suite/2800-iPhone-shared-addresses.result @@ -0,0 +1,15 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule +DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy +Content-Length: 0 +Content-Type: text/plain; charset="utf-8" + + +SQL Query 1 Result: + by_collection: >1609< + by_principal: >NULL< + displayname: >User 4< + privileges: >000000000000000000100001< + to_principal: >1005< + diff --git a/testing/tests/regression-suite/2800-iPhone-shared-addresses.test b/testing/tests/regression-suite/2800-iPhone-shared-addresses.test new file mode 100644 index 00000000..f6140ffb --- /dev/null +++ b/testing/tests/regression-suite/2800-iPhone-shared-addresses.test @@ -0,0 +1,47 @@ +# Test for iPhone devices which incorrectly implement +# DAV:current-user-principal from RFC 5397. They assume that +# current-user-principal is the href for the resource being queried. The +# RFC says it should be the current resource. #Sigh. +# +# See: https://gitlab.com/davical-project/davical/-/issues/335 +# +# Ensure that user4 can access user1's address book. +TYPE=ACL +HEADER=User-Agent: RFC3744 Spec Tests +HEADER=Content-Type: text/xml; charset="UTF-8" +HEAD + +BEGINDATA + + + + + /caldav.php/user4/ + + + + + + + + + + + + + +ENDDATA + +URL=http://regression.host/caldav.php/user1/ + +# This is by_collection, and by_principal isn't set, shouldn't it be set? +# WHERE p_by.dav_name = '/user1/' +QUERY +SELECT by_principal, by_collection, privileges, p_to.displayname, to_principal + FROM grants JOIN dav_principal p_to ON (to_principal=principal_id) + LEFT JOIN collection ON (by_collection=collection.collection_id) + LEFT JOIN dav_principal p_by ON (by_principal=p_by.principal_id) + WHERE by_collection = 1609 + AND p_to.dav_name = '/user4/' + ORDER BY by_principal, to_principal +ENDQUERY diff --git a/testing/tests/regression-suite/2801-iPhone-shared-addresses.result b/testing/tests/regression-suite/2801-iPhone-shared-addresses.result new file mode 100644 index 00000000..be6afe76 --- /dev/null +++ b/testing/tests/regression-suite/2801-iPhone-shared-addresses.result @@ -0,0 +1,50 @@ +HTTP/1.1 207 Multi-Status +Date: Dow, 01 Jan 2000 00:00:00 GMT +Content-Location: /caldav.php/user1/addresses/ +DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule +DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy +ETag: "43765875e20eef2d841725645b2f3c95" +Content-Length: 1129 +Content-Type: text/xml; charset="utf-8" + + + + + /caldav.php/user1/addresses/ + + + httpd/unix-directory + + + + + user1 addresses + Sun, 15 Mar 1998 12:00:00 GMT + 19570725T120000Z + + + + + + + + + + + + + /caldav.php/user1/ + + + /caldav.php/user1/ + + 6550000 + + + + + HTTP/1.1 200 OK + + + + diff --git a/testing/tests/regression-suite/2801-iPhone-shared-addresses.test b/testing/tests/regression-suite/2801-iPhone-shared-addresses.test new file mode 100644 index 00000000..2e778c8e --- /dev/null +++ b/testing/tests/regression-suite/2801-iPhone-shared-addresses.test @@ -0,0 +1,16 @@ +# Test for iPhone devices which incorrectly implement +# DAV:current-user-principal from RFC 5397. They assume that +# current-user-principal is the href for the resource being queried. The +# RFC says it should be the current resource. #Sigh. +# +# See: https://gitlab.com/davical-project/davical/-/issues/335 +# +# Ensure that user4 has user1 as the current-user-principal as we're an +# 'iPhone'. +TYPE=PROPFIND +AUTH=user4:user4 +HEADER=Content-Type: text/xml; charset="UTF-8" +HEADER=User-Agent: DAVKit/4.0 (728.3); iCalendar/1 (34); iPhone/3.0 +HEAD + +URL=http://regression.host/caldav.php/user1/addresses diff --git a/testing/tests/regression-suite/2802-correct-shared-addresses.result b/testing/tests/regression-suite/2802-correct-shared-addresses.result new file mode 100644 index 00000000..15aaaaf8 --- /dev/null +++ b/testing/tests/regression-suite/2802-correct-shared-addresses.result @@ -0,0 +1,50 @@ +HTTP/1.1 207 Multi-Status +Date: Dow, 01 Jan 2000 00:00:00 GMT +Content-Location: /caldav.php/user1/addresses/ +DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule +DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy +ETag: "3f9506c10fe5b434f966d4c82f026c40" +Content-Length: 1129 +Content-Type: text/xml; charset="utf-8" + + + + + /caldav.php/user1/addresses/ + + + httpd/unix-directory + + + + + user1 addresses + Sun, 15 Mar 1998 12:00:00 GMT + 19570725T120000Z + + + + + + + + + + + + + /caldav.php/user1/ + + + /caldav.php/user4/ + + 6550000 + + + + + HTTP/1.1 200 OK + + + + diff --git a/testing/tests/regression-suite/2802-correct-shared-addresses.test b/testing/tests/regression-suite/2802-correct-shared-addresses.test new file mode 100644 index 00000000..419d9630 --- /dev/null +++ b/testing/tests/regression-suite/2802-correct-shared-addresses.test @@ -0,0 +1,16 @@ +# Test for iPhone devices which incorrectly implement +# DAV:current-user-principal from RFC 5397. They assume that +# current-user-principal is the href for the resource being queried. The +# RFC says it should be the current resource. #Sigh. +# +# See: https://gitlab.com/davical-project/davical/-/issues/335 +# +# Ensure that user4 has user4 as the current-user-principal as we're not an +# 'iPhone'. +TYPE=PROPFIND +AUTH=user4:user4 +HEADER=Content-Type: text/xml; charset="UTF-8" +HEADER=User-Agent: Evolution/1.8.1 +HEAD + +URL=http://regression.host/caldav.php/user1/addresses