From 9814a75f82670f492b786d6f2cd9ec783d769c29 Mon Sep 17 00:00:00 2001 From: Loic d'Anterroches Date: Fri, 21 Nov 2008 20:33:39 +0100 Subject: [PATCH] Added the first work on an API. --- src/IDF/Middleware.php | 8 +- src/IDF/Precondition.php | 32 +++++++ src/IDF/Views.php | 16 ++++ src/IDF/Views/Api.php | 110 ++++++++++++++++++++++ src/IDF/Views/Issue.php | 38 ++++---- src/IDF/Views/User.php | 4 + src/IDF/conf/urls.php | 20 ++++ src/IDF/templates/idf/base-simple.html | 2 +- src/IDF/templates/idf/base.html | 2 +- src/IDF/templates/idf/faq-api.html | 83 ++++++++++++++++ src/IDF/templates/idf/faq.html | 6 ++ src/IDF/templates/idf/user/myaccount.html | 8 ++ 12 files changed, 305 insertions(+), 24 deletions(-) create mode 100644 src/IDF/Views/Api.php create mode 100644 src/IDF/templates/idf/faq-api.html diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php index a1603da..e6fdfe0 100644 --- a/src/IDF/Middleware.php +++ b/src/IDF/Middleware.php @@ -34,9 +34,9 @@ class IDF_Middleware * When processing the request, check if matching a project. If * so, directly set $request->project to the project. * - * The url to match a project is in the format - * /p/(\w+)/whatever. This means that it will not try to match on - * /login/ or /logout/. + * The url to match a project is in the format /p/(\w+)/whatever + * or /api/p/(\w+)/whatever. This means that it will not try to + * match on /login/ or /logout/. * * @param Pluf_HTTP_Request The request * @return bool false or redirect. @@ -44,7 +44,7 @@ class IDF_Middleware function process_request(&$request) { $match = array(); - if (preg_match('#^/p/([\-\w]+)/#', $request->query, $match)) { + if (preg_match('#^/(?:api/p|p)/([\-\w]+)/#', $request->query, $match)) { try { $request->project = IDF_Project::getOr404($match[1]); } catch (Pluf_HTTP_Error404 $e) { diff --git a/src/IDF/Precondition.php b/src/IDF/Precondition.php index 686f7ad..c26c8f5 100644 --- a/src/IDF/Precondition.php +++ b/src/IDF/Precondition.php @@ -142,4 +142,36 @@ class IDF_Precondition } return self::accessTabGeneric($request, 'downloads_access_rights'); } + + /** + * Based on the request, it is automatically setting the user. + * + * API calls are not translated. + */ + static public function apiSetUser($request) + { + // REQUEST is used to be used both for POST and GET requests. + if (!isset($request->REQUEST['_hash']) + or !isset($request->REQUEST['_login']) + or !isset($request->REQUEST['_salt'])) { + // equivalent to anonymous access. + return true; + } + $db =& Pluf::db(); + $true = Pluf_DB_BooleanToDb(true, $db); + $sql = new Pluf_SQL('login=%s AND active='.$true, + $request->REQUEST['_login']); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ($users->count() != 1) { + // Should return a special authentication error like user + // not found. + return true; + } + $hash = sha1($request->REQUEST['_salt'].sha1($users[0]->password)); + if ($hash != $request->REQUEST['_hash']) { + return true; // Again need authentication error + } + $request->user = $users[0]; + return true; + } } \ No newline at end of file diff --git a/src/IDF/Views.php b/src/IDF/Views.php index b1b8ab4..9de0386 100644 --- a/src/IDF/Views.php +++ b/src/IDF/Views.php @@ -184,6 +184,22 @@ class IDF_Views } + /** + * API FAQ. + */ + public function faqApi($request, $match) + { + $title = __('InDefero API (Application Programming Interface)'); + $projects = self::getProjects($request->user); + return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html', + array( + 'page_title' => $title, + 'projects' => $projects, + ), + $request); + + } + /** * Returns a list of projects accessible for the user. * diff --git a/src/IDF/Views/Api.php b/src/IDF/Views/Api.php new file mode 100644 index 0000000..c29ef0e --- /dev/null +++ b/src/IDF/Views/Api.php @@ -0,0 +1,110 @@ +user from the + * _login, _hash and _salt parameters. + */ +class IDF_Views_Api +{ + /** + * View list of issues for a given project. + */ + public $issuesIndex_precond = array('IDF_Precondition::apiSetUser', + 'IDF_Precondition::accessIssues'); + public function issuesIndex($request, $match) + { + $views = new IDF_Views_Issue(); + $p = $views->index($request, $match, true); + $out = array( + 'project' => $request->project->shortname, + 'open' => $p['open'], + 'closed' => $p['closed'], + 'issues' => $p['issues']->render_array(), + ); + return new Pluf_HTTP_Response_Json($out); + } + + /** + * Create a new issue. + */ + public $issueCreate_precond = array('IDF_Precondition::apiSetUser', + 'IDF_Precondition::accessIssues'); + public function issueCreate($request, $match) + { + $views = new IDF_Views_Issue(); + $p = $views->create($request, $match, true); + $out = array(); + if ($request->method == 'GET') { + // We give the details of the form + $out['doc'] = 'A POST request against this url will allow you to create a new issue.'; + if ($request->user->hasPerm('IDF.project-owner', $request->project) + or $request->user->hasPerm('IDF.project-member', $request->project)) { + $out['status'] = array(); + foreach (self::getTags($request->project) as $tag) { + $out['status'][] = $tag->name; + } + } + + } else { + // We need to give back the results of the creation + if (is_object($p) and 'IDF_Issue' == get_class($p)) { + $out['mess'] = 'success'; + $out['issue'] = $p->id; + } else { + $out['mess'] = 'error'; + $out['errors'] = $p['form']->errors; + } + } + return new Pluf_HTTP_Response_Json($out); + } + + /** + * Get the list of tags to give them to the end users when doing a + * GET request against a form. That way it is possible for them to + * know which tags/labels are available. + * + * @param IDF_Project Current project + * @param string Which tags to get ('issue-open') + * @return ArrayObject Tags + */ + + public static function getTags($project, $what='issue-open') + { + switch ($what) { + case 'issue-open': + $key = 'labels_issue_open'; + $default = IDF_Form_IssueTrackingConf::init_open; + return $project->getTagsFromConfig($key, $default); + case 'issue-closed': + return $project->getTagIdsByStatus('closed'); + } + return array(); + } +} \ No newline at end of file diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index fe3c11a..8a7b783 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -35,7 +35,7 @@ class IDF_Views_Issue * View list of issues for a given project. */ public $index_precond = array('IDF_Precondition::accessIssues'); - public function index($request, $match) + public function index($request, $match, $api=false) { $prj = $request->project; $title = sprintf(__('%s Open Issues'), (string) $prj); @@ -65,15 +65,15 @@ class IDF_Views_Issue $pag->items_per_page = 10; $pag->no_results_text = __('No issues were found.'); $pag->setFromRequest($request); + $params = array('project' => $prj, + 'page_title' => $title, + 'open' => $open, + 'closed' => $closed, + 'issues' => $pag, + 'cloud' => 'issues'); + if ($api) return $params; return Pluf_Shortcuts_RenderToResponse('idf/issues/index.html', - array('project' => $prj, - 'page_title' => $title, - 'open' => $open, - 'closed' => $closed, - 'issues' => $pag, - 'cloud' => 'issues', - ), - $request); + $params, $request); } /** @@ -133,7 +133,7 @@ class IDF_Views_Issue public $create_precond = array('IDF_Precondition::accessIssues', 'Pluf_Precondition::loginRequired'); - public function create($request, $match) + public function create($request, $match, $api=false) { $prj = $request->project; $title = __('Submit a new issue'); @@ -169,20 +169,22 @@ class IDF_Views_Issue $email->addTextMessage($tmpl->render($context)); $email->sendMail(); } + if ($api) return $issue; return new Pluf_HTTP_Response_Redirect($url); } } else { $form = new IDF_Form_IssueCreate(null, $params); } - $arrays = self::autoCompleteArrays($prj); + $params = array_merge( + array('project' => $prj, + 'form' => $form, + 'page_title' => $title, + ), + self::autoCompleteArrays($prj) + ); + if ($api == true) return $params; return Pluf_Shortcuts_RenderToResponse('idf/issues/create.html', - array_merge( - array('project' => $prj, - 'form' => $form, - 'page_title' => $title, - ), - $arrays), - $request); + $params, $request); } public $search_precond = array('IDF_Precondition::accessIssues'); diff --git a/src/IDF/Views/User.php b/src/IDF/Views/User.php index ca553ab..036e1a1 100644 --- a/src/IDF/Views/User.php +++ b/src/IDF/Views/User.php @@ -38,6 +38,9 @@ class IDF_Views_User public $myAccount_precond = array('Pluf_Precondition::loginRequired'); public function myAccount($request, $match) { + // As the password is salted, we can directly take the sha1 of + // the salted password. + $api_key = sha1($request->user->password); $params = array('user' => $request->user); if ($request->method == 'POST') { $form = new IDF_Form_UserAccount($request->POST, $params); @@ -52,6 +55,7 @@ class IDF_Views_User } return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html', array('page_title' => __('Your Account'), + 'api_key' => $api_key, 'form' => $form), $request); } diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 52fd269..3eedef9 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -272,4 +272,24 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/tabs/$#', 'model' => 'IDF_Views_Project', 'method' => 'adminTabs'); +// ---------- API ---------------------------------------- + +$ctl[] = array('regex' => '#^/help/api/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views', + 'method' => 'faqApi'); + +$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Api', + 'method' => 'issuesIndex'); + +$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/create/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Api', + 'method' => 'issueCreate'); + return $ctl; diff --git a/src/IDF/templates/idf/base-simple.html b/src/IDF/templates/idf/base-simple.html index c87aba6..4358844 100644 --- a/src/IDF/templates/idf/base-simple.html +++ b/src/IDF/templates/idf/base-simple.html @@ -35,7 +35,7 @@

{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} -| {trans 'Help'} +| {trans 'Help'}

{block title}{$page_title}{/block}

diff --git a/src/IDF/templates/idf/base.html b/src/IDF/templates/idf/base.html index dcb33b6..4ff00d1 100644 --- a/src/IDF/templates/idf/base.html +++ b/src/IDF/templates/idf/base.html @@ -37,7 +37,7 @@

{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} {if $project} | {trans 'Project List'}{/if} -| {trans 'Help'} +| {trans 'Help'}