3 * This file is (c) Copyright 2010 by Sabri LABBENE, Madhumita DHAR,
4 * Olivier BERGER, Institut TELECOM
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 $controller_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
22 require_once($controller_dir . 'CmController.php');
23 require_once($controller_dir . 'FusionForgeOSLCConnector.php');
25 class FusionForgeCmController extends CmController {
30 * This will be the OSLC-CM controller managing the business logic of the application
35 * Defines accepted mime-types for queries on actions, and corresponding
38 * ATTENTION : order is important for the XML variants : the first one is the default returned when only basic XML is required
42 private static $supportedAcceptMimeTypes = array();
43 private static $fusionforgeSupportedAcceptMimeTypes = array(
44 'oslcServiceCatalogProject' => array(
45 'application/x-oslc-disc-service-provider-catalog+xml' => 'xml',
46 'application/xml' => 'xml',
47 'application/x-oslc-disc-service-provider-catalog+json' => 'json',
48 'application/json' => 'json',
49 'application/rdf+xml' => 'xml'
52 private $actionMimeType;
54 public function setActionMimeType($action) {
55 if(!isset($this->actionMimeType)) {
56 $this->actionMimeType = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $action);
61 * Init FusionForge REST controller.
63 public function init(){
64 self::$supportedAcceptMimeTypes = array_merge(parent::getSupportedAcceptMimeTypes(), self::$fusionforgeSupportedAcceptMimeTypes);
66 // TODO : render this path configurable
67 // $writer = new Zend_Log_Writer_Stream('/tmp/zend-log.txt');
68 // $this->logger = new Zend_Log($writer);
70 parent::loadModelClasses('ChangeRequests');
72 // now do things that relate to the REST framework
73 $req = $this->getRequest();
76 $action = $req->getActionName();
78 if(($action == 'post')||($action == 'put'))
80 $accept = $req->getHeader('Content-Type');
82 elseif($action =='get')
84 $accept = $req->getHeader('Accept');
87 // Set the mime type for action.
88 $this->setActionMimeType($action);
90 if(isset($this->actionMimeType)) {
91 $accept = $this->actionMimeType;
94 // determine output format
95 if (isset(self::$supportedAcceptMimeTypes[$action])) {
96 if (isset(self::$supportedAcceptMimeTypes[$action][$accept])) {
97 $format = self::$supportedAcceptMimeTypes[$action][$accept];
98 //print_r('format :'.$format);
99 $req->setParam('format', $format);
103 $contextSwitch = $this->_helper->getHelper('contextSwitch');
105 // we'll handle JSON ourselves
106 $contextSwitch->setAutoJsonSerialization(false);
108 foreach (self::$supportedAcceptMimeTypes as $action => $typesarr) {
109 //print_r(array_unique(array_values($typesarr)));
110 $types = array_unique(array_values($typesarr));
111 //print_r("Typesarr : ".$typesarr);
112 $contextSwitch->addActionContext($action, $types)->initContext();
115 // Create an OSLC Controller for FusionForge.
116 $this->oslc = new FusionForgeOSLCConnector();
119 public function getAction(){
120 $params = $this->getRequest()->getParams();
122 // check authentication although it's not yet really useful
124 $authenticated = $this->retrieveAuthentication($login);
126 // Basic auth requested
127 if (!$authenticated) {
128 // not succesfully authd as $login
130 throw new Exception('Invalid authentication provided !');
134 // handle OSLC services catalog access (http://open-services.net/bin/view/Main/OslcServiceProviderCatalogV1)
135 if ( isset($params['id']) && ($params['id'] == "oslc-services")) {
136 $this->_forward('oslcServiceCatalog');
140 // Handle OSLC-CM services catalog for specific project
141 // An OSLC-CM services catalog in FusionForge lists all the trackers
142 // of a specific project.
143 elseif (isset($params['oslc-cm-services'])){
144 $this->_forward('oslcServiceCatalogProject');
148 // handle OSLC-CM service document access
149 // An OSLC-CM service document describes capabilities of a FusionForge tracker.
150 elseif (isset($params['oslc-cm-service']) && isset($params['tracker'])) {
151 $this->_forward('oslcCmServiceDocument');
154 // Handle creation UI access
155 elseif (isset($params['ui']) && $params['ui'] == 'creation' && isset($params['project']) && isset($params['tracker'])){
156 $this->_forward('showCreationUi');
159 // Handle selection UI access
160 elseif (isset($params['ui']) && $params['ui'] == 'selection' && isset($params['project']) && isset($params['tracker'])){
161 $this->_forward('showSelectionUi');
165 // Now, do the OSLC-CM resources access work
166 // if no bug was mentioned, then return a resource collection
167 if (!array_key_exists('bug', $params)) {
168 // forward to an independant action so that it has its own views
169 // (see readresourcecollectionAction())
170 $this->_forward('readResourceCollection');
172 elseif(array_key_exists('bug', $params)) {
173 // now we're indeed getting one single resource
174 $this->_forward('readResource');
176 throw new NotFoundException("Resource ".$this->getRequest()->getRequestUri()." was not found on the server!");
179 // This is not explicitely required in OSLC-CM V1
180 // In the case of RDF+XML requested, mention it explicitely : LOD friendly
181 $req = $this->getRequest();
182 $accept = $req->getHeader('Accept');
184 case (strstr($accept, 'application/rdf+xml')):
185 $resp = $this->getResponse()->setHeader('Content-Type', 'application/rdf+xml');
191 * Handles PUT action as routed by Zend_Rest_Route
193 * Update of an existing changerequest
194 * Will be invoked if PUT or if POST on a path relating to resources (due to Zend REST route behaviour)
195 * So in case of POST, will pass the handling to postAction()
197 * @return unknown_type
199 public function putAction(){
200 $req = $this->getRequest();
202 // in case invoked like POST on .../cm/project/whatever we arrive to putAction
203 // so we check such case and then redirect to postAction() if needed
204 if ($req->isPost()) {
209 // otherwise it is indeed a PUT and we are trying to modify a change request
211 // do authentication.
213 $authenticated = $this->retrieveAuthentication($login);
215 // Basic auth requested
216 if (!$authenticated) {
217 // not succesfully authd as $login
219 throw new Exception('Invalid authentication provided !');
223 $contenttype = $req->getHeader('Content-Type');
224 $contenttype = $contenttype ? $contenttype : 'none';
226 switch($contenttype) {
227 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
228 case 'application/x-oslc-cm-change-request+json; charset=UTF-8':
229 case 'application/xml; charset=UTF-8':
230 case 'application/json; charset=UTF-8':
231 case 'application/x-oslc-cm-change-request+xml':
232 case 'application/x-oslc-cm-change-request+json':
233 case 'application/xml':
234 case 'application/json':
237 throw new UnsupportedMediaTypeException('Unknown Content-Type for method put : '. $contenttype .' !');
243 $params = $req->getParams();
245 if (array_key_exists('id', $params)) {
246 $identifier = $req->getParam('id');
249 $identifier = $req->getParam('bug');
251 if (! isset($identifier)) {
252 throw new Exception('No change request id provided !');
256 // checking if modification
258 $modifiedproperties = null;
260 $oslc_cm_properties = $req->getParam('oslc_cm_properties');
261 if (isset($oslc_cm_properties)) {
262 $modifiedproperties = explode(',', $oslc_cm_properties);
263 if (array_key_exists('identifier', $modifiedproperties)) {
264 throw new Exception('Identifier cannot be modified !');
268 $body = file_get_contents('php://input');
270 // TODO: This should be done by $this->oslc
271 switch($contenttype) {
272 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
273 case 'application/xml; charset=UTF-8':
274 case 'application/x-oslc-cm-change-request+xml':
275 case 'application/xml':
276 // extract values from XML
277 $newchangerequest = FusionForgeChangeRequest::CreateFusionForgeArrayFromXml($body);
279 case 'application/x-oslc-cm-change-request+json; charset=UTF-8':
280 case 'application/json; charset=UTF-8':
281 case 'application/x-oslc-cm-change-request+json':
282 case 'application/json':
283 // extract values from JSON.
284 $newchangerequest = FusionForgeChangeRequest::CreateFusionForgeArrayFromJson($body);
288 if(!$this->oslc->checkChangeRequestExists($identifier)) {
289 throw new Exception("Change Request to be updated doesn't exist!");
292 // Proceed to change request update
293 $this->oslc->updateChangeRequest($identifier, $newchangerequest, $modifiedproperties);
301 public function postAction(){
302 $req = $this->getRequest();
304 // check that we're indeed invoked by a POST request
305 if(! $req->isPost()) {
306 throw new Exception('postAction invoked without POST !');
309 $params = $req->getParams();
311 // do authentication.
313 $authenticated = $this->retrieveAuthentication($login);
315 // Basic auth requested
316 if (!$authenticated) {
317 // not succesfully authd as $login
319 throw new Exception('Invalid authentication provided !');
323 $contenttype = $req->getHeader('Content-Type');
324 $contenttype = $contenttype ? $contenttype : 'none';
326 switch($contenttype) {
327 case 'application/x-oslc-cm-change-request+xml':
328 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
329 case 'application/x-oslc-cm-change-request+json':
330 case 'application/x-oslc-cm-change-request+json; charset=UTF-8':
331 case 'application/json':
332 case 'application/json; charset=UTF-8':
333 case 'application/xml; charset=UTF-8':
334 case 'application/xml':
337 throw new UnsupportedMediaTypeException('Unknown Content-Type for method post : '. $contenttype .' !');
341 // used for PhpUnit tests.
342 if(APPLICATION_ENV=='testing') {
343 $body = $_POST['xml'];
345 $body = file_get_contents('php://input');
349 if(array_key_exists('project',$params)) {
350 if (array_key_exists('tracker', $params)) {
351 // create a change request
352 switch($contenttype) {
353 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
354 case 'application/x-oslc-cm-change-request+xml':
355 case 'application/xml; charset=UTF-8':
356 case 'application/xml':
357 $newchangerequest = FusionForgeChangeRequest::CreateFusionForgeArrayFromXml($body);
359 case 'application/x-oslc-cm-change-request+json; charset=UTF-8':
360 case 'application/x-oslc-cm-change-request+json':
361 case 'application/json; charset=UTF-8':
362 case 'application/json':
363 $newchangerequest = FusionForgeChangeRequest::CreateFusionForgeArrayFromJson($body);
366 //print_r($newchangerequest);
368 $creationparams = array('project' => $params['project'],
369 'tracker' => $params['tracker'],
370 'new' => $newchangerequest);
372 // pass the creation work to the OSLC connector
373 $identifier = $this->oslc->createChangeRequest($creationparams);
376 throw new ConflictException('Need a valid tracker to create a change request');
379 throw new ConflictException('Need a valid project and tracker to create change request !');
382 // prepare redirection
383 $httpScheme = $this->getRequest()->getScheme();
384 $httpHost = $this->getRequest()->getHttpHost();
385 $requestUri = $this->getRequest()->getRequestUri();
386 $baseURL = $this->getFrontController()->getBaseUrl();
387 $controllerName = $this->getFrontController()->getDefaultControllerName();
389 if(APPLICATION_ENV=='testing')
391 $newlocation = '/'.$controllerName.'/project/'.$params['project'].'/tracker/'.$params['tracker'].'/bug/'.$identifier;
395 $newlocation = $httpScheme.'://'.$httpHost.$baseURL.'/'.$controllerName.'/project/'.$params['project'].'/tracker/'.$params['tracker'].'/bug/'.$identifier;
401 //redirect to new change request
402 $this->getResponse()->setRedirect($newlocation,201);
404 public function indexAction(){
407 public function deleteAction(){
412 * Retrieve an individual resource and populates the view of an OSLC CM ChangeRequest
414 * @param string $identifier
417 public function readresourceAction() {
419 $params = $this->getRequest()->getParams();
420 //$content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
421 if (!isset($this->actionMimeType)) {
422 $this->_forward('UnknownAcceptType','error');
426 $identifier = $params['bug'];
428 // prepare resource URI
429 $httpScheme = $this->getRequest()->getScheme();
430 $httpHost = $this->getRequest()->getHttpHost();
431 $requestUri = $this->getRequest()->getRequestUri();
432 $changerequestURI = $httpScheme.'://'.$httpHost.$requestUri;
434 // Check if some specific fields have been requested for the ChangeRequest.
435 if (isset($params['oslc_properties'])){
436 $preparedChangeRequest = $this->oslc->fetchChangeRequest($identifier, $changerequestURI, $params['oslc_properties']);
438 $preparedChangeRequest = $this->oslc->fetchChangeRequest($identifier, $changerequestURI);
441 if(isset($preparedChangeRequest)) {
443 // populate the view with the model
444 foreach($preparedChangeRequest as $field => $value) {
445 $this->view->{$field} = $value;
448 $this->getResponse()->setHeader('Content-Type', $this->actionMimeType);
451 $this->view->missing_resource = $identifier;
452 $this->_forward('ResNotFound','error');
456 public function readresourcecollectionAction() {
457 $req = $this->getRequest();
458 $params = $req->getParams();
460 //$content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
461 // TODO: raise the correct error code according to the specs.
462 if (!isset($this->actionMimeType)) {
464 throw new NotAcceptableException("Accept header ".$req->getHeader('Accept')." not supported!");
468 // load the model. Will fetch requested change requests from the db.
469 $params = $this->oslc->init($params);
471 // construct ATOM feed-like header
472 $httpScheme = $this->getRequest()->getScheme();
473 $httpHost = $this->getRequest()->getHttpHost();
474 $requestUri = $this->getRequest()->getRequestUri();
475 $prefix = $httpScheme.'://'.$httpHost.$requestUri;
478 $collection = $this->oslc->getResourceCollection($prefix);
480 // construct an intermediate array that will be used to populate the view
481 $preparedCollection = array ('id' => $httpScheme.'://'.$httpHost.$requestUri,
482 'collection' => $collection
484 // Add request params so they ca reach views.
485 foreach($params as $key => $value){
486 $preparedCollection[$key] = $value;
489 // populate the view with the loaded values
490 foreach($preparedCollection as $key => $value) {
491 $this->view->$key = $value;
494 //print_r($this->view);
495 $this->getResponse()->setHeader('Content-Type', $this->actionMimeType);
500 * Handle OSLC Core services provider catalog access (http://open-services.net/bin/view/Main/OslcCoreSpecification)
501 * Will show the list of prjects.
504 public function oslcservicecatalogAction() {
506 //$content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
507 if (! isset($this->actionMimeType)) {
509 $this->_forward('UnknownAcceptType','error');
512 // each project is considered as a service Provider.
513 $proj_arr = $this->oslc->getProjectsList();
515 $this->view->projects = $proj_arr;
517 $this->getResponse()->setHeader('Content-Type', $this->actionMimeType);
521 * Handle OSLC services catalog access per project.
522 * Accessed by uris like ".../cm/oslc-cm-services/x"
523 * where x is a project id.
525 public function oslcservicecatalogprojectAction() {
526 //$content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
527 if (! isset($this->actionMimeType)) {
528 $this->_forward('UnknownAcceptType','error');
532 $req = $this->getRequest();
533 $params = $req->getParams();
535 $project = $params['oslc-cm-services'];
536 $trackers = $this->oslc->getProjectTrackers($project);
538 $this->view->project = $project;
539 $this->view->trackers = $trackers;
541 $this->getResponse()->setHeader('Content-Type', $this->actionMimeType);
546 * Handles OSLC-CM service document access.
548 public function oslccmservicedocumentAction() {
549 //$this->actionMimeType = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
550 if (! isset($this->actionMimeType)) {
551 $this->_forward('UnknownAcceptType','error');
555 $req = $this->getRequest();
556 $params = $req->getParams();
558 $this->view->project = $params['oslc-cm-service'];
559 $this->view->tracker = $params['tracker'];
561 $this->getResponse()->setHeader('Content-Type', $this->actionMimeType);
565 public function showselectionuiAction() {
566 $req = $this->getRequest();
567 $params = $req->getParams();
568 $project = $params['project'];
569 $tracker = $params['tracker'];
570 $data = $this->oslc->getDataForSelectionUi($project, $tracker);
571 $this->view->data = $data;
574 public function showcreationuiAction() {
575 $req = $this->getRequest();
576 $params = $req->getParams();
577 $project = $params['project'];
578 $tracker = $params['tracker'];
579 $data = $this->oslc->getDataForCreationUi($project, $tracker);
580 $this->view->data = $data;
584 * Performs authentication according to the authorization header recieved.
586 * @param string $login
587 * @return True if auth is valid, FALSE otherwise.
590 private function retrieveAuthentication(&$login) {
591 $request = $this->getRequest();
592 $auth = $request->getHeader('Authorization');
594 $auth_type = explode(' ',$auth);
595 $auth_type = $auth_type[0];
596 if (strcasecmp($auth_type, 'OAuth')) {
597 $returned = $this->oslc->checkOauthAuthorization($auth);
599 } elseif (strcasecmp($auth_type, 'basic')) {
600 return $this->retrieveRequestAuthHttpBasic($login);
602 throw new BadRequestException('Unsupported Authorization type : '. $auth_type .' !');
611 * Helper function that performs HTTP Basic authentication from request parameters/headers
613 * @param string $login
614 * @return True if auth is valid, in which case $login is modified.
615 * If there was actually no auth requested, then return False, but $login will be set to null.
618 private function retrieveRequestAuthHttpBasic(&$login) {
619 // extract login and password from Basic auth
625 $request = $this->getRequest();
626 $auth = $request->getHeader('Authorization');
627 // print_r('Auth :'.$auth);
628 // print_r('Auth :'.$auth.'!');
629 if (strlen($auth) != 0) {
630 $auth = explode(' ',$auth);
631 if ($auth[0] == 'Basic') {
633 $basic = base64_decode($auth[1]);
635 $basic = explode(':',$basic);
638 $password = $basic[1];
639 //print_r('request username'.$login);
640 //print_r('request password'.$password);
642 elseif ($auth[0] == 'OAuth') {
643 session_set_for_authplugin('oauthprovider');
646 throw new BadRequestException('Unsupported auth method : '. $auth[0] .' !');
649 if (isset($password)) {
652 'accept_schemes' => 'basic',
653 'realm' => 'Oslc-Demo',
654 'digest_domains' => '/cm',
655 'nonce_timeout' => 3600,
658 // Http authentication adapter
659 $adapter = new Zend_Auth_Adapter_Http($config);
661 // setup the OslcControler's Auth HTTP Basic resolver
662 $basicResolver = $this->oslc->getHttpAuthBasicResolver($login, $password);
664 // The authentication check will be performed by Mantis
665 $adapter->setBasicResolver($basicResolver);
667 $request = $this->getRequest();
668 $adapter->setRequest($request);
669 $response = $this->getResponse();
670 $adapter->setResponse($response);
672 // perform authentication check
673 //$result = $this->auth->authenticate($adapter);
674 $result = $adapter->authenticate();
675 if (!$result->isValid()) {
676 print_r('Access denied for : '. $login .' !');
678 //print_r($result->getCode());
679 switch ($result->getCode()) {
680 case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND:
681 /** do stuff for nonexistent identity **/
682 print_r('Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND');
684 case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID:
685 /** do stuff for invalid credential **/
686 print_r('Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID');
688 case Zend_Auth_Result::SUCCESS:
689 /** do stuff for successful authentication **/
690 // print_r('Zend_Auth_Result::SUCCESS');
694 /** do stuff for other failure **/
695 print_r('other problem');