mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 06:30:59 +01:00
use etemplate-exec-id as CSRF token for ajax requests
This commit is contained in:
parent
2045c08e54
commit
d95894d530
@ -339,9 +339,12 @@ class admin_account
|
|||||||
*
|
*
|
||||||
* @param int $account_id
|
* @param int $account_id
|
||||||
* @param String[] $data Optional data
|
* @param String[] $data Optional data
|
||||||
|
* @param string $etemplate_exec_id to check against CSRF
|
||||||
*/
|
*/
|
||||||
public static function ajax_delete_group($account_id, $data)
|
public static function ajax_delete_group($account_id, $data, $etemplate_exec_id)
|
||||||
{
|
{
|
||||||
|
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args());
|
||||||
|
|
||||||
$cmd = new admin_cmd_delete_account(Api\Accounts::id2name(Api\Accounts::id2name($account_id)), null, false, (array)$data['admin_cmd']);
|
$cmd = new admin_cmd_delete_account(Api\Accounts::id2name(Api\Accounts::id2name($account_id)), null, false, (array)$data['admin_cmd']);
|
||||||
$msg = $cmd->run();
|
$msg = $cmd->run();
|
||||||
|
|
||||||
|
@ -335,12 +335,15 @@ class admin_acl
|
|||||||
* Checks access and throws an exception, if a change is attempted without proper access
|
* Checks access and throws an exception, if a change is attempted without proper access
|
||||||
*
|
*
|
||||||
* @param string|array $ids "$app:$account:$location" string used as row-id in list
|
* @param string|array $ids "$app:$account:$location" string used as row-id in list
|
||||||
* @param int $rights =null null to delete, or new rights
|
* @param int $rights null to delete, or new rights
|
||||||
* @param Array $values =array() Additional values from UI
|
* @param array $values Additional values from UI
|
||||||
|
* @param string $etemplate_exec_id to check against CSRF
|
||||||
* @throws Api\Exception\NoPermission
|
* @throws Api\Exception\NoPermission
|
||||||
*/
|
*/
|
||||||
public static function ajax_change_acl($ids, $rights=null, $values = array())
|
public static function ajax_change_acl($ids, $rights, $values, $etemplate_exec_id)
|
||||||
{
|
{
|
||||||
|
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
foreach((array)$ids as $id)
|
foreach((array)$ids as $id)
|
||||||
{
|
{
|
||||||
|
@ -288,13 +288,17 @@ class admin_customfields
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a type over ajax. Used when Policy is involved, otherwise
|
* Delete a type over ajax.
|
||||||
* things go normally
|
|
||||||
*
|
*
|
||||||
* @param Array $content
|
* Used when Policy is involved, otherwise things go normally
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @param string $etemplate_exec_id to check against CSRF
|
||||||
*/
|
*/
|
||||||
public function ajax_delete_type($content)
|
public function ajax_delete_type($content, $etemplate_exec_id)
|
||||||
{
|
{
|
||||||
|
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args());
|
||||||
|
|
||||||
// Read fields
|
// Read fields
|
||||||
$this->appname = $content['appname'];
|
$this->appname = $content['appname'];
|
||||||
$this->fields = Api\Storage\Customfields::get($content['appname'],true);
|
$this->fields = Api\Storage\Customfields::get($content['appname'],true);
|
||||||
|
@ -1568,10 +1568,13 @@ class admin_mail
|
|||||||
* domain => mailLocalAddress,
|
* domain => mailLocalAddress,
|
||||||
* status => mail activation status('active'|'')
|
* status => mail activation status('active'|'')
|
||||||
* )
|
* )
|
||||||
|
* @param string $etemplate_exec_id to check against CSRF
|
||||||
* @return json response
|
* @return json response
|
||||||
*/
|
*/
|
||||||
public function ajax_activeAccounts($_data)
|
public function ajax_activeAccounts($_data, $etemplate_exec_id)
|
||||||
{
|
{
|
||||||
|
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args());
|
||||||
|
|
||||||
if (!$this->is_admin) die('no rights to be here!');
|
if (!$this->is_admin) die('no rights to be here!');
|
||||||
$response = Api\Json\Response::get();
|
$response = Api\Json\Response::get();
|
||||||
if (($account = $GLOBALS['egw']->accounts->read($_data['id'])))
|
if (($account = $GLOBALS['egw']->accounts->read($_data['id'])))
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
* @link http://www.egroupware.org
|
* @link http://www.egroupware.org
|
||||||
* @package filemanager
|
* @package filemanager
|
||||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||||
* @copyright (c) 2013-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
* @copyright (c) 2013-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
* @version $Id: app.js 56051 2016-05-06 07:58:37Z ralfbecker $
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +35,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
nm: null,
|
nm: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refarence to div to hold AJAX loadable pages
|
* Reference to div to hold AJAX loadable pages
|
||||||
*
|
*
|
||||||
* {et2_box}
|
* {et2_box}
|
||||||
*/
|
*/
|
||||||
@ -443,7 +442,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
this.egw.json('admin_account::ajax_delete_group', [account_id, _action.data]).sendRequest();
|
this.egw.json('admin_account::ajax_delete_group', [account_id, _action.data, this.et2._inst.etemplate_exec_id]).sendRequest();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!_action.data.url)
|
if (!_action.data.url)
|
||||||
@ -510,7 +509,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
var callback = function(_button_id, _value) {
|
var callback = function(_button_id, _value) {
|
||||||
if(_button_id != et2_dialog.OK_BUTTON) return;
|
if(_button_id != et2_dialog.OK_BUTTON) return;
|
||||||
|
|
||||||
var request = egw.json(className+'::ajax_change_acl', [ids,null,_value], this._acl_callback,this,false,this)
|
var request = egw.json(className+'::ajax_change_acl', [ids, null, _value, this.et2._inst.etemplate_exec_id], this._acl_callback,this,false,this)
|
||||||
.sendRequest();
|
.sendRequest();
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
@ -674,7 +673,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
{
|
{
|
||||||
// Changed the account or location, remove previous or we
|
// Changed the account or location, remove previous or we
|
||||||
// get a new line instead of an edit
|
// get a new line instead of an edit
|
||||||
this.egw.json(className+'::ajax_change_acl', [content.id, 0], null,this,false,this)
|
this.egw.json(className+'::ajax_change_acl', [content.id, 0, [], this.et2._inst.etemplate_exec_id], null,this,false,this)
|
||||||
.sendRequest();
|
.sendRequest();
|
||||||
}
|
}
|
||||||
id = [id];
|
id = [id];
|
||||||
@ -708,11 +707,11 @@ app.classes.admin = AppJS.extend(
|
|||||||
// Remove any removed
|
// Remove any removed
|
||||||
if(removed.length > 0)
|
if(removed.length > 0)
|
||||||
{
|
{
|
||||||
this.egw.json(className+'::ajax_change_acl', [removed, 0], callback ? callback : this._acl_callback,this,false,this)
|
this.egw.json(className+'::ajax_change_acl', [removed, 0, [], this.et2._inst.etemplate_exec_id], callback ? callback : this._acl_callback,this,false,this)
|
||||||
.sendRequest();
|
.sendRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.egw.json(className+'::ajax_change_acl', [id, rights, _value], callback ? callback : this._acl_callback,this,false,this)
|
this.egw.json(className+'::ajax_change_acl', [id, rights, _value, this.et2._inst.etemplate_exec_id], callback ? callback : this._acl_callback,this,false,this)
|
||||||
.sendRequest();
|
.sendRequest();
|
||||||
}
|
}
|
||||||
},this),
|
},this),
|
||||||
@ -971,7 +970,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
value,
|
value,
|
||||||
{appname: this.getRoot().getArrayMgr('content').getEntry('content_types[appname]')}
|
{appname: this.getRoot().getArrayMgr('content').getEntry('content_types[appname]')}
|
||||||
);
|
);
|
||||||
egw.json('admin.admin_customfields.ajax_delete_type', [values]).sendRequest();
|
egw.json('admin.admin_customfields.ajax_delete_type', [values, this.getInstanceManager().etemplate_exec_id]).sendRequest();
|
||||||
|
|
||||||
// Immediately remove the type
|
// Immediately remove the type
|
||||||
var types = this.getRoot().getWidgetById('types');
|
var types = this.getRoot().getWidgetById('types');
|
||||||
@ -1025,7 +1024,6 @@ app.classes.admin = AppJS.extend(
|
|||||||
*
|
*
|
||||||
* @param {egw_action} _action
|
* @param {egw_action} _action
|
||||||
* @param {array} _selected selected users
|
* @param {array} _selected selected users
|
||||||
* @todo remove under construction message
|
|
||||||
*/
|
*/
|
||||||
emailadminActiveAccounts: function (_action, _selected){
|
emailadminActiveAccounts: function (_action, _selected){
|
||||||
|
|
||||||
@ -1035,7 +1033,7 @@ app.classes.admin = AppJS.extend(
|
|||||||
|
|
||||||
for (var i=0;i< Object.keys(_selected).length;i++)
|
for (var i=0;i< Object.keys(_selected).length;i++)
|
||||||
{
|
{
|
||||||
accounts[i] = {id:_selected[i]['id'].split('::')[1],qouta:"", domain:"", status:_action.id == 'active'?_action.id:''};
|
accounts[i] = [{id:_selected[i]['id'].split('::')[1],qouta:"", domain:"", status:_action.id == 'active'?_action.id:''}, this.et2._inst.etemplate_exec_id];
|
||||||
}
|
}
|
||||||
var callbackDialog = function (btn){
|
var callbackDialog = function (btn){
|
||||||
if (btn === et2_dialog.YES_BUTTON)
|
if (btn === et2_dialog.YES_BUTTON)
|
||||||
|
@ -291,9 +291,12 @@ class Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return a random string of letters [0-9a-zA-Z] of size $size
|
* return a random string of size $size either just alphanumeric or with special chars
|
||||||
*
|
*
|
||||||
* @param $size int-size of random string to return
|
* @param $size int-size of random string to return
|
||||||
|
* @param $use_specialchars =false false: only letters and numbers, true: incl. special chars
|
||||||
|
* @return string
|
||||||
|
* @throws \Exception if it was not possible to gather sufficient entropy.
|
||||||
*/
|
*/
|
||||||
static function randomstring($size, $use_specialchars=false)
|
static function randomstring($size, $use_specialchars=false)
|
||||||
{
|
{
|
||||||
@ -310,13 +313,10 @@ class Auth
|
|||||||
$random_char = array_merge($random_char, str_split(str_replace('\\', '', self::SPECIALCHARS)), $random_char);
|
$random_char = array_merge($random_char, str_split(str_replace('\\', '', self::SPECIALCHARS)), $random_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
// use cryptographically secure random_int available in PHP 7+
|
|
||||||
$func = function_exists('random_int') ? 'random_int' : 'mt_rand';
|
|
||||||
|
|
||||||
$s = '';
|
$s = '';
|
||||||
for ($i=0; $i < $size; $i++)
|
for ($i=0; $i < $size; $i++)
|
||||||
{
|
{
|
||||||
$s .= $random_char[$func(0, count($random_char)-1)];
|
$s .= $random_char[random_int(0, count($random_char)-1)];
|
||||||
}
|
}
|
||||||
return $s;
|
return $s;
|
||||||
}
|
}
|
||||||
|
@ -142,9 +142,10 @@ class Request
|
|||||||
* the sesison to constantly grow).
|
* the sesison to constantly grow).
|
||||||
*
|
*
|
||||||
* @param string $id =null
|
* @param string $id =null
|
||||||
* @return Request
|
* @param bool $handle_not_found =true true: handle not found by trying to redirect, false: just return null
|
||||||
|
* @return Request|null null if Request not found and $handle_not_found === false
|
||||||
*/
|
*/
|
||||||
public static function read($id=null)
|
public static function read($id=null, $handle_not_found=true)
|
||||||
{
|
{
|
||||||
if (is_null(self::$request_class))
|
if (is_null(self::$request_class))
|
||||||
{
|
{
|
||||||
@ -192,7 +193,7 @@ class Request
|
|||||||
//error_log(__METHOD__."() size of request = ".bytes($id));
|
//error_log(__METHOD__."() size of request = ".bytes($id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$request) // eT2 request/session expired
|
if (!$request && $handle_not_found) // eT2 request/session expired
|
||||||
{
|
{
|
||||||
list($app) = explode('.', $_GET['menuaction']);
|
list($app) = explode('.', $_GET['menuaction']);
|
||||||
$global = false;
|
$global = false;
|
||||||
@ -228,6 +229,33 @@ class Request
|
|||||||
return $request;
|
return $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSRF check using an etemplate-exec-id
|
||||||
|
*
|
||||||
|
* If eTemplate request object could not be read, the function will NOT return,
|
||||||
|
* but send an Ajax error response and exit or die with the error-message!
|
||||||
|
*
|
||||||
|
* @param string $id etemplate-exec-id
|
||||||
|
* @param string $caller calling method to log
|
||||||
|
* @param array $args =[] arguments to log
|
||||||
|
* @throws Api\Json\Exception
|
||||||
|
*/
|
||||||
|
public static function csrfCheck($id, $caller, $args=[])
|
||||||
|
{
|
||||||
|
if (!self::read($id, false)) // false: do NOT handle not found, but return null
|
||||||
|
{
|
||||||
|
error_log(__METHOD__."('$id', $caller, ".json_encode($args).") called with invalid/expired etemplate_exec_id: possible CSRF detected from IP ".$_SERVER['REMOTE_ADDR'].' to '.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI']);
|
||||||
|
$msg = lang('Request could not be processed, please reload your window (press F5 or Cmd R)!');
|
||||||
|
|
||||||
|
if (Api\Json\Request::isJSONRequest())
|
||||||
|
{
|
||||||
|
Api\Json\Response::get()->message($msg, 'error');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
die($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to force the instancation of this class only via it's static factory method read
|
* Private constructor to force the instancation of this class only via it's static factory method read
|
||||||
*
|
*
|
||||||
@ -381,17 +409,15 @@ class Request
|
|||||||
* creates a new unique request-id
|
* creates a new unique request-id
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
|
* @throws \Exception if it was not possible to gather sufficient entropy.
|
||||||
*/
|
*/
|
||||||
static function request_id()
|
static function request_id()
|
||||||
{
|
{
|
||||||
// replace url-unsafe chars with _ to not run into url-encoding issues when used in a url
|
// replace url-unsafe chars with _ to not run into url-encoding issues when used in a url
|
||||||
$userID = preg_replace('/[^a-z0-9_\\.@-]/i', '_', $GLOBALS['egw_info']['user']['account_lid']);
|
$userID = preg_replace('/[^a-z0-9_\\.@-]/i', '_', $GLOBALS['egw_info']['user']['account_lid']);
|
||||||
|
|
||||||
// generate random token (using oppenssl if available otherwise mt_rand based Auth::randomstring)
|
// replace + with _ to not run into url-encoding issues when used in a url
|
||||||
$token = function_exists('openssl_random_pseudo_bytes') ?
|
$token = str_replace('+', '_', base64_encode(random_bytes(32)));
|
||||||
// replace + with _ to not run into url-encoding issues when used in a url
|
|
||||||
str_replace('+', '_', base64_encode(openssl_random_pseudo_bytes(32))) :
|
|
||||||
\EGroupware\Api\Auth::randomstring(44);
|
|
||||||
|
|
||||||
return $GLOBALS['egw_info']['flags']['currentapp'].'_'.$userID.'_'.$token;
|
return $GLOBALS['egw_info']['flags']['currentapp'].'_'.$userID.'_'.$token;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user