summaryrefslogtreecommitdiff
path: root/modules/rest
diff options
context:
space:
mode:
authorTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
committerTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
commit406abd7c4df1ace2cd3e4e17159e8941a2e8c0c4 (patch)
treea324be16021f44f2fd6d55e609f47024e945b1db /modules/rest
Initial import
Diffstat (limited to 'modules/rest')
-rw-r--r--modules/rest/controllers/rest.php121
-rw-r--r--modules/rest/helpers/registry_rest.php30
-rw-r--r--modules/rest/helpers/rest.php191
-rw-r--r--modules/rest/helpers/rest_event.php102
-rw-r--r--modules/rest/helpers/rest_installer.php52
-rw-r--r--modules/rest/libraries/Rest_Exception.php37
-rw-r--r--modules/rest/models/user_access_key.php21
-rw-r--r--modules/rest/module.info8
-rw-r--r--modules/rest/views/error_rest.json.php6
-rw-r--r--modules/rest/views/reset_api_key_confirm.html.php7
-rw-r--r--modules/rest/views/user_profile_rest.html.php13
11 files changed, 588 insertions, 0 deletions
diff --git a/modules/rest/controllers/rest.php b/modules/rest/controllers/rest.php
new file mode 100644
index 0000000..b3d59e0
--- /dev/null
+++ b/modules/rest/controllers/rest.php
@@ -0,0 +1,121 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Rest_Controller extends Controller {
+ const ALLOW_PRIVATE_GALLERY = true;
+
+ public function index() {
+ $username = Input::instance()->post("user");
+ $password = Input::instance()->post("password");
+
+ if (empty($username) || auth::too_many_failures($username)) {
+ throw new Rest_Exception("Forbidden", 403);
+ }
+
+ $user = identity::lookup_user_by_name($username);
+ if (empty($user) || !identity::is_correct_password($user, $password)) {
+ module::event("user_login_failed", $username);
+ throw new Rest_Exception("Forbidden", 403);
+ }
+
+ auth::login($user);
+
+ rest::reply(rest::access_key());
+ }
+
+ public function reset_api_key_confirm() {
+ $form = new Forge("rest/reset_api_key", "", "post", array("id" => "g-reset-api-key"));
+ $group = $form->group("confirm_reset")->label(t("Confirm resetting your REST API key"));
+ $group->submit("")->value(t("Reset"));
+ $v = new View("reset_api_key_confirm.html");
+ $v->form = $form;
+ print $v;
+ }
+
+ public function reset_api_key() {
+ access::verify_csrf();
+ rest::reset_access_key();
+ message::success(t("Your REST API key has been reset."));
+ json::reply(array("result" => "success"));
+ }
+
+ public function __call($function, $args) {
+ try {
+ $input = Input::instance();
+ $request = new stdClass();
+
+ switch ($method = strtolower($input->server("REQUEST_METHOD"))) {
+ case "get":
+ $request->params = (object) $input->get();
+ break;
+
+ default:
+ $request->params = (object) $input->post();
+ if (isset($_FILES["file"])) {
+ $request->file = upload::save("file");
+ system::delete_later($request->file);
+ }
+ break;
+ }
+
+ if (isset($request->params->entity)) {
+ $request->params->entity = json_decode($request->params->entity);
+ }
+ if (isset($request->params->members)) {
+ $request->params->members = json_decode($request->params->members);
+ }
+
+ $request->method = strtolower($input->server("HTTP_X_GALLERY_REQUEST_METHOD", $method));
+ $request->access_key = $input->server("HTTP_X_GALLERY_REQUEST_KEY");
+
+ if (empty($request->access_key) && !empty($request->params->access_key)) {
+ $request->access_key = $request->params->access_key;
+ }
+
+ $request->url = url::abs_current(true);
+ if ($suffix = Kohana::config('core.url_suffix')) {
+ $request->url = substr($request->url, 0, strlen($request->url) - strlen($suffix));
+ }
+
+ rest::set_active_user($request->access_key);
+
+ $handler_class = "{$function}_rest";
+ $handler_method = $request->method;
+
+ if (!class_exists($handler_class) || !method_exists($handler_class, $handler_method)) {
+ throw new Rest_Exception("Bad Request", 400);
+ }
+
+ $response = call_user_func(array($handler_class, $handler_method), $request);
+ if ($handler_method == "post") {
+ // post methods must return a response containing a URI.
+ header("HTTP/1.1 201 Created");
+ header("Location: {$response['url']}");
+ }
+ rest::reply($response);
+ } catch (ORM_Validation_Exception $e) {
+ // Note: this is totally insufficient because it doesn't take into account localization. We
+ // either need to map the result values to localized strings in the application code, or every
+ // client needs its own l10n string set.
+ throw new Rest_Exception("Bad Request", 400, $e->validation->errors());
+ } catch (Kohana_404_Exception $e) {
+ throw new Rest_Exception("Not Found", 404);
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/rest/helpers/registry_rest.php b/modules/rest/helpers/registry_rest.php
new file mode 100644
index 0000000..2b4087a
--- /dev/null
+++ b/modules/rest/helpers/registry_rest.php
@@ -0,0 +1,30 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class registry_rest_Core {
+ static function get($request) {
+ $results = array();
+ foreach (module::active() as $module) {
+ foreach (glob(MODPATH . "{$module->name}/helpers/*_rest.php") as $filename) {
+ $results[] = str_replace("_rest.php", "", basename($filename));
+ }
+ }
+ return array_unique($results);
+ }
+}
diff --git a/modules/rest/helpers/rest.php b/modules/rest/helpers/rest.php
new file mode 100644
index 0000000..c6be1e1
--- /dev/null
+++ b/modules/rest/helpers/rest.php
@@ -0,0 +1,191 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class rest_Core {
+ const API_VERSION = "3.0";
+
+ static function reply($data=array()) {
+ Session::instance()->abort_save();
+
+ header("X-Gallery-API-Version: " . rest::API_VERSION);
+ switch (Input::instance()->get("output", "json")) {
+ case "json":
+ json::reply($data);
+ break;
+
+ case "jsonp":
+ if (!($callback = Input::instance()->get("callback", ""))) {
+ throw new Rest_Exception(
+ "Bad Request", 400, array("errors" => array("callback" => "missing")));
+ }
+
+ if (preg_match('/^[$A-Za-z_][0-9A-Za-z_]*$/', $callback) == 1) {
+ header("Content-type: application/javascript; charset=UTF-8");
+ print "$callback(" . json_encode($data) . ")";
+ } else {
+ throw new Rest_Exception(
+ "Bad Request", 400, array("errors" => array("callback" => "invalid")));
+ }
+ break;
+
+ case "html":
+ header("Content-type: text/html; charset=UTF-8");
+ if ($data) {
+ $html = preg_replace(
+ "#([\w]+?://[\w]+[^ \'\"\n\r\t<]*)#ise", "'<a href=\"\\1\" >\\1</a>'",
+ var_export($data, 1));
+ } else {
+ $html = t("Empty response");
+ }
+ print "<pre>$html</pre>";
+ if (gallery::show_profiler()) {
+ Profiler::enable();
+ $profiler = new Profiler();
+ $profiler->render();
+ }
+ break;
+
+ default:
+ throw new Rest_Exception("Bad Request", 400);
+ }
+ }
+
+ static function set_active_user($access_key) {
+ if (empty($access_key)) {
+ if (module::get_var("rest", "allow_guest_access")) {
+ identity::set_active_user(identity::guest());
+ return;
+ } else {
+ throw new Rest_Exception("Forbidden", 403);
+ }
+ }
+
+ $key = ORM::factory("user_access_key")
+ ->where("access_key", "=", $access_key)
+ ->find();
+
+ if (!$key->loaded()) {
+ throw new Rest_Exception("Forbidden", 403);
+ }
+
+ $user = identity::lookup_user($key->user_id);
+ if (empty($user)) {
+ throw new Rest_Exception("Forbidden", 403);
+ }
+
+ identity::set_active_user($user);
+ }
+
+ static function reset_access_key() {
+ $key = ORM::factory("user_access_key")
+ ->where("user_id", "=", identity::active_user()->id)
+ ->find();
+ if ($key->loaded()) {
+ $key->delete();
+ }
+ return rest::access_key();
+ }
+
+ static function access_key() {
+ $key = ORM::factory("user_access_key")
+ ->where("user_id", "=", identity::active_user()->id)
+ ->find();
+
+ if (!$key->loaded()) {
+ $key->user_id = identity::active_user()->id;
+ $key->access_key = md5(random::hash() . access::private_key());
+ $key->save();
+ }
+
+ return $key->access_key;
+ }
+
+ /**
+ * Convert a REST url into an object.
+ * Eg:
+ * http://example.com/gallery3/index.php/rest/item/35 -> Item_Model
+ * http://example.com/gallery3/index.php/rest/tag/16 -> Tag_Model
+ * http://example.com/gallery3/index.php/rest/tagged_item/1,16 -> [Tag_Model, Item_Model]
+ *
+ * @param string the fully qualified REST url
+ * @return mixed the corresponding object (usually a model of some kind)
+ */
+ static function resolve($url) {
+ if ($suffix = Kohana::config('core.url_suffix')) {
+ $relative_url = substr($url, strlen(url::abs_site("rest")) - strlen($suffix));
+ } else {
+ $relative_url = substr($url, strlen(url::abs_site("rest")));
+ }
+
+ $path = parse_url($relative_url, PHP_URL_PATH);
+ $components = explode("/", $path, 3);
+
+ if (count($components) != 3) {
+ throw new Kohana_404_Exception($url);
+ }
+
+ $class = "$components[1]_rest";
+ if (!class_exists($class) || !method_exists($class, "resolve")) {
+ throw new Kohana_404_Exception($url);
+ }
+
+ return call_user_func(array($class, "resolve"), !empty($components[2]) ? $components[2] : null);
+ }
+
+ /**
+ * Return an absolute url used for REST resource location.
+ * @param string resource type (eg, "item", "tag")
+ * @param object resource
+ */
+ static function url() {
+ $args = func_get_args();
+ $resource_type = array_shift($args);
+
+ $class = "{$resource_type}_rest";
+ if (!class_exists($class) || !method_exists($class, "url")) {
+ throw new Rest_Exception("Bad Request", 400);
+ }
+
+ $url = call_user_func_array(array($class, "url"), $args);
+ if (Input::instance()->get("output") == "html") {
+ if (strpos($url, "?") === false) {
+ $url .= "?output=html";
+ } else {
+ $url .= "&output=html";
+ }
+ }
+ return $url;
+ }
+
+ static function relationships($resource_type, $resource) {
+ $results = array();
+ foreach (module::active() as $module) {
+ foreach (glob(MODPATH . "{$module->name}/helpers/*_rest.php") as $filename) {
+ $class = str_replace(".php", "", basename($filename));
+ if (class_exists($class) && method_exists($class, "relationships")) {
+ if ($tmp = call_user_func(array($class, "relationships"), $resource_type, $resource)) {
+ $results = array_merge($results, $tmp);
+ }
+ }
+ }
+ }
+
+ return $results;
+ }
+}
diff --git a/modules/rest/helpers/rest_event.php b/modules/rest/helpers/rest_event.php
new file mode 100644
index 0000000..991e2b6
--- /dev/null
+++ b/modules/rest/helpers/rest_event.php
@@ -0,0 +1,102 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class rest_event {
+ /**
+ * Called just before a user is deleted. This will remove the user from
+ * the user_homes directory.
+ */
+ static function user_before_delete($user) {
+ db::build()
+ ->delete("user_access_keys")
+ ->where("id", "=", $user->id)
+ ->execute();
+ }
+
+
+ static function change_provider($new_provider) {
+ db::build()
+ ->delete("user_access_keys")
+ ->execute();
+ }
+
+ /**
+ * Called after a user has been added. Just add a remote access key
+ * on every add.
+ */
+ static function user_add_form_admin_completed($user, $form) {
+ $key = ORM::factory("user_access_key");
+ $key->user_id = $user->id;
+ $key->access_key = random::hash();
+ $key->save();
+ }
+
+ /**
+ * Called when admin is editing a user
+ */
+ static function user_edit_form_admin($user, $form) {
+ self::_get_access_key_form($user, $form);
+ }
+
+ /**
+ * Get the form fields for user edit
+ */
+ static function _get_access_key_form($user, $form) {
+ $key = ORM::factory("user_access_key")
+ ->where("user_id", "=", $user->id)
+ ->find();
+
+ if (!$key->loaded()) {
+ $key->user_id = $user->id;
+ $key->access_key = random::hash();
+ $key->save();
+ }
+
+ $form->edit_user->input("user_access_key")
+ ->value($key->access_key)
+ ->readonly("readonly")
+ ->class("g-form-static")
+ ->label(t("Remote access key"));
+ }
+
+ static function show_user_profile($data) {
+ // Guests can't see a REST key
+ if (identity::active_user()->guest) {
+ return;
+ }
+
+ // Only logged in users can see their own REST key
+ if (identity::active_user()->id != $data->user->id) {
+ return;
+ }
+
+ $view = new View("user_profile_rest.html");
+ $key = ORM::factory("user_access_key")
+ ->where("user_id", "=", $data->user->id)
+ ->find();
+
+ if (!$key->loaded()) {
+ $key->user_id = $data->user->id;
+ $key->access_key = random::hash();
+ $key->save();
+ }
+ $view->rest_key = $key->access_key;
+ $data->content[] = (object)array("title" => t("REST API"), "view" => $view);
+ }
+}
diff --git a/modules/rest/helpers/rest_installer.php b/modules/rest/helpers/rest_installer.php
new file mode 100644
index 0000000..96f8acf
--- /dev/null
+++ b/modules/rest/helpers/rest_installer.php
@@ -0,0 +1,52 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class rest_installer {
+ static function install() {
+ Database::instance()
+ ->query("CREATE TABLE {user_access_keys} (
+ `id` int(9) NOT NULL auto_increment,
+ `user_id` int(9) NOT NULL,
+ `access_key` char(32) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`access_key`),
+ UNIQUE KEY(`user_id`))
+ DEFAULT CHARSET=utf8;");
+ module::set_var("rest", "allow_guest_access", false);
+ }
+
+ static function upgrade($version) {
+ $db = Database::instance();
+ if ($version == 1) {
+ if (in_array("user_access_tokens", Database::instance()->list_tables())) {
+ $db->query("RENAME TABLE {user_access_tokens} TO {user_access_keys}");
+ }
+ module::set_version("rest", $version = 2);
+ }
+
+ if ($version == 2) {
+ module::set_var("rest", "allow_guest_access", false);
+ module::set_version("rest", $version = 3);
+ }
+ }
+
+ static function uninstall() {
+ Database::instance()->query("DROP TABLE IF EXISTS {user_access_keys}");
+ }
+}
diff --git a/modules/rest/libraries/Rest_Exception.php b/modules/rest/libraries/Rest_Exception.php
new file mode 100644
index 0000000..bd08aa8
--- /dev/null
+++ b/modules/rest/libraries/Rest_Exception.php
@@ -0,0 +1,37 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Rest_Exception_Core extends Kohana_Exception {
+ var $response = array();
+
+ public function __construct($message, $code, $response=array()) {
+ parent::__construct($message, null, $code);
+ $this->response = $response;
+ }
+
+ public function sendHeaders() {
+ if (!headers_sent()) {
+ header("HTTP/1.1 " . $this->getCode() . " " . $this->getMessage());
+ }
+ }
+
+ public function getTemplate() {
+ return "error_rest.json";
+ }
+} \ No newline at end of file
diff --git a/modules/rest/models/user_access_key.php b/modules/rest/models/user_access_key.php
new file mode 100644
index 0000000..d464380
--- /dev/null
+++ b/modules/rest/models/user_access_key.php
@@ -0,0 +1,21 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class User_Access_Key_Model_Core extends ORM {
+}
diff --git a/modules/rest/module.info b/modules/rest/module.info
new file mode 100644
index 0000000..93a7873
--- /dev/null
+++ b/modules/rest/module.info
@@ -0,0 +1,8 @@
+name = "REST API Module"
+description = "A REST-based API that allows desktop clients and other apps to interact with Gallery 3"
+
+version = 3
+author_name = "Gallery Team"
+author_url = "http://codex.galleryproject.org/Gallery:Team"
+info_url = "http://codex.galleryproject.org/Gallery3:Modules:rest"
+discuss_url = "http://galleryproject.org/forum_module_rest"
diff --git a/modules/rest/views/error_rest.json.php b/modules/rest/views/error_rest.json.php
new file mode 100644
index 0000000..8c99ef4
--- /dev/null
+++ b/modules/rest/views/error_rest.json.php
@@ -0,0 +1,6 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<?
+// Log error response to ease debugging
+Kohana_Log::add("error", "Rest error details: " . print_r($e->response, 1));
+?>
+<?= json_encode($e->response); \ No newline at end of file
diff --git a/modules/rest/views/reset_api_key_confirm.html.php b/modules/rest/views/reset_api_key_confirm.html.php
new file mode 100644
index 0000000..3aae2a9
--- /dev/null
+++ b/modules/rest/views/reset_api_key_confirm.html.php
@@ -0,0 +1,7 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-rest-reset-api-key" class="ui-helper-clearfix">
+ <p>
+ <?= t("Do you really want to reset your REST API key? Any clients that use this key will need to be updated with the new value.") ?>
+ </p>
+ <?= $form ?>
+</div>
diff --git a/modules/rest/views/user_profile_rest.html.php b/modules/rest/views/user_profile_rest.html.php
new file mode 100644
index 0000000..3e5d3db
--- /dev/null
+++ b/modules/rest/views/user_profile_rest.html.php
@@ -0,0 +1,13 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-rest-detail">
+ <ul>
+ <li id="g-rest-key">
+ <p>
+ <?= t("<b>Key</b>: %key", array("key" => $rest_key)) ?>
+ <a class="g-button ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("rest/reset_api_key_confirm") ?>">
+ <?= t("reset") ?>
+ </a>
+ </p>
+ </li>
+ </ul>
+</div>