use Tine 2.0 groups for group principal calendar grants
[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             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
77                     . ' Loading user principal from cache');
78             
79             $this->currentUserPrincipal = Tinebase_Core::getCache()->load($cacheId);
80             $user = $this->_setUser();
81             if (! $user) {
82                 return false;
83             }
84             
85             return true;
86         }
87         
88         $result = $this->calDavRequest('PROPFIND', '/principals/', self::findCurrentUserPrincipalRequest, 0, $tries);
89         if (isset($result['{DAV:}current-user-principal']))
90         {
91             $this->currentUserPrincipal = $result['{DAV:}current-user-principal'];
92             $user = $this->_setUser();
93             if (! $user) {
94                 return false;
95             }
96             
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     protected function _setUser()
106     {
107         try {
108             $user = Tinebase_User::getInstance()->getUserByLoginName($this->userName, 'Tinebase_Model_FullUser');
109             Tinebase_Core::set(Tinebase_Core::USER, $user);
110             $credentialCache = Tinebase_Auth_CredentialCache::getInstance()->cacheCredentials($this->userName, $this->password);
111             Tinebase_Core::set(Tinebase_Core::USERCREDENTIALCACHE, $credentialCache);
112         } catch (Tinebase_Exception_NotFound $e) {
113             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' can\'t find tine20 user: ' . $this->userName);
114             return null;
115         }
116         
117         $this->principals[$this->currentUserPrincipal] = $user;
118         
119         return $user;
120     }
121     
122     public function findCurrentUserPrincipalForUsers(array &$users)
123     {
124         foreach ($users as $username => $pwd) {
125             $this->userName = $username;
126             $this->password = $pwd;
127             
128             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
129                 . ' Find principal for user ' . $this->userName);
130             try {
131                 if (! $this->findCurrentUserPrincipal()) {
132                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
133                         . ' Skipping ' . $username);
134                     unset($users[$username]);
135                 }
136             } catch (Tinebase_Exception $te) {
137                 // TODO should use better exception (Not_Authenticatied, ...)
138                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
139                         . ' Skipping ' . $username);
140                 unset($users[$username]);
141             }
142         }
143         return count($users) > 0;
144     }
145     
146     /**
147      * findCalendarHomeSet
148      * - result ($this->calendarHomeSet) is cached for 1 week
149      * 
150      * @return boolean
151      */
152     public function findCalendarHomeSet()
153     {
154         if ('' == $this->currentUserPrincipal && ! $this->findCurrentUserPrincipal(/* tries = */ 3)) {
155             return false;
156         }
157         $cacheId = convertCacheId('findCalendarHomeSet' . $this->userName);
158         if (Tinebase_Core::getCache()->test($cacheId)) {
159             $this->calendarHomeSet = Tinebase_Core::getCache()->load($cacheId);
160             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
161                     . ' Loading user home set from cache');
162             return true;
163         }
164         
165         $result = $this->calDavRequest('PROPFIND', $this->currentUserPrincipal, self::findCalendarHomeSetRequest);
166         
167         if (isset($result['{urn:ietf:params:xml:ns:caldav}calendar-home-set'])) {
168             $this->calendarHomeSet = $result['{urn:ietf:params:xml:ns:caldav}calendar-home-set'];
169             Tinebase_Core::getCache()->save($this->calendarHomeSet, $cacheId, array(), /* 1 week */ 24*3600*7);
170             return true;
171         }
172         
173         Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' couldn\'t find calendar homeset');
174         return false;
175     }
176     
177     /**
178      * resolve principals
179      * 
180      * @param array $privileges
181      */
182     public function resolvePrincipals(array $privileges)
183     {
184         foreach ($privileges as $ace)
185         {
186             if ( $ace['principal'] == '{DAV:}authenticated' || $ace['principal'] == $this->currentUserPrincipal ||
187                  isset($this->principals[$ace['principal']]) || isset($this->principalGroups[$ace['principal']])) {
188                      continue;
189             }
190             
191             $result = $this->calDavRequest('PROPFIND', $ace['principal'], self::resolvePrincipalRequest);
192             if (isset($result['{DAV:}group-member-set'])) {
193                 $principals = $result['{DAV:}group-member-set']->getPrincipals();
194                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
195                         . ' ' . print_r($principals, true));
196                 $this->principalGroups[$ace['principal']] = $result['{DAV:}group-member-set']->getPrincipals();
197             }
198         }
199     }
200     
201     public function clearCurrentUserData()
202     {
203         $this->currentUserPrincipal = '';
204         $this->calendarHomeSet = '';
205     }
206     
207     /**
208      * perform calDavRequest
209      * 
210      * @param string $method
211      * @param string $uri
212      * @param strubg $body
213      * @param number $depth
214      * @param number $tries
215      * @param number $sleep
216      * @throws Tinebase_Exception
217      */
218     public function calDavRequest($method, $uri, $body, $depth = 0, $tries = 10, $sleep = 30)
219     {
220         $response = null;
221         while ($tries > 0)
222         {
223             try {
224                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
225                         . ' Sending ' . $method . ' request ...');
226                 $response = $this->request($method, $uri, $body, array(
227                     'Depth' => $depth,
228                     'Content-Type' => 'text/xml',
229                 ));
230             } catch (Exception $e) {
231                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
232                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
233                             . ' Caldav request failed: '
234                             . '(' . $this->userName . ')' . $method . ' ' . $uri . "\n" . $body
235                             . "\n" . $e->getMessage());
236                 if (--$tries > 0) {
237                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
238                             . ' Sleeping ' . $sleep . ' seconds and retrying ... ');
239                     sleep($sleep);
240                 }
241                 continue;
242             }
243             break;
244         }
245         
246         if (! $response) {
247             throw new Tinebase_Exception("no response");
248         }
249         
250         $result = $this->parseMultiStatus($response['body']);
251         
252         //fputs($this->requestLogFH, $method.' '.$uri."\n".$body."\n".$depth."\n".$response['body']."\n\n\n\n\n\n\n", 10000000);
253         
254         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
255                 . ' request: ' . $body . ' response: ' . print_r($response, true));
256         
257         // If depth was 0, we only return the top item
258         if ($depth===0) {
259             reset($result);
260             $result = current($result);
261             return isset($result[200])?$result[200]:array();
262         }
263         
264         $newResult = array();
265         foreach($result as $href => $statusList)
266         {
267             $newResult[$href] = isset($statusList[200])?$statusList[200]:array();
268         }
269         
270         return $newResult;
271     }
272 }