Browse Source

initial import of appframework

tags/v6.0.0alpha2
Thomas Müller 10 years ago
parent
commit
fde9cabe97
37 changed files with 4963 additions and 0 deletions
  1. 97
    0
      lib/appframework/app.php
  2. 154
    0
      lib/appframework/controller/controller.php
  3. 524
    0
      lib/appframework/core/api.php
  4. 125
    0
      lib/appframework/dependencyinjection/dicontainer.php
  5. 98
    0
      lib/appframework/http/dispatcher.php
  6. 51
    0
      lib/appframework/http/downloadresponse.php
  7. 208
    0
      lib/appframework/http/http.php
  8. 74
    0
      lib/appframework/http/jsonresponse.php
  9. 54
    0
      lib/appframework/http/redirectresponse.php
  10. 217
    0
      lib/appframework/http/request.php
  11. 169
    0
      lib/appframework/http/response.php
  12. 126
    0
      lib/appframework/http/templateresponse.php
  13. 100
    0
      lib/appframework/middleware/middleware.php
  14. 159
    0
      lib/appframework/middleware/middlewaredispatcher.php
  15. 41
    0
      lib/appframework/middleware/security/securityexception.php
  16. 141
    0
      lib/appframework/middleware/security/securitymiddleware.php
  17. 42
    0
      lib/appframework/routing/routeactionhandler.php
  18. 186
    0
      lib/appframework/routing/routeconfig.php
  19. 61
    0
      lib/appframework/utility/methodannotationreader.php
  20. 42
    0
      lib/appframework/utility/timefactory.php
  21. 107
    0
      tests/lib/appframework/AppTest.php
  22. 45
    0
      tests/lib/appframework/classloader.php
  23. 161
    0
      tests/lib/appframework/controller/ControllerTest.php
  24. 98
    0
      tests/lib/appframework/dependencyinjection/DIContainerTest.php
  25. 218
    0
      tests/lib/appframework/http/DispatcherTest.php
  26. 51
    0
      tests/lib/appframework/http/DownloadResponseTest.php
  27. 87
    0
      tests/lib/appframework/http/HttpTest.php
  28. 96
    0
      tests/lib/appframework/http/JSONResponseTest.php
  29. 55
    0
      tests/lib/appframework/http/RedirectResponseTest.php
  30. 78
    0
      tests/lib/appframework/http/RequestTest.php
  31. 119
    0
      tests/lib/appframework/http/ResponseTest.php
  32. 157
    0
      tests/lib/appframework/http/TemplateResponseTest.php
  33. 280
    0
      tests/lib/appframework/middleware/MiddlewareDispatcherTest.php
  34. 82
    0
      tests/lib/appframework/middleware/MiddlewareTest.php
  35. 388
    0
      tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php
  36. 214
    0
      tests/lib/appframework/routing/RoutingTest.php
  37. 58
    0
      tests/lib/appframework/utility/MethodAnnotationReaderTest.php

+ 97
- 0
lib/appframework/app.php View File

@@ -0,0 +1,97 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework;

use OC\AppFramework\DependencyInjection\DIContainer;


/**
* Entry point for every request in your app. You can consider this as your
* public static void main() method
*
* Handles all the dependency injection, controllers and output flow
*/
class App {


/**
* Shortcut for calling a controller method and printing the result
* @param string $controllerName the name of the controller under which it is
* stored in the DI container
* @param string $methodName the method that you want to call
* @param array $urlParams an array with variables extracted from the routes
* @param DIContainer $container an instance of a pimple container.
*/
public static function main($controllerName, $methodName, array $urlParams,
DIContainer $container) {
$container['urlParams'] = $urlParams;
$controller = $container[$controllerName];

// initialize the dispatcher and run all the middleware before the controller
$dispatcher = $container['Dispatcher'];

list($httpHeaders, $responseHeaders, $output) =
$dispatcher->dispatch($controller, $methodName);

if(!is_null($httpHeaders)) {
header($httpHeaders);
}

foreach($responseHeaders as $name => $value) {
header($name . ': ' . $value);
}

if(!is_null($output)) {
header('Content-Length: ' . strlen($output));
print($output);
}

}

/**
* Shortcut for calling a controller method and printing the result.
* Similar to App:main except that no headers will be sent.
* This should be used for example when registering sections via
* \OC\AppFramework\Core\API::registerAdmin()
*
* @param string $controllerName the name of the controller under which it is
* stored in the DI container
* @param string $methodName the method that you want to call
* @param array $urlParams an array with variables extracted from the routes
* @param DIContainer $container an instance of a pimple container.
*/
public static function part($controllerName, $methodName, array $urlParams,
DIContainer $container){

$container['urlParams'] = $urlParams;
$controller = $container[$controllerName];

$dispatcher = $container['Dispatcher'];

list(, , $output) = $dispatcher->dispatch($controller, $methodName);
return $output;
}

}

+ 154
- 0
lib/appframework/controller/controller.php View File

@@ -0,0 +1,154 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Controller;

use OC\AppFramework\Http\TemplateResponse;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Core\API;


/**
* Base class to inherit your controllers from
*/
abstract class Controller {

/**
* @var API instance of the api layer
*/
protected $api;

protected $request;

/**
* @param API $api an api wrapper instance
* @param Request $request an instance of the request
*/
public function __construct(API $api, Request $request){
$this->api = $api;
$this->request = $request;
}


/**
* Lets you access post and get parameters by the index
* @param string $key the key which you want to access in the URL Parameter
* placeholder, $_POST or $_GET array.
* The priority how they're returned is the following:
* 1. URL parameters
* 2. POST parameters
* 3. GET parameters
* @param mixed $default If the key is not found, this value will be returned
* @return mixed the content of the array
*/
public function params($key, $default=null){
return isset($this->request->parameters[$key])
? $this->request->parameters[$key]
: $default;
}


/**
* Returns all params that were received, be it from the request
* (as GET or POST) or throuh the URL by the route
* @return array the array with all parameters
*/
public function getParams() {
return $this->request->parameters;
}


/**
* Returns the method of the request
* @return string the method of the request (POST, GET, etc)
*/
public function method() {
return $this->request->method;
}


/**
* Shortcut for accessing an uploaded file through the $_FILES array
* @param string $key the key that will be taken from the $_FILES array
* @return array the file in the $_FILES element
*/
public function getUploadedFile($key) {
return isset($this->request->files[$key]) ? $this->request->files[$key] : null;
}


/**
* Shortcut for getting env variables
* @param string $key the key that will be taken from the $_ENV array
* @return array the value in the $_ENV element
*/
public function env($key) {
return isset($this->request->env[$key]) ? $this->request->env[$key] : null;
}


/**
* Shortcut for getting session variables
* @param string $key the key that will be taken from the $_SESSION array
* @return array the value in the $_SESSION element
*/
public function session($key) {
return isset($this->request->session[$key]) ? $this->request->session[$key] : null;
}


/**
* Shortcut for getting cookie variables
* @param string $key the key that will be taken from the $_COOKIE array
* @return array the value in the $_COOKIE element
*/
public function cookie($key) {
return isset($this->request->cookies[$key]) ? $this->request->cookies[$key] : null;
}


/**
* Shortcut for rendering a template
* @param string $templateName the name of the template
* @param array $params the template parameters in key => value structure
* @param string $renderAs user renders a full page, blank only your template
* admin an entry in the admin settings
* @param array $headers set additional headers in name/value pairs
* @return \OC\AppFramework\Http\TemplateResponse containing the page
*/
public function render($templateName, array $params=array(),
$renderAs='user', array $headers=array()){
$response = new TemplateResponse($this->api, $templateName);
$response->setParams($params);
$response->renderAs($renderAs);

foreach($headers as $name => $value){
$response->addHeader($name, $value);
}

return $response;
}


}

+ 524
- 0
lib/appframework/core/api.php View File

@@ -0,0 +1,524 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Core;


/**
* This is used to wrap the owncloud static api calls into an object to make the
* code better abstractable for use in the dependency injection container
*
* Should you find yourself in need for more methods, simply inherit from this
* class and add your methods
*/
class API {

private $appName;

/**
* constructor
* @param string $appName the name of your application
*/
public function __construct($appName){
$this->appName = $appName;
}


/**
* used to return the appname of the set application
* @return string the name of your application
*/
public function getAppName(){
return $this->appName;
}


/**
* Creates a new navigation entry
* @param array $entry containing: id, name, order, icon and href key
*/
public function addNavigationEntry(array $entry){
\OCP\App::addNavigationEntry($entry);
}


/**
* Gets the userid of the current user
* @return string the user id of the current user
*/
public function getUserId(){
return \OCP\User::getUser();
}


/**
* Sets the current navigation entry to the currently running app
*/
public function activateNavigationEntry(){
\OCP\App::setActiveNavigationEntry($this->appName);
}


/**
* Adds a new javascript file
* @param string $scriptName the name of the javascript in js/ without the suffix
* @param string $appName the name of the app, defaults to the current one
*/
public function addScript($scriptName, $appName=null){
if($appName === null){
$appName = $this->appName;
}
\OCP\Util::addScript($appName, $scriptName);
}


/**
* Adds a new css file
* @param string $styleName the name of the css file in css/without the suffix
* @param string $appName the name of the app, defaults to the current one
*/
public function addStyle($styleName, $appName=null){
if($appName === null){
$appName = $this->appName;
}
\OCP\Util::addStyle($appName, $styleName);
}


/**
* shorthand for addScript for files in the 3rdparty directory
* @param string $name the name of the file without the suffix
*/
public function add3rdPartyScript($name){
\OCP\Util::addScript($this->appName . '/3rdparty', $name);
}


/**
* shorthand for addStyle for files in the 3rdparty directory
* @param string $name the name of the file without the suffix
*/
public function add3rdPartyStyle($name){
\OCP\Util::addStyle($this->appName . '/3rdparty', $name);
}

/**
* Looks up a systemwide defined value
* @param string $key the key of the value, under which it was saved
* @return string the saved value
*/
public function getSystemValue($key){
return \OCP\Config::getSystemValue($key, '');
}


/**
* Sets a new systemwide value
* @param string $key the key of the value, under which will be saved
* @param string $value the value that should be stored
*/
public function setSystemValue($key, $value){
return \OCP\Config::setSystemValue($key, $value);
}


/**
* Looks up an appwide defined value
* @param string $key the key of the value, under which it was saved
* @return string the saved value
*/
public function getAppValue($key, $appName=null){
if($appName === null){
$appName = $this->appName;
}
return \OCP\Config::getAppValue($appName, $key, '');
}


/**
* Writes a new appwide value
* @param string $key the key of the value, under which will be saved
* @param string $value the value that should be stored
*/
public function setAppValue($key, $value, $appName=null){
if($appName === null){
$appName = $this->appName;
}
return \OCP\Config::setAppValue($appName, $key, $value);
}



/**
* Shortcut for setting a user defined value
* @param string $key the key under which the value is being stored
* @param string $value the value that you want to store
* @param string $userId the userId of the user that we want to store the value under, defaults to the current one
*/
public function setUserValue($key, $value, $userId=null){
if($userId === null){
$userId = $this->getUserId();
}
\OCP\Config::setUserValue($userId, $this->appName, $key, $value);
}


/**
* Shortcut for getting a user defined value
* @param string $key the key under which the value is being stored
* @param string $userId the userId of the user that we want to store the value under, defaults to the current one
*/
public function getUserValue($key, $userId=null){
if($userId === null){
$userId = $this->getUserId();
}
return \OCP\Config::getUserValue($userId, $this->appName, $key);
}


/**
* Returns the translation object
* @return \OC_L10N the translation object
*/
public function getTrans(){
# TODO: use public api
return \OC_L10N::get($this->appName);
}


/**
* Used to abstract the owncloud database access away
* @param string $sql the sql query with ? placeholder for params
* @param int $limit the maximum number of rows
* @param int $offset from which row we want to start
* @return \OCP\DB a query object
*/
public function prepareQuery($sql, $limit=null, $offset=null){
return \OCP\DB::prepare($sql, $limit, $offset);
}


/**
* Used to get the id of the just inserted element
* @param string $tableName the name of the table where we inserted the item
* @return int the id of the inserted element
*/
public function getInsertId($tableName){
return \OCP\DB::insertid($tableName);
}


/**
* Returns the URL for a route
* @param string $routeName the name of the route
* @param array $arguments an array with arguments which will be filled into the url
* @return string the url
*/
public function linkToRoute($routeName, $arguments=array()){
return \OCP\Util::linkToRoute($routeName, $arguments);
}


/**
* Returns an URL for an image or file
* @param string $file the name of the file
* @param string $appName the name of the app, defaults to the current one
*/
public function linkTo($file, $appName=null){
if($appName === null){
$appName = $this->appName;
}
return \OCP\Util::linkTo($appName, $file);
}


/**
* Returns the link to an image, like link to but only with prepending img/
* @param string $file the name of the file
* @param string $appName the name of the app, defaults to the current one
*/
public function imagePath($file, $appName=null){
if($appName === null){
$appName = $this->appName;
}
return \OCP\Util::imagePath($appName, $file);
}


/**
* Makes an URL absolute
* @param string $url the url
* @return string the absolute url
*/
public function getAbsoluteURL($url){
# TODO: use public api
return \OC_Helper::makeURLAbsolute($url);
}


/**
* links to a file
* @param string $file the name of the file
* @param string $appName the name of the app, defaults to the current one
* @deprecated replaced with linkToRoute()
* @return string the url
*/
public function linkToAbsolute($file, $appName=null){
if($appName === null){
$appName = $this->appName;
}
return \OCP\Util::linkToAbsolute($appName, $file);
}


/**
* Checks if the current user is logged in
* @return bool true if logged in
*/
public function isLoggedIn(){
return \OCP\User::isLoggedIn();
}


/**
* Checks if a user is an admin
* @param string $userId the id of the user
* @return bool true if admin
*/
public function isAdminUser($userId){
# TODO: use public api
return \OC_User::isAdminUser($userId);
}


/**
* Checks if a user is an subadmin
* @param string $userId the id of the user
* @return bool true if subadmin
*/
public function isSubAdminUser($userId){
# TODO: use public api
return \OC_SubAdmin::isSubAdmin($userId);
}


/**
* Checks if the CSRF check was correct
* @return bool true if CSRF check passed
*/
public function passesCSRFCheck(){
# TODO: use public api
return \OC_Util::isCallRegistered();
}


/**
* Checks if an app is enabled
* @param string $appName the name of an app
* @return bool true if app is enabled
*/
public function isAppEnabled($appName){
return \OCP\App::isEnabled($appName);
}


/**
* Writes a function into the error log
* @param string $msg the error message to be logged
* @param int $level the error level
*/
public function log($msg, $level=null){
switch($level){
case 'debug':
$level = \OCP\Util::DEBUG;
break;
case 'info':
$level = \OCP\Util::INFO;
break;
case 'warn':
$level = \OCP\Util::WARN;
break;
case 'fatal':
$level = \OCP\Util::FATAL;
break;
default:
$level = \OCP\Util::ERROR;
break;
}
\OCP\Util::writeLog($this->appName, $msg, $level);
}


/**
* Returns a template
* @param string $templateName the name of the template
* @param string $renderAs how it should be rendered
* @param string $appName the name of the app
* @return \OCP\Template a new template
*/
public function getTemplate($templateName, $renderAs='user', $appName=null){
if($appName === null){
$appName = $this->appName;
}

if($renderAs === 'blank'){
return new \OCP\Template($appName, $templateName);
} else {
return new \OCP\Template($appName, $templateName, $renderAs);
}
}


/**
* turns an owncloud path into a path on the filesystem
* @param string path the path to the file on the oc filesystem
* @return string the filepath in the filesystem
*/
public function getLocalFilePath($path){
# TODO: use public api
return \OC_Filesystem::getLocalFile($path);
}


/**
* used to return and open a new eventsource
* @return \OC_EventSource a new open EventSource class
*/
public function openEventSource(){
# TODO: use public api
return new \OC_EventSource();
}

/**
* @brief connects a function to a hook
* @param string $signalClass class name of emitter
* @param string $signalName name of signal
* @param string $slotClass class name of slot
* @param string $slotName name of slot, in another word, this is the
* name of the method that will be called when registered
* signal is emitted.
* @return bool, always true
*/
public function connectHook($signalClass, $signalName, $slotClass, $slotName) {
return \OCP\Util::connectHook($signalClass, $signalName, $slotClass, $slotName);
}

/**
* @brief Emits a signal. To get data from the slot use references!
* @param string $signalClass class name of emitter
* @param string $signalName name of signal
* @param array $params defautl: array() array with additional data
* @return bool, true if slots exists or false if not
*/
public function emitHook($signalClass, $signalName, $params = array()) {
return \OCP\Util::emitHook($signalClass, $signalName, $params);
}

/**
* @brief clear hooks
* @param string $signalClass
* @param string $signalName
*/
public function clearHook($signalClass=false, $signalName=false) {
if ($signalClass) {
\OC_Hook::clear($signalClass, $signalName);
}
}

/**
* Gets the content of an URL by using CURL or a fallback if it is not
* installed
* @param string $url the url that should be fetched
* @return string the content of the webpage
*/
public function getUrlContent($url) {
return \OC_Util::getUrlContent($url);
}

/**
* Register a backgroundjob task
* @param string $className full namespace and class name of the class
* @param string $methodName the name of the static method that should be
* called
*/
public function addRegularTask($className, $methodName) {
\OCP\Backgroundjob::addRegularTask($className, $methodName);
}

/**
* Tells ownCloud to include a template in the admin overview
* @param string $mainPath the path to the main php file without the php
* suffix, relative to your apps directory! not the template directory
* @param string $appName the name of the app, defaults to the current one
*/
public function registerAdmin($mainPath, $appName=null) {
if($appName === null){
$appName = $this->appName;
}

\OCP\App::registerAdmin($appName, $mainPath);
}

/**
* Do a user login
* @param string $user the username
* @param string $password the password
* @return bool true if successful
*/
public function login($user, $password) {
return \OC_User::login($user, $password);
}

/**
* @brief Loggs the user out including all the session data
* Logout, destroys session
*/
public function logout() {
return \OCP\User::logout();
}

/**
* get the filesystem info
*
* @param string $path
* @return array with the following keys:
* - size
* - mtime
* - mimetype
* - encrypted
* - versioned
*/
public function getFileInfo($path) {
return \OC\Files\Filesystem::getFileInfo($path);
}

/**
* get the view
*
* @return OC\Files\View instance
*/
public function getView() {
return \OC\Files\Filesystem::getView();
}
}

+ 125
- 0
lib/appframework/dependencyinjection/dicontainer.php View File

@@ -0,0 +1,125 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\DependencyInjection;

use OC\AppFramework\Http\Http;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Core\API;
use OC\AppFramework\Middleware\MiddlewareDispatcher;
use OC\AppFramework\Middleware\Http\HttpMiddleware;
use OC\AppFramework\Middleware\Security\SecurityMiddleware;
use OC\AppFramework\Utility\TimeFactory;

// register 3rdparty autoloaders
require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php';


/**
* This class extends Pimple (http://pimple.sensiolabs.org/) for reusability
* To use this class, extend your own container from this. Should you require it
* you can overwrite the dependencies with your own classes by simply redefining
* a dependency
*/
class DIContainer extends \Pimple {


/**
* Put your class dependencies in here
* @param string $appName the name of the app
*/
public function __construct($appName){

$this['AppName'] = $appName;

$this['API'] = $this->share(function($c){
return new API($c['AppName']);
});

/**
* Http
*/
$this['Request'] = $this->share(function($c) {
$params = json_decode(file_get_contents('php://input'), true);
$params = is_array($params) ? $params: array();

return new Request(
array(
'get' => $_GET,
'post' => $_POST,
'files' => $_FILES,
'server' => $_SERVER,
'env' => $_ENV,
'session' => $_SESSION,
'cookies' => $_COOKIE,
'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
? $_SERVER['REQUEST_METHOD']
: null,
'params' => $params,
'urlParams' => $c['urlParams']
)
);
});

$this['Protocol'] = $this->share(function($c){
if(isset($_SERVER['SERVER_PROTOCOL'])) {
return new Http($_SERVER, $_SERVER['SERVER_PROTOCOL']);
} else {
return new Http($_SERVER);
}
});

$this['Dispatcher'] = $this->share(function($c) {
return new Dispatcher($c['Protocol'], $c['MiddlewareDispatcher']);
});


/**
* Middleware
*/
$this['SecurityMiddleware'] = $this->share(function($c){
return new SecurityMiddleware($c['API'], $c['Request']);
});

$this['MiddlewareDispatcher'] = $this->share(function($c){
$dispatcher = new MiddlewareDispatcher();
$dispatcher->registerMiddleware($c['SecurityMiddleware']);

return $dispatcher;
});


/**
* Utilities
*/
$this['TimeFactory'] = $this->share(function($c){
return new TimeFactory();
});


}


}

+ 98
- 0
lib/appframework/http/dispatcher.php View File

@@ -0,0 +1,98 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt, Thomas Tanghus, Bart Visscher
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;

use \OC\AppFramework\Controller\Controller;
use \OC\AppFramework\Middleware\MiddlewareDispatcher;


/**
* Class to dispatch the request to the middleware disptacher
*/
class Dispatcher {

private $middlewareDispatcher;
private $protocol;


/**
* @param Http $protocol the http protocol with contains all status headers
* @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
* runs the middleware
*/
public function __construct(Http $protocol,
MiddlewareDispatcher $middlewareDispatcher) {
$this->protocol = $protocol;
$this->middlewareDispatcher = $middlewareDispatcher;
}


/**
* Handles a request and calls the dispatcher on the controller
* @param Controller $controller the controller which will be called
* @param string $methodName the method name which will be called on
* the controller
* @return array $array[0] contains a string with the http main header,
* $array[1] contains headers in the form: $key => value, $array[2] contains
* the response output
*/
public function dispatch(Controller $controller, $methodName) {
$out = array(null, array(), null);

try {

$this->middlewareDispatcher->beforeController($controller,
$methodName);
$response = $controller->$methodName();


// if an exception appears, the middleware checks if it can handle the
// exception and creates a response. If no response is created, it is
// assumed that theres no middleware who can handle it and the error is
// thrown again
} catch(\Exception $exception){
$response = $this->middlewareDispatcher->afterException(
$controller, $methodName, $exception);
}

$response = $this->middlewareDispatcher->afterController(
$controller, $methodName, $response);

// get the output which should be printed and run the after output
// middleware to modify the response
$output = $response->render();
$out[2] = $this->middlewareDispatcher->beforeOutput(
$controller, $methodName, $output);

// depending on the cache object the headers need to be changed
$out[0] = $this->protocol->getStatusHeader($response->getStatus(),
$response->getLastModified(), $response->getETag());
$out[1] = $response->getHeaders();

return $out;
}


}

+ 51
- 0
lib/appframework/http/downloadresponse.php View File

@@ -0,0 +1,51 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


/**
* Prompts the user to download the a file
*/
abstract class DownloadResponse extends Response {

private $content;
private $filename;
private $contentType;

/**
* Creates a response that prompts the user to download the file
* @param string $filename the name that the downloaded file should have
* @param string $contentType the mimetype that the downloaded file should have
*/
public function __construct($filename, $contentType) {
$this->filename = $filename;
$this->contentType = $contentType;

$this->addHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
$this->addHeader('Content-Type', $contentType);
}


}

+ 208
- 0
lib/appframework/http/http.php View File

@@ -0,0 +1,208 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt, Thomas Tanghus, Bart Visscher
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


class Http {

const STATUS_CONTINUE = 100;
const STATUS_SWITCHING_PROTOCOLS = 101;
const STATUS_PROCESSING = 102;
const STATUS_OK = 200;
const STATUS_CREATED = 201;
const STATUS_ACCEPTED = 202;
const STATUS_NON_AUTHORATIVE_INFORMATION = 203;
const STATUS_NO_CONTENT = 204;
const STATUS_RESET_CONTENT = 205;
const STATUS_PARTIAL_CONTENT = 206;
const STATUS_MULTI_STATUS = 207;
const STATUS_ALREADY_REPORTED = 208;
const STATUS_IM_USED = 226;
const STATUS_MULTIPLE_CHOICES = 300;
const STATUS_MOVED_PERMANENTLY = 301;
const STATUS_FOUND = 302;
const STATUS_SEE_OTHER = 303;
const STATUS_NOT_MODIFIED = 304;
const STATUS_USE_PROXY = 305;
const STATUS_RESERVED = 306;
const STATUS_TEMPORARY_REDIRECT = 307;
const STATUS_BAD_REQUEST = 400;
const STATUS_UNAUTHORIZED = 401;
const STATUS_PAYMENT_REQUIRED = 402;
const STATUS_FORBIDDEN = 403;
const STATUS_NOT_FOUND = 404;
const STATUS_METHOD_NOT_ALLOWED = 405;
const STATUS_NOT_ACCEPTABLE = 406;
const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
const STATUS_REQUEST_TIMEOUT = 408;
const STATUS_CONFLICT = 409;
const STATUS_GONE = 410;
const STATUS_LENGTH_REQUIRED = 411;
const STATUS_PRECONDITION_FAILED = 412;
const STATUS_REQUEST_ENTITY_TOO_LARGE = 413;
const STATUS_REQUEST_URI_TOO_LONG = 414;
const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
const STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416;
const STATUS_EXPECTATION_FAILED = 417;
const STATUS_IM_A_TEAPOT = 418;
const STATUS_UNPROCESSABLE_ENTITY = 422;
const STATUS_LOCKED = 423;
const STATUS_FAILED_DEPENDENCY = 424;
const STATUS_UPGRADE_REQUIRED = 426;
const STATUS_PRECONDITION_REQUIRED = 428;
const STATUS_TOO_MANY_REQUESTS = 429;
const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
const STATUS_INTERNAL_SERVER_ERROR = 500;
const STATUS_NOT_IMPLEMENTED = 501;
const STATUS_BAD_GATEWAY = 502;
const STATUS_SERVICE_UNAVAILABLE = 503;
const STATUS_GATEWAY_TIMEOUT = 504;
const STATUS_HTTP_VERSION_NOT_SUPPORTED = 505;
const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
const STATUS_INSUFFICIENT_STORAGE = 507;
const STATUS_LOOP_DETECTED = 508;
const STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509;
const STATUS_NOT_EXTENDED = 510;
const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;

private $server;
private $protocolVersion;
protected $headers;

/**
* @param $_SERVER $server
* @param string $protocolVersion the http version to use defaults to HTTP/1.1
*/
public function __construct($server, $protocolVersion='HTTP/1.1') {
$this->server = $server;
$this->protocolVersion = $protocolVersion;

$this->headers = array(
self::STATUS_CONTINUE => 'Continue',
self::STATUS_SWITCHING_PROTOCOLS => 'Switching Protocols',
self::STATUS_PROCESSING => 'Processing',
self::STATUS_OK => 'OK',
self::STATUS_CREATED => 'Created',
self::STATUS_ACCEPTED => 'Accepted',
self::STATUS_NON_AUTHORATIVE_INFORMATION => 'Non-Authorative Information',
self::STATUS_NO_CONTENT => 'No Content',
self::STATUS_RESET_CONTENT => 'Reset Content',
self::STATUS_PARTIAL_CONTENT => 'Partial Content',
self::STATUS_MULTI_STATUS => 'Multi-Status', // RFC 4918
self::STATUS_ALREADY_REPORTED => 'Already Reported', // RFC 5842
self::STATUS_IM_USED => 'IM Used', // RFC 3229
self::STATUS_MULTIPLE_CHOICES => 'Multiple Choices',
self::STATUS_MOVED_PERMANENTLY => 'Moved Permanently',
self::STATUS_FOUND => 'Found',
self::STATUS_SEE_OTHER => 'See Other',
self::STATUS_NOT_MODIFIED => 'Not Modified',
self::STATUS_USE_PROXY => 'Use Proxy',
self::STATUS_RESERVED => 'Reserved',
self::STATUS_TEMPORARY_REDIRECT => 'Temporary Redirect',
self::STATUS_BAD_REQUEST => 'Bad request',
self::STATUS_UNAUTHORIZED => 'Unauthorized',
self::STATUS_PAYMENT_REQUIRED => 'Payment Required',
self::STATUS_FORBIDDEN => 'Forbidden',
self::STATUS_NOT_FOUND => 'Not Found',
self::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed',
self::STATUS_NOT_ACCEPTABLE => 'Not Acceptable',
self::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required',
self::STATUS_REQUEST_TIMEOUT => 'Request Timeout',
self::STATUS_CONFLICT => 'Conflict',
self::STATUS_GONE => 'Gone',
self::STATUS_LENGTH_REQUIRED => 'Length Required',
self::STATUS_PRECONDITION_FAILED => 'Precondition failed',
self::STATUS_REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large',
self::STATUS_REQUEST_URI_TOO_LONG => 'Request-URI Too Long',
self::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type',
self::STATUS_REQUEST_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable',
self::STATUS_EXPECTATION_FAILED => 'Expectation Failed',
self::STATUS_IM_A_TEAPOT => 'I\'m a teapot', // RFC 2324
self::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', // RFC 4918
self::STATUS_LOCKED => 'Locked', // RFC 4918
self::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', // RFC 4918
self::STATUS_UPGRADE_REQUIRED => 'Upgrade required',
self::STATUS_PRECONDITION_REQUIRED => 'Precondition required', // draft-nottingham-http-new-status
self::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', // draft-nottingham-http-new-status
self::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', // draft-nottingham-http-new-status
self::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error',
self::STATUS_NOT_IMPLEMENTED => 'Not Implemented',
self::STATUS_BAD_GATEWAY => 'Bad Gateway',
self::STATUS_SERVICE_UNAVAILABLE => 'Service Unavailable',
self::STATUS_GATEWAY_TIMEOUT => 'Gateway Timeout',
self::STATUS_HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported',
self::STATUS_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates',
self::STATUS_INSUFFICIENT_STORAGE => 'Insufficient Storage', // RFC 4918
self::STATUS_LOOP_DETECTED => 'Loop Detected', // RFC 5842
self::STATUS_BANDWIDTH_LIMIT_EXCEEDED => 'Bandwidth Limit Exceeded', // non-standard
self::STATUS_NOT_EXTENDED => 'Not extended',
self::STATUS_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', // draft-nottingham-http-new-status
);
}


/**
* Gets the correct header
* @param Http::CONSTANT $status the constant from the Http class
* @param \DateTime $lastModified formatted last modified date
* @param string $Etag the etag
*/
public function getStatusHeader($status, \DateTime $lastModified=null,
$ETag=null) {

if(!is_null($lastModified)) {
$lastModified = $lastModified->format(\DateTime::RFC2822);
}

// if etag or lastmodified have not changed, return a not modified
if ((isset($this->server['HTTP_IF_NONE_MATCH'])
&& trim($this->server['HTTP_IF_NONE_MATCH']) === $ETag)

||

(isset($this->server['HTTP_IF_MODIFIED_SINCE'])
&& trim($this->server['HTTP_IF_MODIFIED_SINCE']) ===
$lastModified)) {

$status = self::STATUS_NOT_MODIFIED;
}

// we have one change currently for the http 1.0 header that differs
// from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND
// if this differs any more, we want to create childclasses for this
if($status === self::STATUS_TEMPORARY_REDIRECT
&& $this->protocolVersion === 'HTTP/1.0') {

$status = self::STATUS_FOUND;
}

return $this->protocolVersion . ' ' . $status . ' ' .
$this->headers[$status];
}


}



+ 74
- 0
lib/appframework/http/jsonresponse.php View File

@@ -0,0 +1,74 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


/**
* A renderer for JSON calls
*/
class JSONResponse extends Response {

protected $data;


/**
* @param array|object $data the object or array that should be transformed
* @param int $statusCode the Http status code, defaults to 200
*/
public function __construct($data=array(), $statusCode=Http::STATUS_OK) {
$this->data = $data;
$this->setStatus($statusCode);
$this->addHeader('X-Content-Type-Options', 'nosniff');
$this->addHeader('Content-type', 'application/json; charset=utf-8');
}


/**
* Returns the rendered json
* @return string the rendered json
*/
public function render(){
return json_encode($this->data);
}

/**
* Sets values in the data json array
* @param array|object $params an array or object which will be transformed
* to JSON
*/
public function setData($data){
$this->data = $data;
}


/**
* Used to get the set parameters
* @return array the data
*/
public function getData(){
return $this->data;
}

}

+ 54
- 0
lib/appframework/http/redirectresponse.php View File

@@ -0,0 +1,54 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


/**
* Redirects to a different URL
*/
class RedirectResponse extends Response {

private $redirectURL;

/**
* Creates a response that redirects to a url
* @param string $redirectURL the url to redirect to
*/
public function __construct($redirectURL) {
$this->redirectURL = $redirectURL;
$this->setStatus(Http::STATUS_TEMPORARY_REDIRECT);
$this->addHeader('Location', $redirectURL);
}


/**
* @return string the url to redirect
*/
public function getRedirectURL() {
return $this->redirectURL;
}


}

+ 217
- 0
lib/appframework/http/request.php View File

@@ -0,0 +1,217 @@
<?php
/**
* ownCloud - Request
*
* @author Thomas Tanghus
* @copyright 2013 Thomas Tanghus (thomas@tanghus.net)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OC\AppFramework\Http;

/**
* Class for accessing variables in the request.
* This class provides an immutable object with request variables.
*/

class Request implements \ArrayAccess, \Countable {

protected $items = array();
protected $allowedKeys = array(
'get',
'post',
'files',
'server',
'env',
'session',
'cookies',
'urlParams',
'params',
'parameters',
'method'
);

/**
* @param array $vars An associative array with the following optional values:
* @param array 'params' the parsed json array
* @param array 'urlParams' the parameters which were matched from the URL
* @param array 'get' the $_GET array
* @param array 'post' the $_POST array
* @param array 'files' the $_FILES array
* @param array 'server' the $_SERVER array
* @param array 'env' the $_ENV array
* @param array 'session' the $_SESSION array
* @param array 'cookies' the $_COOKIE array
* @param string 'method' the request method (GET, POST etc)
* @see http://www.php.net/manual/en/reserved.variables.php
*/
public function __construct(array $vars=array()) {

foreach($this->allowedKeys as $name) {
$this->items[$name] = isset($vars[$name])
? $vars[$name]
: array();
}

$this->items['parameters'] = array_merge(
$this->items['params'],
$this->items['get'],
$this->items['post'],
$this->items['urlParams']
);

}

// Countable method.
public function count() {
return count(array_keys($this->items['parameters']));
}

/**
* ArrayAccess methods
*
* Gives access to the combined GET, POST and urlParams arrays
*
* Examples:
*
* $var = $request['myvar'];
*
* or
*
* if(!isset($request['myvar']) {
* // Do something
* }
*
* $request['myvar'] = 'something'; // This throws an exception.
*
* @param string $offset The key to lookup
* @return string|null
*/
public function offsetExists($offset) {
return isset($this->items['parameters'][$offset]);
}

/**
* @see offsetExists
*/
public function offsetGet($offset) {
return isset($this->items['parameters'][$offset])
? $this->items['parameters'][$offset]
: null;
}

/**
* @see offsetExists
*/
public function offsetSet($offset, $value) {
throw new \RuntimeException('You cannot change the contents of the request object');
}

/**
* @see offsetExists
*/
public function offsetUnset($offset) {
throw new \RuntimeException('You cannot change the contents of the request object');
}

// Magic property accessors
public function __set($name, $value) {
throw new \RuntimeException('You cannot change the contents of the request object');
}

/**
* Access request variables by method and name.
* Examples:
*
* $request->post['myvar']; // Only look for POST variables
* $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
* Looks in the combined GET, POST and urlParams array.
*
* if($request->method !== 'POST') {
* throw new Exception('This function can only be invoked using POST');
* }
*
* @param string $name The key to look for.
* @return mixed|null
*/
public function __get($name) {
switch($name) {
case 'get':
case 'post':
case 'files':
case 'server':
case 'env':
case 'session':
case 'cookies':
case 'parameters':
case 'params':
case 'urlParams':
return isset($this->items[$name])
? $this->items[$name]
: null;
break;
case 'method':
return $this->items['method'];
break;
default;
return isset($this[$name])
? $this[$name]
: null;
break;
}
}


public function __isset($name) {
return isset($this->items['parameters'][$name]);
}


public function __unset($id) {
throw new \RunTimeException('You cannot change the contents of the request object');
}

/**
* Returns the value for a specific http header.
*
* This method returns null if the header did not exist.
*
* @param string $name
* @return string
*/
public function getHeader($name) {

$name = strtoupper(str_replace(array('-'),array('_'),$name));
if (isset($this->server['HTTP_' . $name])) {
return $this->server['HTTP_' . $name];
}

// There's a few headers that seem to end up in the top-level
// server array.
switch($name) {
case 'CONTENT_TYPE' :
case 'CONTENT_LENGTH' :
if (isset($this->server[$name])) {
return $this->server[$name];
}
break;

}

return null;
}

}

+ 169
- 0
lib/appframework/http/response.php View File

@@ -0,0 +1,169 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt, Thomas Tanghus, Bart Visscher
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


/**
* Base class for responses. Also used to just send headers
*/
class Response {

/**
* @var array default headers
*/
private $headers = array(
'Cache-Control' => 'no-cache, must-revalidate'
);


/**
* @var string
*/
private $status = Http::STATUS_OK;


/**
* @var \DateTime
*/
private $lastModified;


/**
* @var string
*/
private $ETag;


/**
* Caches the response
* @param int $cacheSeconds the amount of seconds that should be cached
* if 0 then caching will be disabled
*/
public function cacheFor($cacheSeconds) {

if($cacheSeconds > 0) {
$this->addHeader('Cache-Control', 'max-age=' . $cacheSeconds .
', must-revalidate');
} else {
$this->addHeader('Cache-Control', 'no-cache, must-revalidate');
}

}


/**
* Adds a new header to the response that will be called before the render
* function
* @param string $name The name of the HTTP header
* @param string $value The value, null will delete it
*/
public function addHeader($name, $value) {
if(is_null($value)) {
unset($this->headers[$name]);
} else {
$this->headers[$name] = $value;
}
}


/**
* Returns the set headers
* @return array the headers
*/
public function getHeaders() {
$mergeWith = array();
if($this->lastModified) {
$mergeWith['Last-Modified'] =
$this->lastModified->format(\DateTime::RFC2822);
}

if($this->ETag) {
$mergeWith['ETag'] = '"' . $this->ETag . '"';
}
return array_merge($mergeWith, $this->headers);
}


/**
* By default renders no output
* @return null
*/
public function render() {
return null;
}


/**
* Set response status
* @param int $status a HTTP status code, see also the STATUS constants
*/
public function setStatus($status) {
$this->status = $status;
}


/**
* Get response status
*/
public function getStatus() {
return $this->status;
}


/**
* @return string the etag
*/
public function getETag() {
return $this->ETag;
}


/**
* @return string RFC2822 formatted last modified date
*/
public function getLastModified() {
return $this->lastModified;
}


/**
* @param string $ETag
*/
public function setETag($ETag) {
$this->ETag = $ETag;
}


/**
* @param \DateTime $lastModified
*/
public function setLastModified($lastModified) {
$this->lastModified = $lastModified;
}


}

+ 126
- 0
lib/appframework/http/templateresponse.php View File

@@ -0,0 +1,126 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;

use OC\AppFramework\Core\API;


/**
* Response for a normal template
*/
class TemplateResponse extends Response {

protected $templateName;
protected $params;
protected $api;
protected $renderAs;
protected $appName;

/**
* @param API $api an API instance
* @param string $templateName the name of the template
* @param string $appName optional if you want to include a template from
* a different app
*/
public function __construct(API $api, $templateName, $appName=null) {
$this->templateName = $templateName;
$this->appName = $appName;
$this->api = $api;
$this->params = array();
$this->renderAs = 'user';
}


/**
* Sets template parameters
* @param array $params an array with key => value structure which sets template
* variables
*/
public function setParams(array $params){
$this->params = $params;
}


/**
* Used for accessing the set parameters
* @return array the params
*/
public function getParams(){
return $this->params;
}


/**
* Used for accessing the name of the set template
* @return string the name of the used template
*/
public function getTemplateName(){
return $this->templateName;
}


/**
* Sets the template page
* @param string $renderAs admin, user or blank. Admin also prints the admin
* settings header and footer, user renders the normal
* normal page including footer and header and blank
* just renders the plain template
*/
public function renderAs($renderAs){
$this->renderAs = $renderAs;
}


/**
* Returns the set renderAs
* @return string the renderAs value
*/
public function getRenderAs(){
return $this->renderAs;
}


/**
* Returns the rendered html
* @return string the rendered html
*/
public function render(){

if($this->appName !== null){
$appName = $this->appName;
} else {
$appName = $this->api->getAppName();
}

$template = $this->api->getTemplate($this->templateName, $this->renderAs, $appName);

foreach($this->params as $key => $value){
$template->assign($key, $value);
}

return $template->fetchPage();
}

}

+ 100
- 0
lib/appframework/middleware/middleware.php View File

@@ -0,0 +1,100 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Middleware;

use OC\AppFramework\Http\Response;


/**
* Middleware is used to provide hooks before or after controller methods and
* deal with possible exceptions raised in the controller methods.
* They're modeled after Django's middleware system:
* https://docs.djangoproject.com/en/dev/topics/http/middleware/
*/
abstract class Middleware {


/**
* This is being run in normal order before the controller is being
* called which allows several modifications and checks
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
*/
public function beforeController($controller, $methodName){

}


/**
* This is being run when either the beforeController method or the
* controller method itself is throwing an exception. The middleware is
* asked in reverse order to handle the exception and to return a response.
* If the response is null, it is assumed that the exception could not be
* handled and the error will be thrown again
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @throws \Exception the passed in exception if it cant handle it
* @return Response a Response object in case that the exception was handled
*/
public function afterException($controller, $methodName, \Exception $exception){
throw $exception;
}


/**
* This is being run after a successful controllermethod call and allows
* the manipulation of a Response object. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param Response $response the generated response from the controller
* @return Response a Response object
*/
public function afterController($controller, $methodName, Response $response){
return $response;
}


/**
* This is being run after the response object has been rendered and
* allows the manipulation of the output. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param string $output the generated output from a response
* @return string the output that should be printed
*/
public function beforeOutput($controller, $methodName, $output){
return $output;
}

}

+ 159
- 0
lib/appframework/middleware/middlewaredispatcher.php View File

@@ -0,0 +1,159 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Middleware;

use OC\AppFramework\Controller\Controller;
use OC\AppFramework\Http\Response;


/**
* This class is used to store and run all the middleware in correct order
*/
class MiddlewareDispatcher {

/**
* @var array array containing all the middlewares
*/
private $middlewares;

/**
* @var int counter which tells us what middlware was executed once an
* exception occurs
*/
private $middlewareCounter;


/**
* Constructor
*/
public function __construct(){
$this->middlewares = array();
$this->middlewareCounter = 0;
}


/**
* Adds a new middleware
* @param Middleware $middleware the middleware which will be added
*/
public function registerMiddleware(Middleware $middleWare){
array_push($this->middlewares, $middleWare);
}


/**
* returns an array with all middleware elements
* @return array the middlewares
*/
public function getMiddlewares(){
return $this->middlewares;
}


/**
* This is being run in normal order before the controller is being
* called which allows several modifications and checks
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
*/
public function beforeController(Controller $controller, $methodName){
// we need to count so that we know which middlewares we have to ask in
// case theres an exception
for($i=0; $i<count($this->middlewares); $i++){
$this->middlewareCounter++;
$middleware = $this->middlewares[$i];
$middleware->beforeController($controller, $methodName);
}
}


/**
* This is being run when either the beforeController method or the
* controller method itself is throwing an exception. The middleware is asked
* in reverse order to handle the exception and to return a response.
* If the response is null, it is assumed that the exception could not be
* handled and the error will be thrown again
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @return Response a Response object if the middleware can handle the
* exception
* @throws \Exception the passed in exception if it cant handle it
*/
public function afterException(Controller $controller, $methodName, \Exception $exception){
for($i=$this->middlewareCounter-1; $i>=0; $i--){
$middleware = $this->middlewares[$i];
try {
return $middleware->afterException($controller, $methodName, $exception);
} catch(\Exception $exception){
continue;
}
}
throw $exception;
}


/**
* This is being run after a successful controllermethod call and allows
* the manipulation of a Response object. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param Response $response the generated response from the controller
* @return Response a Response object
*/
public function afterController(Controller $controller, $methodName, Response $response){
for($i=count($this->middlewares)-1; $i>=0; $i--){
$middleware = $this->middlewares[$i];
$response = $middleware->afterController($controller, $methodName, $response);
}
return $response;
}


/**
* This is being run after the response object has been rendered and
* allows the manipulation of the output. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param string $output the generated output from a response
* @return string the output that should be printed
*/
public function beforeOutput(Controller $controller, $methodName, $output){
for($i=count($this->middlewares)-1; $i>=0; $i--){
$middleware = $this->middlewares[$i];
$output = $middleware->beforeOutput($controller, $methodName, $output);
}
return $output;
}

}

+ 41
- 0
lib/appframework/middleware/security/securityexception.php View File

@@ -0,0 +1,41 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Middleware\Security;


/**
* Thrown when the security middleware encounters a security problem
*/
class SecurityException extends \Exception {

/**
* @param string $msg the security error message
* @param bool $ajax true if it resulted because of an ajax request
*/
public function __construct($msg, $code = 0) {
parent::__construct($msg, $code);
}

}

+ 141
- 0
lib/appframework/middleware/security/securitymiddleware.php View File

@@ -0,0 +1,141 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Middleware\Security;

use OC\AppFramework\Controller\Controller;
use OC\AppFramework\Http\Http;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\Response;
use OC\AppFramework\Http\JSONResponse;
use OC\AppFramework\Http\RedirectResponse;
use OC\AppFramework\Utility\MethodAnnotationReader;
use OC\AppFramework\Middleware\Middleware;
use OC\AppFramework\Core\API;


/**
* Used to do all the authentication and checking stuff for a controller method
* It reads out the annotations of a controller method and checks which if
* security things should be checked and also handles errors in case a security
* check fails
*/
class SecurityMiddleware extends Middleware {

private $api;

/**
* @var \OC\AppFramework\Http\Request
*/
private $request;

/**
* @param API $api an instance of the api
*/
public function __construct(API $api, Request $request){
$this->api = $api;
$this->request = $request;
}


/**
* This runs all the security checks before a method call. The
* security checks are determined by inspecting the controller method
* annotations
* @param string/Controller $controller the controllername or string
* @param string $methodName the name of the method
* @throws SecurityException when a security check fails
*/
public function beforeController($controller, $methodName){

// get annotations from comments
$annotationReader = new MethodAnnotationReader($controller, $methodName);

// this will set the current navigation entry of the app, use this only
// for normal HTML requests and not for AJAX requests
$this->api->activateNavigationEntry();

// security checks
if(!$annotationReader->hasAnnotation('IsLoggedInExemption')) {
if(!$this->api->isLoggedIn()) {
throw new SecurityException('Current user is not logged in', Http::STATUS_UNAUTHORIZED);
}
}

if(!$annotationReader->hasAnnotation('IsAdminExemption')) {
if(!$this->api->isAdminUser($this->api->getUserId())) {
throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN);
}
}

if(!$annotationReader->hasAnnotation('IsSubAdminExemption')) {
if(!$this->api->isSubAdminUser($this->api->getUserId())) {
throw new SecurityException('Logged in user must be a subadmin', Http::STATUS_FORBIDDEN);
}
}

if(!$annotationReader->hasAnnotation('CSRFExemption')) {
if(!$this->api->passesCSRFCheck()) {
throw new SecurityException('CSRF check failed', Http::STATUS_PRECONDITION_FAILED);
}
}

}


/**
* If an SecurityException is being caught, ajax requests return a JSON error
* response and non ajax requests redirect to the index
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @throws \Exception the passed in exception if it cant handle it
* @return Response a Response object or null in case that the exception could not be handled
*/
public function afterException($controller, $methodName, \Exception $exception){
if($exception instanceof SecurityException){

if (stripos($this->request->getHeader('Accept'),'html')===false) {

$response = new JSONResponse(
array('message' => $exception->getMessage()),
$exception->getCode()
);
$this->api->log($exception->getMessage(), 'debug');
} else {

$url = $this->api->linkToAbsolute('index.php', ''); // TODO: replace with link to route
$response = new RedirectResponse($url);
$this->api->log($exception->getMessage(), 'debug');
}

return $response;

}

throw $exception;
}

}

+ 42
- 0
lib/appframework/routing/routeactionhandler.php View File

@@ -0,0 +1,42 @@
<?php
/**
* ownCloud - App Framework
*
* @author Thomas Müller
* @copyright 2013 Thomas Müller thomas.mueller@tmit.eu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OC\AppFramework\routing;

use \OC\AppFramework\App;
use \OC\AppFramework\DependencyInjection\DIContainer;

class RouteActionHandler {
private $controllerName;
private $actionName;
private $container;

public function __construct(DIContainer $container, $controllerName, $actionName) {
$this->controllerName = $controllerName;
$this->actionName = $actionName;
$this->container = $container;
}

public function __invoke($params) {
App::main($this->controllerName, $this->actionName, $params, $this->container);
}
}

+ 186
- 0
lib/appframework/routing/routeconfig.php View File

@@ -0,0 +1,186 @@
<?php
/**
* ownCloud - App Framework
*
* @author Thomas Müller
* @copyright 2013 Thomas Müller thomas.mueller@tmit.eu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OC\AppFramework\routing;

use OC\AppFramework\DependencyInjection\DIContainer;

/**
* Class RouteConfig
* @package OC\AppFramework\routing
*/
class RouteConfig {
private $container;
private $router;
private $routes;
private $appName;

/**
* @param \OC\AppFramework\DependencyInjection\DIContainer $container
* @param \OC_Router $router
* @param string $pathToYml
* @internal param $appName
*/
public function __construct(DIContainer $container, \OC_Router $router, $routes) {
$this->routes = $routes;
$this->container = $container;
$this->router = $router;
$this->appName = $container['AppName'];
}

/**
* The routes and resource will be registered to the \OC_Router
*/
public function register() {

// parse simple
$this->processSimpleRoutes($this->routes);

// parse resources
$this->processResources($this->routes);
}

/**
* Creates one route base on the give configuration
* @param $routes
* @throws \UnexpectedValueException
*/
private function processSimpleRoutes($routes)
{
$simpleRoutes = isset($routes['routes']) ? $routes['routes'] : array();
foreach ($simpleRoutes as $simpleRoute) {
$name = $simpleRoute['name'];
$url = $simpleRoute['url'];
$verb = isset($simpleRoute['verb']) ? strtoupper($simpleRoute['verb']) : 'GET';

$split = explode('#', $name, 2);
if (count($split) != 2) {
throw new \UnexpectedValueException('Invalid route name');
}
$controller = $split[0];
$action = $split[1];

$controllerName = $this->buildControllerName($controller);
$actionName = $this->buildActionName($action);

// register the route
$handler = new RouteActionHandler($this->container, $controllerName, $actionName);
$this->router->create($this->appName.'.'.$controller.'.'.$action, $url)->method($verb)->action($handler);
}
}

/**
* For a given name and url restful routes are created:
* - index
* - show
* - new
* - create
* - update
* - destroy
*
* @param $routes
*/
private function processResources($routes)
{
// declaration of all restful actions
$actions = array(
array('name' => 'index', 'verb' => 'GET', 'on-collection' => true),
array('name' => 'show', 'verb' => 'GET'),
array('name' => 'create', 'verb' => 'POST', 'on-collection' => true),
array('name' => 'update', 'verb' => 'PUT'),
array('name' => 'destroy', 'verb' => 'DELETE'),
);

$resources = isset($routes['resources']) ? $routes['resources'] : array();
foreach ($resources as $resource => $config) {

// the url parameter used as id to the resource
$resourceId = $this->buildResourceId($resource);
foreach($actions as $action) {
$url = $config['url'];
$method = $action['name'];
$verb = isset($action['verb']) ? strtoupper($action['verb']) : 'GET';
$collectionAction = isset($action['on-collection']) ? $action['on-collection'] : false;
if (!$collectionAction) {
$url = $url . '/' . $resourceId;
}
if (isset($action['url-postfix'])) {
$url = $url . '/' . $action['url-postfix'];
}

$controller = $resource;

$controllerName = $this->buildControllerName($controller);
$actionName = $this->buildActionName($method);

$routeName = $this->appName . '.' . strtolower($resource) . '.' . strtolower($method);

$this->router->create($routeName, $url)->method($verb)->action(
new RouteActionHandler($this->container, $controllerName, $actionName)
);
}
}
}

/**
* Based on a given route name the controller name is generated
* @param $controller
* @return string
*/
private function buildControllerName($controller)
{
return $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
}

/**
* Based on the action part of the route name the controller method name is generated
* @param $action
* @return string
*/
private function buildActionName($action) {
return $this->underScoreToCamelCase($action);
}

/**
* Generates the id used in the url part o the route url
* @param $resource
* @return string
*/
private function buildResourceId($resource) {
return '{'.$this->underScoreToCamelCase(rtrim($resource, 's')).'Id}';
}

/**
* Underscored strings are converted to camel case strings
* @param $str string
* @return string
*/
private function underScoreToCamelCase($str) {
$pattern = "/_[a-z]?/";
return preg_replace_callback(
$pattern,
function ($matches) {
return strtoupper(ltrim($matches[0], "_"));
},
$str);
}
}

+ 61
- 0
lib/appframework/utility/methodannotationreader.php View File

@@ -0,0 +1,61 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Utility;


/**
* Reads and parses annotations from doc comments
*/
class MethodAnnotationReader {

private $annotations;

/**
* @param object $object an object or classname
* @param string $method the method which we want to inspect for annotations
*/
public function __construct($object, $method){
$this->annotations = array();

$reflection = new \ReflectionMethod($object, $method);
$docs = $reflection->getDocComment();

// extract everything prefixed by @ and first letter uppercase
preg_match_all('/@([A-Z]\w+)/', $docs, $matches);
$this->annotations = $matches[1];
}


/**
* Check if a method contains an annotation
* @param string $name the name of the annotation
* @return bool true if the annotation is found
*/
public function hasAnnotation($name){
return in_array($name, $this->annotations);
}


}

+ 42
- 0
lib/appframework/utility/timefactory.php View File

@@ -0,0 +1,42 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Utility;


/**
* Needed to mock calls to time()
*/
class TimeFactory {


/**
* @return int the result of a call to time()
*/
public function getTime() {
return time();
}


}

+ 107
- 0
tests/lib/appframework/AppTest.php View File

@@ -0,0 +1,107 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework;

use OC\AppFramework\Http\Request;
use OC\AppFramework\Core\API;
use OC\AppFramework\Middleware\MiddlewareDispatcher;

// FIXME: loading pimpl correctly from 3rdparty repo
require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php';
require_once __DIR__ . "/classloader.php";


class AppTest extends \PHPUnit_Framework_TestCase {

private $container;
private $api;
private $controller;
private $dispatcher;
private $params;
private $headers;
private $output;
private $controllerName;
private $controllerMethod;

protected function setUp() {
$this->container = new \Pimple();
$this->controller = $this->getMockBuilder(
'OC\AppFramework\Controller\Controller')
->disableOriginalConstructor()
->getMock();
$this->dispatcher = $this->getMockBuilder(
'OC\AppFramework\Http\Dispatcher')
->disableOriginalConstructor()
->getMock();


$this->headers = array('key' => 'value');
$this->output = 'hi';
$this->controllerName = 'Controller';
$this->controllerMethod = 'method';

$this->container[$this->controllerName] = $this->controller;
$this->container['Dispatcher'] = $this->dispatcher;
}


public function testControllerNameAndMethodAreBeingPassed(){
$return = array(null, array(), null);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod))
->will($this->returnValue($return));

$this->expectOutputString('');

App::main($this->controllerName, $this->controllerMethod, array(),
$this->container);
}


/*
FIXME: this complains about shit headers which are already sent because
of the content length. Would be cool if someone could fix this

public function testOutputIsPrinted(){
$return = array(null, array(), $this->output);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod))
->will($this->returnValue($return));

$this->expectOutputString($this->output);

App::main($this->controllerName, $this->controllerMethod, array(),
$this->container);
}
*/

// FIXME: if someone manages to test the headers output, I'd be grateful


}

+ 45
- 0
tests/lib/appframework/classloader.php View File

@@ -0,0 +1,45 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/

// to execute without ownCloud, we need to create our own class loader
spl_autoload_register(function ($className){
if (strpos($className, 'OC\\AppFramework') === 0) {
$path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php');
$relPath = __DIR__ . '/../../../lib/' . $path;

if(file_exists($relPath)){
require_once $relPath;
}
}

// FIXME: this will most probably not work anymore
if (strpos($className, 'OCA\\') === 0) {

$path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php');
$relPath = __DIR__ . '/../..' . $path;

if(file_exists($relPath)){
require_once $relPath;
}
}
});

+ 161
- 0
tests/lib/appframework/controller/ControllerTest.php View File

@@ -0,0 +1,161 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace Test\AppFramework\Controller;

use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\JSONResponse;
use OC\AppFramework\Http\TemplateResponse;
use OC\AppFramework\Controller\Controller;


require_once(__DIR__ . "/../classloader.php");


class ChildController extends Controller {};

class ControllerTest extends \PHPUnit_Framework_TestCase {

/**
* @var Controller
*/
private $controller;
private $api;

protected function setUp(){
$request = new Request(
array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'),
'urlParams' => array('name' => 'Johnny Weissmüller'),
'files' => array('file' => 'filevalue'),
'env' => array('PATH' => 'daheim'),
'session' => array('sezession' => 'kein'),
'method' => 'hi',
)
);

$this->api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName'), array('test'));
$this->api->expects($this->any())
->method('getAppName')
->will($this->returnValue('apptemplate_advanced'));

$this->controller = new ChildController($this->api, $request);
}


public function testParamsGet(){
$this->assertEquals('Johnny Weissmüller', $this->controller->params('name', 'Tarzan'));
}


public function testParamsGetDefault(){
$this->assertEquals('Tarzan', $this->controller->params('Ape Man', 'Tarzan'));
}


public function testParamsFile(){
$this->assertEquals('filevalue', $this->controller->params('file', 'filevalue'));
}


public function testGetUploadedFile(){
$this->assertEquals('filevalue', $this->controller->getUploadedFile('file'));
}



public function testGetUploadedFileDefault(){
$this->assertEquals('default', $this->controller->params('files', 'default'));
}


public function testGetParams(){
$params = array(
'name' => 'Johnny Weissmüller',
'nickname' => 'Janey',
);

$this->assertEquals($params, $this->controller->getParams());
}


public function testRender(){
$this->assertTrue($this->controller->render('') instanceof TemplateResponse);
}


public function testSetParams(){
$params = array('john' => 'foo');
$response = $this->controller->render('home', $params);

$this->assertEquals($params, $response->getParams());
}


public function testRenderRenderAs(){
$ocTpl = $this->getMock('Template', array('fetchPage'));
$ocTpl->expects($this->once())
->method('fetchPage');

$api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName', 'getTemplate'), array('app'));
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));
$api->expects($this->once())
->method('getTemplate')
->with($this->equalTo('home'), $this->equalTo('admin'), $this->equalTo('app'))
->will($this->returnValue($ocTpl));

$this->controller = new ChildController($api, new Request());
$this->controller->render('home', array(), 'admin')->render();
}


public function testRenderHeaders(){
$headers = array('one', 'two');
$response = $this->controller->render('', array(), '', $headers);

$this->assertTrue(in_array($headers[0], $response->getHeaders()));
$this->assertTrue(in_array($headers[1], $response->getHeaders()));
}


public function testGetRequestMethod(){
$this->assertEquals('hi', $this->controller->method());
}


public function testGetEnvVariable(){
$this->assertEquals('daheim', $this->controller->env('PATH'));
}

public function testGetSessionVariable(){
$this->assertEquals('kein', $this->controller->session('sezession'));
}


}

+ 98
- 0
tests/lib/appframework/dependencyinjection/DIContainerTest.php View File

@@ -0,0 +1,98 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @author Morris Jobke
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
* @copyright 2013 Morris Jobke morris.jobke@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\DependencyInjection;

use \OC\AppFramework\Http\Request;


require_once(__DIR__ . "/../classloader.php");


class DIContainerTest extends \PHPUnit_Framework_TestCase {

private $container;

protected function setUp(){
$this->container = new DIContainer('name');
$this->api = $this->getMock('OC\AppFramework\Core\API', array('getTrans'), array('hi'));
}

private function exchangeAPI(){
$this->api->expects($this->any())
->method('getTrans')
->will($this->returnValue('yo'));
$this->container['API'] = $this->api;
}

public function testProvidesAPI(){
$this->assertTrue(isset($this->container['API']));
}


public function testProvidesRequest(){
$this->assertTrue(isset($this->container['Request']));
}


public function testProvidesSecurityMiddleware(){
$this->assertTrue(isset($this->container['SecurityMiddleware']));
}


public function testProvidesMiddlewareDispatcher(){
$this->assertTrue(isset($this->container['MiddlewareDispatcher']));
}


public function testProvidesAppName(){
$this->assertTrue(isset($this->container['AppName']));
}


public function testAppNameIsSetCorrectly(){
$this->assertEquals('name', $this->container['AppName']);
}


public function testMiddlewareDispatcherIncludesSecurityMiddleware(){
$this->container['Request'] = new Request();
$security = $this->container['SecurityMiddleware'];
$dispatcher = $this->container['MiddlewareDispatcher'];

$this->assertContains($security, $dispatcher->getMiddlewares());
}


public function testMiddlewareDispatcherDoesNotIncludeTwigWhenTplDirectoryNotSet(){
$this->container['Request'] = new Request();
$this->exchangeAPI();
$dispatcher = $this->container['MiddlewareDispatcher'];

$this->assertEquals(1, count($dispatcher->getMiddlewares()));
}

}

+ 218
- 0
tests/lib/appframework/http/DispatcherTest.php View File

@@ -0,0 +1,218 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;

use OC\AppFramework\Core\API;
use OC\AppFramework\Middleware\MiddlewareDispatcher;

require_once(__DIR__ . "/../classloader.php");


class DispatcherTest extends \PHPUnit_Framework_TestCase {


private $middlewareDispatcher;
private $dispatcher;
private $controllerMethod;
private $response;
private $lastModified;
private $etag;
private $http;

protected function setUp() {
$this->controllerMethod = 'test';

$api = $this->getMockBuilder(
'\OC\AppFramework\Core\API')
->disableOriginalConstructor()
->getMock();
$request = $this->getMockBuilder(
'\OC\AppFramework\Http\Request')
->disableOriginalConstructor()
->getMock();
$this->http = $this->getMockBuilder(
'\OC\AppFramework\Http\Http')
->disableOriginalConstructor()
->getMock();

$this->middlewareDispatcher = $this->getMockBuilder(
'\OC\AppFramework\Middleware\MiddlewareDispatcher')
->disableOriginalConstructor()
->getMock();
$this->controller = $this->getMock(
'\OC\AppFramework\Controller\Controller',
array($this->controllerMethod), array($api, $request));
$this->dispatcher = new Dispatcher(
$this->http, $this->middlewareDispatcher);
$this->response = $this->getMockBuilder(
'\OC\AppFramework\Http\Response')
->disableOriginalConstructor()
->getMock();

$this->lastModified = new \DateTime(null, new \DateTimeZone('GMT'));
$this->etag = 'hi';
}


private function setMiddlewareExpections($out=null,
$httpHeaders=null, $responseHeaders=array(),
$ex=false, $catchEx=true) {

if($ex) {
$exception = new \Exception();
$this->middlewareDispatcher->expects($this->once())
->method('beforeController')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod))
->will($this->throwException($exception));
if($catchEx) {
$this->middlewareDispatcher->expects($this->once())
->method('afterException')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod),
$this->equalTo($exception))
->will($this->returnValue($this->response));
} else {
$this->middlewareDispatcher->expects($this->once())
->method('afterException')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod),
$this->equalTo($exception))
->will($this->returnValue(null));
return;
}
} else {
$this->middlewareDispatcher->expects($this->once())
->method('beforeController')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod));
$this->controller->expects($this->once())
->method($this->controllerMethod)
->will($this->returnValue($this->response));
}

$this->response->expects($this->once())
->method('render')
->will($this->returnValue($out));
$this->response->expects($this->once())
->method('getStatus')
->will($this->returnValue(Http::STATUS_OK));
$this->response->expects($this->once())
->method('getLastModified')
->will($this->returnValue($this->lastModified));
$this->response->expects($this->once())
->method('getETag')
->will($this->returnValue($this->etag));
$this->response->expects($this->once())
->method('getHeaders')
->will($this->returnValue($responseHeaders));
$this->http->expects($this->once())
->method('getStatusHeader')
->with($this->equalTo(Http::STATUS_OK),
$this->equalTo($this->lastModified),
$this->equalTo($this->etag))
->will($this->returnValue($httpHeaders));
$this->middlewareDispatcher->expects($this->once())
->method('afterController')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod),
$this->equalTo($this->response))
->will($this->returnValue($this->response));

$this->middlewareDispatcher->expects($this->once())
->method('afterController')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod),
$this->equalTo($this->response))
->will($this->returnValue($this->response));

$this->middlewareDispatcher->expects($this->once())
->method('beforeOutput')
->with($this->equalTo($this->controller),
$this->equalTo($this->controllerMethod),
$this->equalTo($out))
->will($this->returnValue($out));

}


public function testDispatcherReturnsArrayWith2Entries() {
$this->setMiddlewareExpections();

$response = $this->dispatcher->dispatch($this->controller,
$this->controllerMethod);
$this->assertNull($response[0]);
$this->assertEquals(array(), $response[1]);
$this->assertNull($response[2]);
}


public function testHeadersAndOutputAreReturned(){
$out = 'yo';
$httpHeaders = 'Http';
$responseHeaders = array('hell' => 'yeah');
$this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders);

$response = $this->dispatcher->dispatch($this->controller,
$this->controllerMethod);

$this->assertEquals($httpHeaders, $response[0]);
$this->assertEquals($responseHeaders, $response[1]);
$this->assertEquals($out, $response[2]);
}


public function testExceptionCallsAfterException() {
$out = 'yo';
$httpHeaders = 'Http';
$responseHeaders = array('hell' => 'yeah');
$this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true);

$response = $this->dispatcher->dispatch($this->controller,
$this->controllerMethod);

$this->assertEquals($httpHeaders, $response[0]);
$this->assertEquals($responseHeaders, $response[1]);
$this->assertEquals($out, $response[2]);
}


public function testExceptionThrowsIfCanNotBeHandledByAfterException() {
$out = 'yo';
$httpHeaders = 'Http';
$responseHeaders = array('hell' => 'yeah');
$this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true, false);

$this->setExpectedException('\Exception');
$response = $this->dispatcher->dispatch($this->controller,
$this->controllerMethod);

}

}

+ 51
- 0
tests/lib/appframework/http/DownloadResponseTest.php View File

@@ -0,0 +1,51 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");


class ChildDownloadResponse extends DownloadResponse {};


class DownloadResponseTest extends \PHPUnit_Framework_TestCase {

protected $response;

protected function setUp(){
$this->response = new ChildDownloadResponse('file', 'content');
}


public function testHeaders() {
$headers = $this->response->getHeaders();

$this->assertContains('attachment; filename="file"', $headers['Content-Disposition']);
$this->assertContains('content', $headers['Content-Type']);
}


}

+ 87
- 0
tests/lib/appframework/http/HttpTest.php View File

@@ -0,0 +1,87 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");



class HttpTest extends \PHPUnit_Framework_TestCase {

private $server;
private $http;

protected function setUp(){
$this->server = array();
$this->http = new Http($this->server);
}


public function testProtocol() {
$header = $this->http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT);
$this->assertEquals('HTTP/1.1 307 Temporary Redirect', $header);
}


public function testProtocol10() {
$this->http = new Http($this->server, 'HTTP/1.0');
$header = $this->http->getStatusHeader(Http::STATUS_OK);
$this->assertEquals('HTTP/1.0 200 OK', $header);
}


public function testEtagMatchReturnsNotModified() {
$http = new Http(array('HTTP_IF_NONE_MATCH' => 'hi'));

$header = $http->getStatusHeader(Http::STATUS_OK, null, 'hi');
$this->assertEquals('HTTP/1.1 304 Not Modified', $header);
}


public function testLastModifiedMatchReturnsNotModified() {
$dateTime = new \DateTime(null, new \DateTimeZone('GMT'));
$dateTime->setTimestamp('12');

$http = new Http(
array(
'HTTP_IF_MODIFIED_SINCE' => 'Thu, 01 Jan 1970 00:00:12 +0000')
);

$header = $http->getStatusHeader(Http::STATUS_OK, $dateTime);
$this->assertEquals('HTTP/1.1 304 Not Modified', $header);
}



public function testTempRedirectBecomesFoundInHttp10() {
$http = new Http(array(), 'HTTP/1.0');

$header = $http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT);
$this->assertEquals('HTTP/1.0 302 Found', $header);
}
// TODO: write unittests for http codes

}

+ 96
- 0
tests/lib/appframework/http/JSONResponseTest.php View File

@@ -0,0 +1,96 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @author Morris Jobke
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
* @copyright 2013 Morris Jobke morris.jobke@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");



class JSONResponseTest extends \PHPUnit_Framework_TestCase {

/**
* @var JSONResponse
*/
private $json;

protected function setUp() {
$this->json = new JSONResponse();
}


public function testHeader() {
$headers = $this->json->getHeaders();
$this->assertEquals('application/json; charset=utf-8', $headers['Content-type']);
}


public function testSetData() {
$params = array('hi', 'yo');
$this->json->setData($params);

$this->assertEquals(array('hi', 'yo'), $this->json->getData());
}


public function testSetRender() {
$params = array('test' => 'hi');
$this->json->setData($params);

$expected = '{"test":"hi"}';

$this->assertEquals($expected, $this->json->render());
}


public function testRender() {
$params = array('test' => 'hi');
$this->json->setData($params);

$expected = '{"test":"hi"}';

$this->assertEquals($expected, $this->json->render());
}


public function testShouldHaveXContentHeaderByDefault() {
$headers = $this->json->getHeaders();
$this->assertEquals('nosniff', $headers['X-Content-Type-Options']);
}


public function testConstructorAllowsToSetData() {
$data = array('hi');
$code = 300;
$response = new JSONResponse($data, $code);

$expected = '["hi"]';
$this->assertEquals($expected, $response->render());
$this->assertEquals($code, $response->getStatus());
}

}

+ 55
- 0
tests/lib/appframework/http/RedirectResponseTest.php View File

@@ -0,0 +1,55 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");



class RedirectResponseTest extends \PHPUnit_Framework_TestCase {


protected $response;

protected function setUp(){
$this->response = new RedirectResponse('/url');
}


public function testHeaders() {
$headers = $this->response->getHeaders();
$this->assertEquals('/url', $headers['Location']);
$this->assertEquals(Http::STATUS_TEMPORARY_REDIRECT,
$this->response->getStatus());
}


public function testGetRedirectUrl(){
$this->assertEquals('/url', $this->response->getRedirectUrl());
}


}

+ 78
- 0
tests/lib/appframework/http/RequestTest.php View File

@@ -0,0 +1,78 @@
<?php
/**
* Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net)
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/

namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");

class RequestTest extends \PHPUnit_Framework_TestCase {

public function testRequestAccessors() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);

$request = new Request($vars);

// Countable
$this->assertEquals(2, count($request));
// Array access
$this->assertEquals('Joey', $request['nickname']);
// "Magic" accessors
$this->assertEquals('Joey', $request->{'nickname'});
$this->assertTrue(isset($request['nickname']));
$this->assertTrue(isset($request->{'nickname'}));
$this->assertEquals(false, isset($request->{'flickname'}));
// Only testing 'get', but same approach for post, files etc.
$this->assertEquals('Joey', $request->get['nickname']);
// Always returns null if variable not set.
$this->assertEquals(null, $request->{'flickname'});
}

// urlParams has precedence over POST which has precedence over GET
public function testPrecedence() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'),
'urlParams' => array('user' => 'jw', 'name' => 'Johnny Weissmüller'),
);

$request = new Request($vars);

$this->assertEquals(3, count($request));
$this->assertEquals('Janey', $request->{'nickname'});
$this->assertEquals('Johnny Weissmüller', $request->{'name'});
}


/**
* @expectedException RuntimeException
*/
public function testImmutableArrayAccess() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);

$request = new Request($vars);
$request['nickname'] = 'Janey';
}

/**
* @expectedException RuntimeException
*/
public function testImmutableMagicAccess() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);

$request = new Request($vars);
$request->{'nickname'} = 'Janey';
}

}

+ 119
- 0
tests/lib/appframework/http/ResponseTest.php View File

@@ -0,0 +1,119 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;


require_once(__DIR__ . "/../classloader.php");



class ResponseTest extends \PHPUnit_Framework_TestCase {


private $childResponse;

protected function setUp(){
$this->childResponse = new Response();
}


public function testAddHeader(){
$this->childResponse->addHeader('hello', 'world');
$headers = $this->childResponse->getHeaders();
$this->assertEquals('world', $headers['hello']);
}


public function testAddHeaderValueNullDeletesIt(){
$this->childResponse->addHeader('hello', 'world');
$this->childResponse->addHeader('hello', null);
$this->assertEquals(1, count($this->childResponse->getHeaders()));
}


public function testCacheHeadersAreDisabledByDefault(){
$headers = $this->childResponse->getHeaders();
$this->assertEquals('no-cache, must-revalidate', $headers['Cache-Control']);
}


public function testRenderReturnNullByDefault(){
$this->assertEquals(null, $this->childResponse->render());
}


public function testGetStatus() {
$default = $this->childResponse->getStatus();

$this->childResponse->setStatus(Http::STATUS_NOT_FOUND);

$this->assertEquals(Http::STATUS_OK, $default);
$this->assertEquals(Http::STATUS_NOT_FOUND, $this->childResponse->getStatus());
}


public function testGetEtag() {
$this->childResponse->setEtag('hi');
$this->assertEquals('hi', $this->childResponse->getEtag());
}


public function testGetLastModified() {
$lastModified = new \DateTime(null, new \DateTimeZone('GMT'));
$lastModified->setTimestamp(1);
$this->childResponse->setLastModified($lastModified);
$this->assertEquals($lastModified, $this->childResponse->getLastModified());
}



public function testCacheSecondsZero() {
$this->childResponse->cacheFor(0);
$headers = $this->childResponse->getHeaders();
$this->assertEquals('no-cache, must-revalidate', $headers['Cache-Control']);
}


public function testCacheSeconds() {
$this->childResponse->cacheFor(33);
$headers = $this->childResponse->getHeaders();
$this->assertEquals('max-age=33, must-revalidate',
$headers['Cache-Control']);
}



public function testEtagLastModifiedHeaders() {
$lastModified = new \DateTime(null, new \DateTimeZone('GMT'));
$lastModified->setTimestamp(1);
$this->childResponse->setLastModified($lastModified);
$headers = $this->childResponse->getHeaders();
$this->assertEquals('Thu, 01 Jan 1970 00:00:01 +0000', $headers['Last-Modified']);
}


}

+ 157
- 0
tests/lib/appframework/http/TemplateResponseTest.php View File

@@ -0,0 +1,157 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Http;

use OC\AppFramework\Core\API;


require_once(__DIR__ . "/../classloader.php");


class TemplateResponseTest extends \PHPUnit_Framework_TestCase {

private $tpl;
private $api;

protected function setUp() {
$this->api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName'), array('test'));
$this->api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));

$this->tpl = new TemplateResponse($this->api, 'home');
}


public function testSetParams(){
$params = array('hi' => 'yo');
$this->tpl->setParams($params);

$this->assertEquals(array('hi' => 'yo'), $this->tpl->getParams());
}


public function testGetTemplateName(){
$this->assertEquals('home', $this->tpl->getTemplateName());
}


public function testRender(){
$ocTpl = $this->getMock('Template', array('fetchPage'));
$ocTpl->expects($this->once())
->method('fetchPage');

$api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName', 'getTemplate'), array('app'));
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));
$api->expects($this->once())
->method('getTemplate')
->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app'))
->will($this->returnValue($ocTpl));

$tpl = new TemplateResponse($api, 'home');

$tpl->render();
}


public function testRenderAssignsParams(){
$params = array('john' => 'doe');

$ocTpl = $this->getMock('Template', array('assign', 'fetchPage'));
$ocTpl->expects($this->once())
->method('assign')
->with($this->equalTo('john'), $this->equalTo('doe'));

$api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName', 'getTemplate'), array('app'));
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));
$api->expects($this->once())
->method('getTemplate')
->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app'))
->will($this->returnValue($ocTpl));

$tpl = new TemplateResponse($api, 'home');
$tpl->setParams($params);

$tpl->render();
}


public function testRenderDifferentApp(){
$ocTpl = $this->getMock('Template', array('fetchPage'));
$ocTpl->expects($this->once())
->method('fetchPage');

$api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName', 'getTemplate'), array('app'));
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));
$api->expects($this->once())
->method('getTemplate')
->with($this->equalTo('home'), $this->equalTo('user'), $this->equalTo('app2'))
->will($this->returnValue($ocTpl));

$tpl = new TemplateResponse($api, 'home', 'app2');

$tpl->render();
}


public function testRenderDifferentRenderAs(){
$ocTpl = $this->getMock('Template', array('fetchPage'));
$ocTpl->expects($this->once())
->method('fetchPage');

$api = $this->getMock('OC\AppFramework\Core\API',
array('getAppName', 'getTemplate'), array('app'));
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('app'));
$api->expects($this->once())
->method('getTemplate')
->with($this->equalTo('home'), $this->equalTo('admin'), $this->equalTo('app'))
->will($this->returnValue($ocTpl));

$tpl = new TemplateResponse($api, 'home');
$tpl->renderAs('admin');

$tpl->render();
}


public function testGetRenderAs(){
$render = 'myrender';
$this->tpl->renderAs($render);
$this->assertEquals($render, $this->tpl->getRenderAs());
}

}

+ 280
- 0
tests/lib/appframework/middleware/MiddlewareDispatcherTest.php View File

@@ -0,0 +1,280 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework;

use OC\AppFramework\Controller\Controller;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\Response;
use OC\AppFramework\Middleware\Middleware;
use OC\AppFramework\Middleware\MiddlewareDispatcher;


require_once(__DIR__ . "/../classloader.php");


// needed to test ordering
class TestMiddleware extends Middleware {
public static $beforeControllerCalled = 0;
public static $afterControllerCalled = 0;
public static $afterExceptionCalled = 0;
public static $beforeOutputCalled = 0;

public $beforeControllerOrder = 0;
public $afterControllerOrder = 0;
public $afterExceptionOrder = 0;
public $beforeOutputOrder = 0;

public $controller;
public $methodName;
public $exception;
public $response;
public $output;

private $beforeControllerThrowsEx;

public function __construct($beforeControllerThrowsEx) {
self::$beforeControllerCalled = 0;
self::$afterControllerCalled = 0;
self::$afterExceptionCalled = 0;
self::$beforeOutputCalled = 0;
$this->beforeControllerThrowsEx = $beforeControllerThrowsEx;
}

public function beforeController($controller, $methodName){
self::$beforeControllerCalled++;
$this->beforeControllerOrder = self::$beforeControllerCalled;
$this->controller = $controller;
$this->methodName = $methodName;
if($this->beforeControllerThrowsEx){
throw new \Exception();
}
}

public function afterException($controller, $methodName, \Exception $exception){
self::$afterExceptionCalled++;
$this->afterExceptionOrder = self::$afterExceptionCalled;
$this->controller = $controller;
$this->methodName = $methodName;
$this->exception = $exception;
parent::afterException($controller, $methodName, $exception);
}

public function afterController($controller, $methodName, Response $response){
self::$afterControllerCalled++;
$this->afterControllerOrder = self::$afterControllerCalled;
$this->controller = $controller;
$this->methodName = $methodName;
$this->response = $response;
return parent::afterController($controller, $methodName, $response);
}

public function beforeOutput($controller, $methodName, $output){
self::$beforeOutputCalled++;
$this->beforeOutputOrder = self::$beforeOutputCalled;
$this->controller = $controller;
$this->methodName = $methodName;
$this->output = $output;
return parent::beforeOutput($controller, $methodName, $output);
}
}


class MiddlewareDispatcherTest extends \PHPUnit_Framework_TestCase {

private $dispatcher;


public function setUp() {
$this->dispatcher = new MiddlewareDispatcher();
$this->controller = $this->getControllerMock();
$this->method = 'method';
$this->response = new Response();
$this->output = 'hi';
$this->exception = new \Exception();
}


private function getAPIMock(){
return $this->getMock('OC\AppFramework\Core\API',
array('getAppName'), array('app'));
}


private function getControllerMock(){
return $this->getMock('OC\AppFramework\Controller\Controller', array('method'),
array($this->getAPIMock(), new Request()));
}


private function getMiddleware($beforeControllerThrowsEx=false){
$m1 = new TestMiddleware($beforeControllerThrowsEx);
$this->dispatcher->registerMiddleware($m1);
return $m1;
}


public function testAfterExceptionShouldReturnResponseOfMiddleware(){
$response = new Response();
$m1 = $this->getMock('\OC\AppFramework\Middleware\Middleware',
array('afterException', 'beforeController'));
$m1->expects($this->never())
->method('afterException');

$m2 = $this->getMock('OC\AppFramework\Middleware\Middleware',
array('afterException', 'beforeController'));
$m2->expects($this->once())
->method('afterException')
->will($this->returnValue($response));

$this->dispatcher->registerMiddleware($m1);
$this->dispatcher->registerMiddleware($m2);

$this->dispatcher->beforeController($this->controller, $this->method);
$this->assertEquals($response, $this->dispatcher->afterException($this->controller, $this->method, $this->exception));
}


public function testAfterExceptionShouldThrowAgainWhenNotHandled(){
$m1 = new TestMiddleware(false);
$m2 = new TestMiddleware(true);

$this->dispatcher->registerMiddleware($m1);
$this->dispatcher->registerMiddleware($m2);

$this->setExpectedException('\Exception');
$this->dispatcher->beforeController($this->controller, $this->method);
$this->dispatcher->afterException($this->controller, $this->method, $this->exception);
}


public function testBeforeControllerCorrectArguments(){
$m1 = $this->getMiddleware();
$this->dispatcher->beforeController($this->controller, $this->method);

$this->assertEquals($this->controller, $m1->controller);
$this->assertEquals($this->method, $m1->methodName);
}


public function testAfterControllerCorrectArguments(){
$m1 = $this->getMiddleware();

$this->dispatcher->afterController($this->controller, $this->method, $this->response);

$this->assertEquals($this->controller, $m1->controller);
$this->assertEquals($this->method, $m1->methodName);
$this->assertEquals($this->response, $m1->response);
}


public function testAfterExceptionCorrectArguments(){
$m1 = $this->getMiddleware();

$this->setExpectedException('\Exception');

$this->dispatcher->beforeController($this->controller, $this->method);
$this->dispatcher->afterException($this->controller, $this->method, $this->exception);

$this->assertEquals($this->controller, $m1->controller);
$this->assertEquals($this->method, $m1->methodName);
$this->assertEquals($this->exception, $m1->exception);
}


public function testBeforeOutputCorrectArguments(){
$m1 = $this->getMiddleware();

$this->dispatcher->beforeOutput($this->controller, $this->method, $this->output);

$this->assertEquals($this->controller, $m1->controller);
$this->assertEquals($this->method, $m1->methodName);
$this->assertEquals($this->output, $m1->output);
}


public function testBeforeControllerOrder(){
$m1 = $this->getMiddleware();
$m2 = $this->getMiddleware();

$this->dispatcher->beforeController($this->controller, $this->method);

$this->assertEquals(1, $m1->beforeControllerOrder);
$this->assertEquals(2, $m2->beforeControllerOrder);
}

public function testAfterControllerOrder(){
$m1 = $this->getMiddleware();
$m2 = $this->getMiddleware();

$this->dispatcher->afterController($this->controller, $this->method, $this->response);

$this->assertEquals(2, $m1->afterControllerOrder);
$this->assertEquals(1, $m2->afterControllerOrder);
}


public function testAfterExceptionOrder(){
$m1 = $this->getMiddleware();
$m2 = $this->getMiddleware();

$this->setExpectedException('\Exception');
$this->dispatcher->beforeController($this->controller, $this->method);
$this->dispatcher->afterException($this->controller, $this->method, $this->exception);

$this->assertEquals(1, $m1->afterExceptionOrder);
$this->assertEquals(1, $m2->afterExceptionOrder);
}


public function testBeforeOutputOrder(){
$m1 = $this->getMiddleware();
$m2 = $this->getMiddleware();

$this->dispatcher->beforeOutput($this->controller, $this->method, $this->output);

$this->assertEquals(2, $m1->beforeOutputOrder);
$this->assertEquals(1, $m2->beforeOutputOrder);
}


public function testExceptionShouldRunAfterExceptionOfOnlyPreviouslyExecutedMiddlewares(){
$m1 = $this->getMiddleware();
$m2 = $this->getMiddleware(true);
$m3 = $this->getMock('\OC\AppFramework\Middleware\Middleware');
$m3->expects($this->never())
->method('afterException');
$m3->expects($this->never())
->method('beforeController');
$m3->expects($this->never())
->method('afterController');

$this->dispatcher->registerMiddleware($m3);

$this->dispatcher->beforeOutput($this->controller, $this->method, $this->output);

$this->assertEquals(2, $m1->beforeOutputOrder);
$this->assertEquals(1, $m2->beforeOutputOrder);
}
}

+ 82
- 0
tests/lib/appframework/middleware/MiddlewareTest.php View File

@@ -0,0 +1,82 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework;

use OC\AppFramework\Http\Request;
use OC\AppFramework\Middleware\Middleware;


require_once(__DIR__ . "/../classloader.php");


class ChildMiddleware extends Middleware {};


class MiddlewareTest extends \PHPUnit_Framework_TestCase {

private $middleware;
private $controller;
private $exception;
private $api;

protected function setUp(){
$this->middleware = new ChildMiddleware();

$this->api = $this->getMock('OC\AppFramework\Core\API',
array(), array('test'));

$this->controller = $this->getMock('OC\AppFramework\Controller\Controller',
array(), array($this->api, new Request()));
$this->exception = new \Exception();
$this->response = $this->getMock('OC\AppFramework\Http\Response');
}


public function testBeforeController() {
$this->middleware->beforeController($this->controller, null, $this->exception);
}


public function testAfterExceptionRaiseAgainWhenUnhandled() {
$this->setExpectedException('Exception');
$afterEx = $this->middleware->afterException($this->controller, null, $this->exception);
}


public function testAfterControllerReturnResponseWhenUnhandled() {
$response = $this->middleware->afterController($this->controller, null, $this->response);

$this->assertEquals($this->response, $response);
}


public function testBeforeOutputReturnOutputhenUnhandled() {
$output = $this->middleware->beforeOutput($this->controller, null, 'test');

$this->assertEquals('test', $output);
}


}

+ 388
- 0
tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php View File

@@ -0,0 +1,388 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Middleware\Security;

use OC\AppFramework\Http\Http;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\RedirectResponse;
use OC\AppFramework\Http\JSONResponse;
use OC\AppFramework\Middleware\Middleware;


require_once(__DIR__ . "/../../classloader.php");


class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase {

private $middleware;
private $controller;
private $secException;
private $secAjaxException;
private $request;

public function setUp() {
$api = $this->getMock('OC\AppFramework\Core\API', array(), array('test'));
$this->controller = $this->getMock('OC\AppFramework\Controller\Controller',
array(), array($api, new Request()));

$this->request = new Request();
$this->middleware = new SecurityMiddleware($api, $this->request);
$this->secException = new SecurityException('hey', false);
$this->secAjaxException = new SecurityException('hey', true);
}


private function getAPI(){
return $this->getMock('OC\AppFramework\Core\API',
array('isLoggedIn', 'passesCSRFCheck', 'isAdminUser',
'isSubAdminUser', 'activateNavigationEntry',
'getUserId'),
array('app'));
}


private function checkNavEntry($method, $shouldBeActivated=false){
$api = $this->getAPI();

if($shouldBeActivated){
$api->expects($this->once())
->method('activateNavigationEntry');
} else {
$api->expects($this->never())
->method('activateNavigationEntry');
}

$sec = new SecurityMiddleware($api, $this->request);
$sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method);
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testSetNavigationEntry(){
$this->checkNavEntry('testSetNavigationEntry', true);
}


private function ajaxExceptionCheck($method, $shouldBeAjax=false){
$api = $this->getAPI();
$api->expects($this->any())
->method('passesCSRFCheck')
->will($this->returnValue(false));

$sec = new SecurityMiddleware($api, $this->request);

try {
$sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest',
$method);
} catch (SecurityException $ex){
if($shouldBeAjax){
$this->assertTrue($ex->isAjax());
} else {
$this->assertFalse($ex->isAjax());
}

}
}


/**
* @Ajax
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testAjaxException(){
$this->ajaxExceptionCheck('testAjaxException');
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testNoAjaxException(){
$this->ajaxExceptionCheck('testNoAjaxException');
}


private function ajaxExceptionStatus($method, $test, $status) {
$api = $this->getAPI();
$api->expects($this->any())
->method($test)
->will($this->returnValue(false));

$sec = new SecurityMiddleware($api, $this->request);

try {
$sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest',
$method);
} catch (SecurityException $ex){
$this->assertEquals($status, $ex->getCode());
}
}

/**
* @Ajax
*/
public function testAjaxStatusLoggedInCheck() {
$this->ajaxExceptionStatus(
'testAjaxStatusLoggedInCheck',
'isLoggedIn',
Http::STATUS_UNAUTHORIZED
);
}

/**
* @Ajax
* @IsLoggedInExemption
*/
public function testAjaxNotAdminCheck() {
$this->ajaxExceptionStatus(
'testAjaxNotAdminCheck',
'isAdminUser',
Http::STATUS_FORBIDDEN
);
}

/**
* @Ajax
* @IsLoggedInExemption
* @IsAdminExemption
*/
public function testAjaxNotSubAdminCheck() {
$this->ajaxExceptionStatus(
'testAjaxNotSubAdminCheck',
'isSubAdminUser',
Http::STATUS_FORBIDDEN
);
}

/**
* @Ajax
* @IsLoggedInExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testAjaxStatusCSRFCheck() {
$this->ajaxExceptionStatus(
'testAjaxStatusCSRFCheck',
'passesCSRFCheck',
Http::STATUS_PRECONDITION_FAILED
);
}

/**
* @Ajax
* @CSRFExemption
* @IsLoggedInExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testAjaxStatusAllGood() {
$this->ajaxExceptionStatus(
'testAjaxStatusAllGood',
'isLoggedIn',
0
);
$this->ajaxExceptionStatus(
'testAjaxStatusAllGood',
'isAdminUser',
0
);
$this->ajaxExceptionStatus(
'testAjaxStatusAllGood',
'isSubAdminUser',
0
);
$this->ajaxExceptionStatus(
'testAjaxStatusAllGood',
'passesCSRFCheck',
0
);
}

/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testNoChecks(){
$api = $this->getAPI();
$api->expects($this->never())
->method('passesCSRFCheck')
->will($this->returnValue(true));
$api->expects($this->never())
->method('isAdminUser')
->will($this->returnValue(true));
$api->expects($this->never())
->method('isSubAdminUser')
->will($this->returnValue(true));
$api->expects($this->never())
->method('isLoggedIn')
->will($this->returnValue(true));

$sec = new SecurityMiddleware($api, $this->request);
$sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest',
'testNoChecks');
}


private function securityCheck($method, $expects, $shouldFail=false){
$api = $this->getAPI();
$api->expects($this->once())
->method($expects)
->will($this->returnValue(!$shouldFail));

$sec = new SecurityMiddleware($api, $this->request);

if($shouldFail){
$this->setExpectedException('\OC\AppFramework\Middleware\Security\SecurityException');
}

$sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method);
}


/**
* @IsLoggedInExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testCsrfCheck(){
$this->securityCheck('testCsrfCheck', 'passesCSRFCheck');
}


/**
* @IsLoggedInExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testFailCsrfCheck(){
$this->securityCheck('testFailCsrfCheck', 'passesCSRFCheck', true);
}


/**
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testLoggedInCheck(){
$this->securityCheck('testLoggedInCheck', 'isLoggedIn');
}


/**
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function testFailLoggedInCheck(){
$this->securityCheck('testFailLoggedInCheck', 'isLoggedIn', true);
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsSubAdminExemption
*/
public function testIsAdminCheck(){
$this->securityCheck('testIsAdminCheck', 'isAdminUser');
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsSubAdminExemption
*/
public function testFailIsAdminCheck(){
$this->securityCheck('testFailIsAdminCheck', 'isAdminUser', true);
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
*/
public function testIsSubAdminCheck(){
$this->securityCheck('testIsSubAdminCheck', 'isSubAdminUser');
}


/**
* @IsLoggedInExemption
* @CSRFExemption
* @IsAdminExemption
*/
public function testFailIsSubAdminCheck(){
$this->securityCheck('testFailIsSubAdminCheck', 'isSubAdminUser', true);
}



public function testAfterExceptionNotCaughtThrowsItAgain(){
$ex = new \Exception();
$this->setExpectedException('\Exception');
$this->middleware->afterException($this->controller, 'test', $ex);
}


public function testAfterExceptionReturnsRedirect(){
$api = $this->getMock('OC\AppFramework\Core\API', array(), array('test'));
$this->controller = $this->getMock('OC\AppFramework\Controller\Controller',
array(), array($api, new Request()));

$this->request = new Request(
array('server' => array('HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')));
$this->middleware = new SecurityMiddleware($api, $this->request);
$response = $this->middleware->afterException($this->controller, 'test',
$this->secException);

$this->assertTrue($response instanceof RedirectResponse);
}


public function testAfterAjaxExceptionReturnsJSONError(){
$response = $this->middleware->afterException($this->controller, 'test',
$this->secAjaxException);

$this->assertTrue($response instanceof JSONResponse);
}


}

+ 214
- 0
tests/lib/appframework/routing/RoutingTest.php View File

@@ -0,0 +1,214 @@
<?php

namespace OC\AppFramework\Routing;

use OC\AppFramework\DependencyInjection\DIContainer;
use OC\AppFramework\routing\RouteConfig;

require_once(__DIR__ . "/../classloader.php");

class RouteConfigTest extends \PHPUnit_Framework_TestCase
{

public function testSimpleRoute()
{
$routes = array('routes' => array(
array('name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'GET')
));

$this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
}

public function testSimpleRouteWithMissingVerb()
{
$routes = array('routes' => array(
array('name' => 'folders#open', 'url' => '/folders/{folderId}/open')
));

$this->assertSimpleRoute($routes, 'folders.open', 'GET', '/folders/{folderId}/open', 'FoldersController', 'open');
}

public function testSimpleRouteWithLowercaseVerb()
{
$routes = array('routes' => array(
array('name' => 'folders#open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete')
));

$this->assertSimpleRoute($routes, 'folders.open', 'DELETE', '/folders/{folderId}/open', 'FoldersController', 'open');
}

/**
* @expectedException \UnexpectedValueException
*/
public function testSimpleRouteWithBrokenName()
{
$routes = array('routes' => array(
array('name' => 'folders_open', 'url' => '/folders/{folderId}/open', 'verb' => 'delete')
));

// router mock
$router = $this->getMock("\OC_Router", array('create'));

// load route configuration
$container = new DIContainer('app1');
$config = new RouteConfig($container, $router, $routes);

$config->register();
}

public function testSimpleRouteWithUnderScoreNames()
{
$routes = array('routes' => array(
array('name' => 'admin_folders#open_current', 'url' => '/folders/{folderId}/open', 'verb' => 'delete')
));

$this->assertSimpleRoute($routes, 'admin_folders.open_current', 'DELETE', '/folders/{folderId}/open', 'AdminFoldersController', 'openCurrent');
}

public function testResource()
{
$routes = array('resources' => array('accounts' => array('url' => '/accounts')));

$this->assertResource($routes, 'accounts', '/accounts', 'AccountsController', 'accountId');
}

public function testResourceWithUnderScoreName()
{
$routes = array('resources' => array('admin_accounts' => array('url' => '/admin/accounts')));

$this->assertResource($routes, 'admin_accounts', '/admin/accounts', 'AdminAccountsController', 'adminAccountId');
}

private function assertSimpleRoute($routes, $name, $verb, $url, $controllerName, $actionName)
{
// route mocks
$route = $this->mockRoute($verb, $controllerName, $actionName);

// router mock
$router = $this->getMock("\OC_Router", array('create'));

// we expect create to be called once:
$router
->expects($this->once())
->method('create')
->with($this->equalTo('app1.' . $name), $this->equalTo($url))
->will($this->returnValue($route));

// load route configuration
$container = new DIContainer('app1');
$config = new RouteConfig($container, $router, $routes);

$config->register();
}

private function assertResource($yaml, $resourceName, $url, $controllerName, $paramName)
{
// router mock
$router = $this->getMock("\OC_Router", array('create'));

// route mocks
$indexRoute = $this->mockRoute('GET', $controllerName, 'index');
$showRoute = $this->mockRoute('GET', $controllerName, 'show');
$createRoute = $this->mockRoute('POST', $controllerName, 'create');
$updateRoute = $this->mockRoute('PUT', $controllerName, 'update');
$destroyRoute = $this->mockRoute('DELETE', $controllerName, 'destroy');

$urlWithParam = $url . '/{' . $paramName . '}';

// we expect create to be called once:
$router
->expects($this->at(0))
->method('create')
->with($this->equalTo('app1.' . $resourceName . '.index'), $this->equalTo($url))
->will($this->returnValue($indexRoute));

$router
->expects($this->at(1))
->method('create')
->with($this->equalTo('app1.' . $resourceName . '.show'), $this->equalTo($urlWithParam))
->will($this->returnValue($showRoute));

$router
->expects($this->at(2))
->method('create')
->with($this->equalTo('app1.' . $resourceName . '.create'), $this->equalTo($url))
->will($this->returnValue($createRoute));

$router
->expects($this->at(3))
->method('create')
->with($this->equalTo('app1.' . $resourceName . '.update'), $this->equalTo($urlWithParam))
->will($this->returnValue($updateRoute));

$router
->expects($this->at(4))
->method('create')
->with($this->equalTo('app1.' . $resourceName . '.destroy'), $this->equalTo($urlWithParam))
->will($this->returnValue($destroyRoute));

// load route configuration
$container = new DIContainer('app1');
$config = new RouteConfig($container, $router, $yaml);

$config->register();
}

/**
* @param $verb
* @param $controllerName
* @param $actionName
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function mockRoute($verb, $controllerName, $actionName)
{
$container = new DIContainer('app1');
$route = $this->getMock("\OC_Route", array('method', 'action'), array(), '', false);
$route
->expects($this->exactly(1))
->method('method')
->with($this->equalTo($verb))
->will($this->returnValue($route));

$route
->expects($this->exactly(1))
->method('action')
->with($this->equalTo(new RouteActionHandler($container, $controllerName, $actionName)))
->will($this->returnValue($route));
return $route;
}

}

/*
#
# sample routes.yaml for ownCloud
#
# the section simple describes one route

routes:
- name: folders#open
url: /folders/{folderId}/open
verb: GET
# controller: name.split()[0]
# action: name.split()[1]

# for a resource following actions will be generated:
# - index
# - create
# - show
# - update
# - destroy
# - new
resources:
accounts:
url: /accounts

folders:
url: /accounts/{accountId}/folders
# actions can be used to define additional actions on the resource
actions:
- name: validate
verb: GET
on-collection: false

* */

+ 58
- 0
tests/lib/appframework/utility/MethodAnnotationReaderTest.php View File

@@ -0,0 +1,58 @@
<?php

/**
* ownCloud - App Framework
*
* @author Bernhard Posselt
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OC\AppFramework\Utility;


require_once __DIR__ . "/../classloader.php";


class MethodAnnotationReaderTest extends \PHPUnit_Framework_TestCase {


/**
* @Annotation
*/
public function testReadAnnotation(){
$reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest',
'testReadAnnotation');

$this->assertTrue($reader->hasAnnotation('Annotation'));
}


/**
* @Annotation
* @param test
*/
public function testReadAnnotationNoLowercase(){
$reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest',
'testReadAnnotationNoLowercase');

$this->assertTrue($reader->hasAnnotation('Annotation'));
$this->assertFalse($reader->hasAnnotation('param'));
}


}

Loading…
Cancel
Save