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