14b35ec47068382295a2361dd11b814dccc4733e
[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                 $this->principalGroups[$ace['principal']] = $result['{DAV:}group-member-set']->getPrincipals();
171             }
172         }
173     }
174     
175     public function clearCurrentUserData()
176     {
177         $this->currentUserPrincipal = '';
178         $this->calendarHomeSet = '';
179     }
180     
181     /**
182      * perform calDavRequest
183      * 
184      * @param string $method
185      * @param string $uri
186      * @param strubg $body
187      * @param number $depth
188      * @param number $tries
189      * @param number $sleep
190      * @throws Tinebase_Exception
191      */
192     public function calDavRequest($method, $uri, $body, $depth = 0, $tries = 10, $sleep = 30)
193     {
194         $response = null;
195         while ($tries > 0)
196         {
197             try {
198                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
199                         . ' Sending ' . $method . ' request ...');
200                 $response = $this->request($method, $uri, $body, array(
201                     'Depth' => $depth,
202                     'Content-Type' => 'text/xml',
203                 ));
204             } catch (Exception $e) {
205                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
206                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
207                             . ' Caldav request failed: '
208                             . '(' . $this->userName . ')' . $method . ' ' . $uri . "\n" . $body
209                             . "\n" . $e->getMessage());
210                 if (--$tries > 0) {
211                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
212                             . ' Sleeping ' . $sleep . ' seconds and retrying ... ');
213                     sleep($sleep);
214                 }
215                 continue;
216             }
217             break;
218         }
219         
220         if (! $response) {
221             throw new Tinebase_Exception("no response");
222         }
223         
224         $result = $this->parseMultiStatus($response['body']);
225         
226         //fputs($this->requestLogFH, $method.' '.$uri."\n".$body."\n".$depth."\n".$response['body']."\n\n\n\n\n\n\n", 10000000);
227         
228         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
229                 . ' request: ' . $body . ' response: ' . print_r($response, true));
230         
231         // If depth was 0, we only return the top item
232         if ($depth===0) {
233             reset($result);
234             $result = current($result);
235             return isset($result[200])?$result[200]:array();
236         }
237         
238         $newResult = array();
239         foreach($result as $href => $statusList)
240         {
241             $newResult[$href] = isset($statusList[200])?$statusList[200]:array();
242         }
243         
244         return $newResult;
245     }
246 }