Add token management page (only shows tokens for now)
[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         if ($authenticated) {
109             $data['user-ref'] = array(
110                 'api-ref' => $urlGen->getAbsoluteURL(
111                     $urlGen->linkToRoute(
112                         'grauphel.api.user', array('username' => $username)
113                     )
114                 ),
115                 'href' => null,//FIXME
116             );
117         }
118
119         return new JSONResponse($data);
120     }
121
122     /**
123      * /api/1.0/
124      *
125      * @NoAdminRequired
126      * @NoCSRFRequired
127      * @PublicPage
128      */
129     public function indexSlash()
130     {
131         return $this->index('grauphel.api.indexSlash');
132     }
133
134     /**
135      * GET /api/1.0/$user
136      *
137      * @NoAdminRequired
138      * @NoCSRFRequired
139      * @PublicPage
140      */
141     public function user($username)
142     {
143         $this->verifyUser(
144             $username,
145             $this->deps->urlGen->getAbsoluteURL(
146                 $this->deps->urlGen->linkToRoute(
147                     'grauphel.api.user', array('username' => $username)
148                 )
149             )
150         );
151         $syncdata = $this->notes->loadSyncData();
152
153         $data = array(
154             'user-name'  => $username,
155             'first-name' => null,
156             'last-name'  => null,
157             'notes-ref'  => array(
158                 'api-ref' => $this->deps->urlGen->getAbsoluteURL(
159                     $this->deps->urlGen->linkToRoute(
160                         'grauphel.api.notes', array('username' => $username)
161                     )
162                 ),
163                 'href'    => null,
164             ),
165             'latest-sync-revision' => $syncdata->latestSyncRevision,
166             'current-sync-guid'    => $syncdata->currentSyncGuid,
167         );
168         return new JSONResponse($data);
169     }
170
171     /**
172      * GET /api/1.0/$user/notes
173      *
174      * @NoAdminRequired
175      * @NoCSRFRequired
176      * @PublicPage
177      */
178     public function notes($username)
179     {
180         $this->verifyUser(
181             $username,
182             $this->deps->urlGen->getAbsoluteURL(
183                 $this->deps->urlGen->linkToRoute(
184                     'grauphel.api.notes', array('username' => $username)
185                 )
186             )
187         );
188         $syncdata = $this->notes->loadSyncData();
189         return $this->fetchNotes($syncdata);
190     }
191
192     /**
193      * PUT /api/1.0/$user/notes
194      *
195      * @NoAdminRequired
196      * @NoCSRFRequired
197      * @PublicPage
198      */
199     public function notesSave($username)
200     {
201         $this->verifyUser(
202             $username,
203             $this->deps->urlGen->getAbsoluteURL(
204                 $this->deps->urlGen->linkToRoute(
205                     'grauphel.api.notesSave', array('username' => $username)
206                 )
207             )
208         );
209         $syncdata = $this->notes->loadSyncData();
210         
211         $res = $this->handleNoteSave($username, $syncdata);
212         if ($res instanceof \OCP\AppFramework\Http\Response) {
213             return $res;
214         }
215
216         return $this->fetchNotes($syncdata);
217     }
218
219     protected function fetchNotes($syncdata)
220     {
221         $since = null;
222         if (isset($_GET['since'])) {
223             $since = (int) $_GET['since'];
224         }
225
226         if (isset($_GET['include_notes']) && $_GET['include_notes']) {
227             $notes = $this->notes->loadNotesFull($since);
228         } else {
229             $notes = $this->notes->loadNotesOverview($since);
230         }
231
232         //work around bug https://bugzilla.gnome.org/show_bug.cgi?id=734313
233         foreach ($notes as $note) {
234             if (isset($note->{'note-content-version'})) {
235                 $note->{'note-content-version'} = 0.3;
236             }
237         }
238
239         $data = array(
240             'latest-sync-revision' => $syncdata->latestSyncRevision,
241             'notes' => $notes,
242         );
243         return new JSONResponse($data);
244     }
245
246     protected function handleNoteSave($username, $syncdata)
247     {
248         if ($_SERVER['REQUEST_METHOD'] != 'PUT') {
249             return;
250         }
251
252         //note that we have more data in $arPut than just our JSON
253         // request object merges it with other data
254         $arPut = $this->request->put;
255
256         //structural validation
257         if (!isset($arPut['latest-sync-revision'])) {
258             return new ErrorResponse('Missing "latest-sync-revision"');
259         }
260         if (!isset($arPut['note-changes'])) {
261             return new ErrorResponse('Missing "note-changes"');
262         }
263         foreach ($arPut['note-changes'] as $note) {
264             //owncloud converts object to array, so we reverse
265             $note = (object) $note;
266             if (!isset($note->guid) || $note->guid == '') {
267                 return new ErrorResponse('Missing "guid" on note');
268             }
269         }
270
271         //content validation
272         if ($arPut['latest-sync-revision'] != $syncdata->latestSyncRevision +1
273             && $syncdata->latestSyncRevision != -1
274         ) {
275             return new ErrorResponse(
276                 'Wrong "latest-sync-revision". You are not up to date.'
277             );
278         }
279
280         //update
281         ++$syncdata->latestSyncRevision;
282         foreach ($arPut['note-changes'] as $noteUpdate) {
283             //owncloud converts object to array, so we reverse
284             $noteUpdate = (object) $noteUpdate;
285
286             $note = $this->notes->load($noteUpdate->guid);
287             if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
288                 $this->notes->delete($noteUpdate->guid);
289             } else {
290                 $this->notes->update(
291                     $note, $noteUpdate, $syncdata->latestSyncRevision
292                 );
293                 $this->notes->save($note);
294             }
295         }
296
297         $this->notes->saveSyncData($syncdata);
298     }
299
300     /**
301      * GET /api/1.0/$user/notes/$noteguid
302      *
303      * @NoAdminRequired
304      * @NoCSRFRequired
305      * @PublicPage
306      */
307     public function note($username, $guid)
308     {
309         $this->verifyUser(
310             $username,
311             $this->deps->urlGen->getAbsoluteURL(
312                 $this->deps->urlGen->linkToRoute(
313                     'grauphel.api.note',
314                     array('username' => $username, 'guid' => $guid)
315                 )
316             )
317         );
318
319         $note = $this->notes->load($guid, false);
320         if ($note === null) {
321             header('HTTP/1.0 404 Not Found');
322             header('Content-type: text/plain');
323             echo "Note does not exist\n";
324             exit(1);
325         }
326
327         return new JSONResponse($note);
328     }
329
330     /**
331      * Checks if the given user is authorized (by oauth token or normal login)
332      *
333      * @param string $username Username to verify
334      *
335      * @return boolean True if all is fine, Response in case of an error
336      */
337     protected function verifyUser($username, $curUrl)
338     {
339         if ($this->user !== null && $this->user->getUid() == $username) {
340             $this->notes->setUsername($username);
341             return true;
342         }
343
344         $oauth = new OAuth();
345         $oauth->setDeps($this->deps);
346         $oauth->verifyOAuthUser($username, $curUrl);
347
348         $this->notes->setUsername($username);
349         return true;
350     }
351 }
352 ?>