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