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