Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ownCloud is written by:
Sam Tuke
Simon Birnbach
Lukas Reschke
Christian Reiner

With help from many libraries and frameworks including:
Expand Down
41 changes: 41 additions & 0 deletions core/ajax/requesttoken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* ownCloud
* @author Christian Reiner
* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
*
* 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/>.
*
*/

/**
* @file core/ajax/requesttoken.php
* @brief Ajax method to retrieve a fresh request protection token for ajax calls
* @return json: success/error state indicator including a fresh request token
* @author Christian Reiner
*/
require_once '../../lib/base.php';

// don't load apps or filesystem for this task
$RUNTIME_NOAPPS = TRUE;
$RUNTIME_NOSETUPFS = TRUE;

// Sanity checks
// using OCP\JSON::callCheck() below protects the token refreshing itself.
//OCP\JSON::callCheck ( );
OCP\JSON::checkLoggedIn ( );
// hand out a fresh token
OCP\JSON::success ( array ( 'token' => OCP\Util::callRegister() ) );
?>
2 changes: 1 addition & 1 deletion core/js/eventsource.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ OC.EventSource=function(src,data){
dataStr+=name+'='+encodeURIComponent(data[name])+'&';
}
}
dataStr+='requesttoken='+OC.EventSource.requesttoken;
dataStr+='requesttoken='+OC.Request.Token;
if(!this.useFallBack && typeof EventSource !='undefined'){
this.source=new EventSource(src+'?'+dataStr);
this.source.onmessage=function(e){
Expand Down
55 changes: 55 additions & 0 deletions core/js/requesttoken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* ownCloud
*
* @file core/js/requesttoken.js
* @brief Routine to refresh the Request protection request token periodically
* @author Christian Reiner (arkascha)
* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
*
* 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/>.
*
*/

OC.Request = {
// the request token
Token: {},
// the lifespan span (in secs)
Lifespan: {},
// method to refresh the local request token periodically
Refresh: function(){
// just a client side console log to preserve efficiency
console.log("refreshing request token (lifebeat)");
var dfd=new $.Deferred();
$.ajax({
type: 'POST',
url: OC.filePath('core','ajax','requesttoken.php'),
cache: false,
data: { },
dataType: 'json'
}).done(function(response){
// store refreshed token inside this class
OC.Request.Token=response.token;
dfd.resolve();
}).fail(dfd.reject);
return dfd;
}
}
// accept requesttoken and lifespan into the OC namespace
OC.Request.Token = oc_requesttoken;
OC.Request.Lifespan = oc_requestlifespan;
// refresh the request token periodically shortly before it becomes invalid on the server side
setInterval(OC.Request.Refresh,Math.floor(1000*OC.Request.Lifespan*0.93)), // 93% of lifespan value, close to when the token expires
// early bind token as additional ajax argument for every single request
$(document).bind('ajaxSend', function(elm, xhr, s){xhr.setRequestHeader('requesttoken', OC.Request.Token);});
9 changes: 2 additions & 7 deletions core/templates/layout.user.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
var oc_webroot = '<?php echo OC::$WEBROOT; ?>';
var oc_appswebroots = <?php echo $_['apps_paths'] ?>;
var oc_current_user = '<?php echo OC_User::getUser() ?>';
var oc_requesttoken = '<?php echo $_['requesttoken']; ?>';
var oc_requestlifespan = '<?php echo $_['requestlifespan']; ?>';
</script>
<?php foreach($_['jsfiles'] as $jsfile): ?>
<script type="text/javascript" src="<?php echo $jsfile; ?>"></script>
Expand All @@ -24,13 +26,6 @@
echo '/>';
?>
<?php endforeach; ?>
<script type="text/javascript">
requesttoken = '<?php echo $_['requesttoken']; ?>';
OC.EventSource.requesttoken=requesttoken;
$(document).bind('ajaxSend', function(elm, xhr, s) {
xhr.setRequestHeader('requesttoken', requesttoken);
});
</script>
</head>

<body id="<?php echo $_['bodyid'];?>">
Expand Down
2 changes: 2 additions & 0 deletions lib/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ public static function initTemplateEngine() {
OC_Util::addScript( "jquery-tipsy" );
OC_Util::addScript( "oc-dialogs" );
OC_Util::addScript( "js" );
// request protection token MUST be defined after the jquery library but before any $('document').ready()
OC_Util::addScript( "requesttoken" );
OC_Util::addScript( "eventsource" );
OC_Util::addScript( "config" );
//OC_Util::addScript( "multiselect" );
Expand Down
2 changes: 2 additions & 0 deletions lib/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public function __construct( $app, $name, $renderas = "" ) {
$this->vars = array();
if($renderas == 'user') {
$this->vars['requesttoken'] = OC_Util::callRegister();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and L367 breaks some apps which do things like:
<input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>">

IMHO, you shouldn't remove these vars.
(Just commit a new version to your repository and this pull request will get updated.)

$this->vars['requestlifespan'] = OC_Util::$callLifespan;
}
$parts = explode('/', $app); // fix translation when app is something like core/lostpassword
$this->l10n = OC_L10N::get($parts[0]);
Expand Down Expand Up @@ -374,6 +375,7 @@ public function fetchPage() {
$page = new OC_TemplateLayout($this->renderas);
if($this->renderas == 'user') {
$page->assign('requesttoken', $this->vars['requesttoken']);
$page->assign('requestlifespan', $this->vars['requestlifespan']);
}

// Add custom headers
Expand Down
36 changes: 26 additions & 10 deletions lib/util.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,14 +416,29 @@ public static function getInstanceId() {
}

/**
* @brief Register an get/post call. This is important to prevent CSRF attacks
* Todo: Write howto
* @brief Static lifespan (in seconds) when a request token expires.
* @see OC_Util::callRegister()
* @see OC_Util::isCallRegistered()
* @description
* Also required for the client side to compute the piont in time when to
* request a fresh token. The client will do so when nearly 97% of the
* timespan coded here has expired.
*/
public static $callLifespan = 3600; // 3600 secs = 1 hour

/**
* @brief Register an get/post call. Important to prevent CSRF attacks.
* @todo Write howto: CSRF protection guide
* @return $token Generated token.
* @description
* Creates a 'request token' (random) and stores it inside the session.
* Ever subsequent (ajax) request must use such a valid token to succeed,
* otherwise the request will be denied as a protection against CSRF.
* The tokens expire after a fixed lifespan.
* @see OC_Util::$callLifespan
* @see OC_Util::isCallRegistered()
*/
public static function callRegister() {
//mamimum time before token exires
$maxtime=(60*60); // 1 hour

// generate a random token.
$token=mt_rand(1000,9000).mt_rand(1000,9000).mt_rand(1000,9000);

Expand All @@ -436,7 +451,8 @@ public static function callRegister() {
foreach($_SESSION as $key=>$value) {
// search all tokens in the session
if(substr($key,0,12)=='requesttoken') {
if($value+$maxtime<time()) {
// check if static lifespan has expired
if($value+self::$callLifespan<time()) {
// remove outdated tokens
unset($_SESSION[$key]);
}
Expand All @@ -447,14 +463,13 @@ public static function callRegister() {
return($token);
}


/**
* @brief Check an ajax get/post call if the request token is valid.
* @return boolean False if request token is not set or is invalid.
* @see OC_Util::$callLifespan
* @see OC_Util::calLRegister()
*/
public static function isCallRegistered() {
//mamimum time before token exires
$maxtime=(60*60); // 1 hour
if(isset($_GET['requesttoken'])) {
$token=$_GET['requesttoken'];
}elseif(isset($_POST['requesttoken'])) {
Expand All @@ -467,7 +482,8 @@ public static function isCallRegistered() {
}
if(isset($_SESSION['requesttoken-'.$token])) {
$timestamp=$_SESSION['requesttoken-'.$token];
if($timestamp+$maxtime<time()) {
// check if static lifespan has expired
if($timestamp+self::$callLifespan<time()) {
return false;
}else{
//token valid
Expand Down