fix owncloud.log notices
[grauphel.git] / controller / apicontroller.php
1 <?php
2 /**
3  * Part of grauphel
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Grauphel
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/grauphel.htm
13  */
14 namespace OCA\Grauphel\Controller;
15
16 use \OCP\AppFramework\Controller;
17 use \OCP\AppFramework\Http\JSONResponse;
18
19 use \OCA\Grauphel\Lib\NoteStorage;
20 use \OCA\Grauphel\Lib\OAuth;
21 use \OCA\Grauphel\Lib\OAuthException;
22 use \OCA\Grauphel\Lib\Dependencies;
23 use \OCA\Grauphel\Lib\Response\ErrorResponse;
24
25 /**
26  * Tomboy's REST API
27  *
28  * @category  Tools
29  * @package   Grauphel
30  * @author    Christian Weiske <cweiske@cweiske.de>
31  * @copyright 2014 Christian Weiske
32  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
33  * @version   Release: @package_version@
34  * @link      http://cweiske.de/grauphel.htm
35  */
36 class ApiController extends Controller
37 {
38     /**
39      * constructor of the controller
40      *
41      * @param string   $appName Name of the app
42      * @param IRequest $request Instance of the request
43      */
44     public function __construct($appName, \OCP\IRequest $request, $user)
45     {
46         parent::__construct($appName, $request);
47         $this->user  = $user;
48         $this->deps  = Dependencies::get();
49         $this->notes = new NoteStorage($this->deps->urlGen);
50
51         //default http header: we assume something is broken
52         header('HTTP/1.0 500 Internal Server Error');
53     }
54
55     /**
56      * /api/1.0
57      *
58      * @NoAdminRequired
59      * @NoCSRFRequired
60      * @PublicPage
61      */
62     public function index($route = 'grauphel.api.index')
63     {
64         $deps = Dependencies::get();
65         $authenticated = false;
66         $oauth = new OAuth();
67         $oauth->setDeps($deps);
68         $urlGen = $deps->urlGen;
69
70         try {
71             $provider = OAuth::getProvider();
72             $oauth->registerHandler($provider)
73                 ->registerAccessTokenHandler($provider);
74             $provider->checkOAuthRequest(
75                 $urlGen->getAbsoluteURL(
76                     $urlGen->linkToRoute($route)
77                 )
78             );
79             $authenticated = true;
80             $token = $deps->tokens->load('access', $provider->token);
81             $username = $token->user;
82
83         } catch (OAuthException $e) {
84             return new ErrorResponse($e->getMessage());
85         } catch (\OAuthException $e) {
86             if ($e->getCode() != OAUTH_PARAMETER_ABSENT) {
87                 $oauth->error($e);
88             }
89             if ($this->user !== null) {
90                 $username = $this->user->getUID();
91                 $authenticated = true;
92             }
93         }
94
95         $data = array(
96             'oauth_request_token_url' => $urlGen->getAbsoluteURL(
97                 $urlGen->linkToRoute('grauphel.oauth.requestToken')
98             ),
99             'oauth_authorize_url'     => $urlGen->getAbsoluteURL(
100                 $urlGen->linkToRoute('grauphel.oauth.authorize')
101             ),
102             'oauth_access_token_url'  => $urlGen->getAbsoluteURL(
103                 $urlGen->linkToRoute('grauphel.oauth.accessToken')
104             ),
105             'api-version' => '1.0',
106         );
107
108         $client = $this->getClient();
109         if ($client !== false) {
110             $data['oauth_authorize_url'] .= '?client='
111                 . urlencode($this->getNiceClientName($client));
112         }
113
114         if ($authenticated) {
115             $data['user-ref'] = array(
116                 'api-ref' => $urlGen->getAbsoluteURL(
117                     $urlGen->linkToRoute(
118                         'grauphel.api.user', array('username' => $username)
119                     )
120                 ),
121                 'href' => null,//FIXME
122             );
123         }
124
125         return new JSONResponse($data);
126     }
127
128     /**
129      * /api/1.0/
130      *
131      * @NoAdminRequired
132      * @NoCSRFRequired
133      * @PublicPage
134      */
135     public function indexSlash()
136     {
137         return $this->index('grauphel.api.indexSlash');
138     }
139
140     /**
141      * GET /api/1.0/$user
142      *
143      * @NoAdminRequired
144      * @NoCSRFRequired
145      * @PublicPage
146      */
147     public function user($username)
148     {
149         $this->verifyUser(
150             $username,
151             $this->deps->urlGen->getAbsoluteURL(
152                 $this->deps->urlGen->linkToRoute(
153                     'grauphel.api.user', array('username' => $username)
154                 )
155             )
156         );
157         $syncdata = $this->notes->loadSyncData();
158
159         $data = array(
160             'user-name'  => $username,
161             'first-name' => null,
162             'last-name'  => null,
163             'notes-ref'  => array(
164                 'api-ref' => $this->deps->urlGen->getAbsoluteURL(
165                     $this->deps->urlGen->linkToRoute(
166                         'grauphel.api.notes', array('username' => $username)
167                     )
168                 ),
169                 'href'    => null,
170             ),
171             'latest-sync-revision' => $syncdata->latestSyncRevision,
172             'current-sync-guid'    => $syncdata->currentSyncGuid,
173         );
174         return new JSONResponse($data);
175     }
176
177     /**
178      * GET /api/1.0/$user/notes
179      *
180      * @NoAdminRequired
181      * @NoCSRFRequired
182      * @PublicPage
183      */
184     public function notes($username)
185     {
186         $this->verifyUser(
187             $username,
188             $this->deps->urlGen->getAbsoluteURL(
189                 $this->deps->urlGen->linkToRoute(
190                     'grauphel.api.notes', array('username' => $username)
191                 )
192             )
193         );
194         $syncdata = $this->notes->loadSyncData();
195         return $this->fetchNotes($syncdata);
196     }
197
198     /**
199      * PUT /api/1.0/$user/notes
200      *
201      * @NoAdminRequired
202      * @NoCSRFRequired
203      * @PublicPage
204      */
205     public function notesSave($username)
206     {
207         $this->verifyUser(
208             $username,
209             $this->deps->urlGen->getAbsoluteURL(
210                 $this->deps->urlGen->linkToRoute(
211                     'grauphel.api.notesSave', array('username' => $username)
212                 )
213             )
214         );
215         $syncdata = $this->notes->loadSyncData();
216         
217         $res = $this->handleNoteSave($username, $syncdata);
218         if ($res instanceof \OCP\AppFramework\Http\Response) {
219             return $res;
220         }
221
222         return $this->fetchNotes($syncdata);
223     }
224
225     protected function fetchNotes($syncdata)
226     {
227         $since = null;
228         if (isset($_GET['since'])) {
229             $since = (int) $_GET['since'];
230         }
231
232         if (isset($_GET['include_notes']) && $_GET['include_notes']) {
233             $notes = $this->notes->loadNotesFull($since);
234         } else {
235             $notes = $this->notes->loadNotesOverview($since);
236         }
237
238         //work around bug https://bugzilla.gnome.org/show_bug.cgi?id=734313
239         foreach ($notes as $note) {
240             if (isset($note->{'note-content-version'})) {
241                 $note->{'note-content-version'} = 0.3;
242             }
243         }
244
245         $data = array(
246             'latest-sync-revision' => $syncdata->latestSyncRevision,
247             'notes' => $notes,
248         );
249         return new JSONResponse($data);
250     }
251
252     protected function handleNoteSave($username, $syncdata)
253     {
254         if ($_SERVER['REQUEST_METHOD'] != 'PUT') {
255             return;
256         }
257
258         //note that we have more data in $arPut than just our JSON
259         // request object merges it with other data
260         $arPut = $this->request->put;
261
262         //structural validation
263         if (!isset($arPut['latest-sync-revision'])) {
264             return new ErrorResponse('Missing "latest-sync-revision"');
265         }
266         if (!isset($arPut['note-changes'])) {
267             return new ErrorResponse('Missing "note-changes"');
268         }
269         foreach ($arPut['note-changes'] as $note) {
270             //owncloud converts object to array, so we reverse
271             $note = (object) $note;
272             if (!isset($note->guid) || $note->guid == '') {
273                 return new ErrorResponse('Missing "guid" on note');
274             }
275         }
276
277         //content validation
278         if ($arPut['latest-sync-revision'] != $syncdata->latestSyncRevision +1
279             && $syncdata->latestSyncRevision != -1
280         ) {
281             return new ErrorResponse(
282                 'Wrong "latest-sync-revision". You are not up to date.'
283             );
284         }
285
286         //update
287         ++$syncdata->latestSyncRevision;
288         foreach ($arPut['note-changes'] as $noteUpdate) {
289             //owncloud converts object to array, so we reverse
290             $noteUpdate = (object) $noteUpdate;
291
292             $note = $this->notes->load($noteUpdate->guid);
293             if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
294                 $this->notes->delete($noteUpdate->guid);
295             } else {
296                 $this->notes->update(
297                     $note, $noteUpdate, $syncdata->latestSyncRevision
298                 );
299                 $this->notes->save($note);
300             }
301         }
302
303         $this->notes->saveSyncData($syncdata);
304     }
305
306     /**
307      * GET /api/1.0/$user/notes/$noteguid
308      *
309      * @NoAdminRequired
310      * @NoCSRFRequired
311      * @PublicPage
312      */
313     public function note($username, $guid)
314     {
315         $this->verifyUser(
316             $username,
317             $this->deps->urlGen->getAbsoluteURL(
318                 $this->deps->urlGen->linkToRoute(
319                     'grauphel.api.note',
320                     array('username' => $username, 'guid' => $guid)
321                 )
322             )
323         );
324
325         $note = $this->notes->load($guid, false);
326         if ($note === null) {
327             header('HTTP/1.0 404 Not Found');
328             header('Content-type: text/plain');
329             echo "Note does not exist\n";
330             exit(1);
331         }
332
333         return new JSONResponse($note);
334     }
335
336     protected function getClient()
337     {
338         if (isset($_SERVER['HTTP_X_TOMBOY_CLIENT'])) {
339             $client = $_SERVER['HTTP_X_TOMBOY_CLIENT'];
340             $doublepos = strpos($client, ', org.tomdroid');
341             if ($doublepos !== false) {
342                 //https://bugs.launchpad.net/tomdroid/+bug/1375436
343                 //X-Tomboy-Client header is sent twice
344                 $client = substr($client, 0, $doublepos);
345             }
346             return $client;
347         }
348
349         return false;
350     }
351
352     protected function getNiceClientName($client)
353     {
354         if (substr($client, 0, 12) == 'org.tomdroid') {
355             //org.tomdroid v0.7.5, build 14, Android v4.4.2, innotek GmbH/VirtualBox
356             return 'Tomdroid';
357         }
358         return $client;
359     }
360
361     /**
362      * Checks if the given user is authorized (by oauth token or normal login)
363      *
364      * @param string $username Username to verify
365      *
366      * @return boolean True if all is fine, Response in case of an error
367      */
368     protected function verifyUser($username, $curUrl)
369     {
370         if ($this->user !== null && $this->user->getUid() == $username) {
371             $this->notes->setUsername($username);
372             return true;
373         }
374
375         $oauth = new OAuth();
376         $oauth->setDeps($this->deps);
377         $oauth->verifyOAuthUser($username, $curUrl);
378
379         $this->notes->setUsername($username);
380         return true;
381     }
382 }
383 ?>