caldav update: checks if record belongs to other application
[tine20] / tine20 / Tinebase / Import / CalDav / Client.php
1 <?php
2
3 /**
4  * Tine 2.0
5  * 
6  * @package     Tinebase
7  * @subpackage  Import
8  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
9  * @author      Paul Mehrer <p.mehrer@metaways.de>
10  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
11  */
12
13 /**
14  * Tinebase_Import_CalDav
15  * 
16  * @package     Tinebase
17  * @subpackage  Import
18  * 
19  */
20 class Tinebase_Import_CalDav_Client extends \Sabre\DAV\Client
21 {
22     /**
23      * used to overwrite default retry behavior (if != null)
24      * 
25      * @var integer
26      */
27     protected $_requestTries = null;
28     
29     protected $currentUserPrincipal = '';
30     protected $calendarHomeSet = '';
31     protected $principals = array();
32     protected $principalGroups = array();
33     
34     protected $requestLogFH;
35     
36     const findCurrentUserPrincipalRequest = 
37 '<?xml version="1.0"?>
38 <d:propfind xmlns:d="DAV:">
39   <d:prop>
40     <d:current-user-principal />
41   </d:prop>
42 </d:propfind>';
43
44     const findCalendarHomeSetRequest =
45 '<?xml version="1.0"?>
46 <d:propfind xmlns:d="DAV:">
47   <d:prop>
48     <x:calendar-home-set xmlns:x="urn:ietf:params:xml:ns:caldav"/>
49   </d:prop>
50 </d:propfind>';
51     
52     const resolvePrincipalRequest =
53 '<?xml version="1.0"?>
54 <d:propfind xmlns:d="DAV:">
55   <d:prop>
56     <d:group-member-set />
57     <d:displayname />
58   </d:prop>
59 </d:propfind>';
60     
61     public function __construct(array $a)
62     {
63         parent::__construct($a);
64         
65         //$this->requestLogFH = fopen('/var/log/tine20/requestLog', 'w');
66         
67         $this->propertyMap['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] = 'Sabre\CalDAV\Property\SupportedCalendarComponentSet';
68         $this->propertyMap['{DAV:}acl'] = 'Sabre\DAVACL\Property\Acl';
69         $this->propertyMap['{DAV:}group-member-set'] = 'Tinebase_Import_CalDav_GroupMemberSet';
70     }
71     
72     /**
73      * findCurrentUserPrincipal
74      * - result ($this->currentUserPrincipal) is cached for 1 week
75      * 
76      * @param number $tries
77      * @return boolean
78      */
79     public function findCurrentUserPrincipal($tries = 1)
80     {
81         $cacheId = convertCacheId('findCurrentUserPrincipal' . $this->userName);
82         if (Tinebase_Core::getCache()->test($cacheId)) {
83             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
84                     . ' Loading user principal from cache');
85             
86             $this->currentUserPrincipal = Tinebase_Core::getCache()->load($cacheId);
87             $user = $this->_setUser();
88             if (! $user) {
89                 return false;
90             }
91             
92             return true;
93         }
94         
95         $result = $this->calDavRequest('PROPFIND', '/principals/', self::findCurrentUserPrincipalRequest, 0, $tries);
96         if (isset($result['{DAV:}current-user-principal']))
97         {
98             $this->currentUserPrincipal = $result['{DAV:}current-user-principal'];
99             $user = $this->_setUser();
100             if (! $user) {
101                 return false;
102             }
103             
104             Tinebase_Core::getCache()->save($this->currentUserPrincipal, $cacheId, array(), /* 1 week */ 24*3600*7);
105             return true;
106         }
107         
108         Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' couldn\'t find current users principal');
109         return false;
110     }
111     
112     protected function _setUser()
113     {
114         try {
115             $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountLoginName', $this->userName, 'Tinebase_Model_FullUser');
116             Tinebase_Core::set(Tinebase_Core::USER, $user);
117             $credentialCache = Tinebase_Auth_CredentialCache::getInstance()->cacheCredentials($this->userName, $this->password);
118             Tinebase_Core::set(Tinebase_Core::USERCREDENTIALCACHE, $credentialCache);
119         } catch (Tinebase_Exception_NotFound $e) {
120             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' can\'t find tine20 user: ' . $this->userName);
121             return null;
122         }
123         
124         $this->principals[$this->currentUserPrincipal] = $user;
125         
126         return $user;
127     }
128     
129     public function findCurrentUserPrincipalForUsers(array &$users)
130     {
131         foreach ($users as $username => $pwd) {
132             $this->userName = $username;
133             $this->password = $pwd;
134             
135             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
136                 . ' Find principal for user ' . $this->userName);
137             try {
138                 if (! $this->findCurrentUserPrincipal()) {
139                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
140                         . ' Skipping ' . $username);
141                     unset($users[$username]);
142                 }
143             } catch (Tinebase_Exception $te) {
144                 // TODO should use better exception (Not_Authenticatied, ...)
145                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
146                         . ' Skipping ' . $username);
147                 unset($users[$username]);
148             }
149         }
150         return count($users) > 0;
151     }
152     
153     /**
154      * findCalendarHomeSet
155      * - result ($this->calendarHomeSet) is cached for 1 week
156      * 
157      * @return boolean
158      */
159     public function findCalendarHomeSet()
160     {
161         if ('' == $this->currentUserPrincipal && ! $this->findCurrentUserPrincipal(/* tries = */ 3)) {
162             return false;
163         }
164         $cacheId = convertCacheId('findCalendarHomeSet' . $this->userName);
165         if (Tinebase_Core::getCache()->test($cacheId)) {
166             $this->calendarHomeSet = Tinebase_Core::getCache()->load($cacheId);
167             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
168                     . ' Loading user home set from cache');
169             return true;
170         }
171         
172         $result = $this->calDavRequest('PROPFIND', $this->currentUserPrincipal, self::findCalendarHomeSetRequest);
173         
174         if (isset($result['{urn:ietf:params:xml:ns:caldav}calendar-home-set'])) {
175             $this->calendarHomeSet = $result['{urn:ietf:params:xml:ns:caldav}calendar-home-set'];
176             Tinebase_Core::getCache()->save($this->calendarHomeSet, $cacheId, array(), /* 1 week */ 24*3600*7);
177             return true;
178         }
179         
180         Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' couldn\'t find calendar homeset');
181         return false;
182     }
183     
184     /**
185      * resolve principals
186      * 
187      * @param array $privileges
188      */
189     public function resolvePrincipals(array $privileges)
190     {
191         foreach ($privileges as $ace)
192         {
193             if ( $ace['principal'] == '{DAV:}authenticated' || $ace['principal'] == $this->currentUserPrincipal ||
194                  isset($this->principals[$ace['principal']]) || isset($this->principalGroups[$ace['principal']])) {
195                      continue;
196             }
197             
198             $result = $this->calDavRequest('PROPFIND', $ace['principal'], self::resolvePrincipalRequest);
199             if (isset($result['{DAV:}group-member-set'])) {
200                 $principals = $result['{DAV:}group-member-set']->getPrincipals();
201                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
202                         . ' ' . print_r($principals, true));
203                 $this->principalGroups[$ace['principal']] = $result['{DAV:}group-member-set']->getPrincipals();
204             }
205         }
206     }
207     
208     public function clearCurrentUserData()
209     {
210         $this->currentUserPrincipal = '';
211         $this->calendarHomeSet = '';
212     }
213     
214     /**
215      * perform calDavRequest
216      * 
217      * @param string $method
218      * @param string $uri
219      * @param strubg $body
220      * @param number $depth
221      * @param number $tries
222      * @param number $sleep
223      * @throws Tinebase_Exception
224      */
225     public function calDavRequest($method, $uri, $body, $depth = 0, $tries = 10, $sleep = 30)
226     {
227         $response = null;
228         if ($this->_requestTries !== null) {
229             // overwrite default retry behavior
230             $tries = $this->_requestTries;
231         }
232         while ($tries > 0)
233         {
234             try {
235                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
236                         . ' Sending ' . $method . ' request for uri ' . $uri . ' ...');
237                 $response = $this->request($method, $uri, $body, array(
238                     'Depth' => $depth,
239                     'Content-Type' => 'text/xml',
240                 ));
241             } catch (Exception $e) {
242                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
243                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
244                             . ' Caldav request failed: '
245                             . '(' . $this->userName . ')' . $method . ' ' . $uri . "\n" . $body
246                             . "\n" . $e->getMessage());
247                 if (--$tries > 0) {
248                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
249                             . ' Sleeping ' . $sleep . ' seconds and retrying ... ');
250                     sleep($sleep);
251                 }
252                 continue;
253             }
254             break;
255         }
256         
257         if (! $response) {
258             throw new Tinebase_Exception("no response");
259         }
260         
261         $result = $this->parseMultiStatus($response['body']);
262         
263         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
264                 . ' Uri: ' . $uri . ' | request: ' . $body . ' | response: ' . print_r($response, true));
265         
266         // If depth was 0, we only return the top item
267         if ($depth===0) {
268             reset($result);
269             $result = current($result);
270             $result = isset($result[200])?$result[200]:array();
271             
272             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
273                     . ' Result (depth 0): ' . var_export($result, true));
274             
275             return $result;
276         }
277         
278         $newResult = array();
279         foreach($result as $href => $statusList)
280         {
281             $newResult[$href] = isset($statusList[200])?$statusList[200]:array();
282         }
283
284         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
285                 . ' Result: ' . var_export($newResult, true));
286         
287         return $newResult;
288     }
289 }