Merge pull request #7986 from JakubOnderka/better-security

chg: [internal] Do not modify session when not necessary
pull/8099/head
Jakub Onderka 2021-12-30 14:40:01 +01:00 committed by GitHub
commit 50d284b643
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 159 additions and 115 deletions

4
.gitmodules vendored
View File

@ -11,10 +11,6 @@
[submodule "app/files/warninglists"]
path = app/files/warninglists
url = https://github.com/MISP/misp-warninglists.git
[submodule "app/Lib/random_compat"]
path = app/Lib/random_compat
url = https://github.com/paragonie/random_compat
branch = master
[submodule "app/files/misp-galaxy"]
path = app/files/misp-galaxy
url = https://github.com/MISP/misp-galaxy

View File

@ -5,7 +5,6 @@ App::uses('AppController', 'Controller');
class AllowedlistsController extends AppController
{
public $components = array(
'Security',
'AdminCrud'
);

View File

@ -22,6 +22,7 @@ App::uses('BlowfishConstantPasswordHasher', 'Controller/Component/Auth');
* @property CompressedRequestHandlerComponent $CompressedRequestHandler
* @property DeprecationComponent $Deprecation
* @property RestSearchComponent $RestSearch
* @property BetterSecurityComponent $Security
*/
class AppController extends Controller
{
@ -81,7 +82,9 @@ class AppController extends Controller
)
)
),
'Security',
'Security' => [
'className' => 'BetterSecurity',
],
'ACL',
'CompressedRequestHandler',
'RestResponse',
@ -217,6 +220,7 @@ class AppController extends Controller
// Throw exception if JSON in request is invalid. Default CakePHP behaviour would just ignore that error.
$this->RequestHandler->addInputType('json', [$jsonDecode]);
$this->Security->unlockedActions = array($this->request->action);
$this->Security->doNotGenerateToken = true;
}
if (
@ -230,9 +234,7 @@ class AppController extends Controller
// REST authentication
if ($this->_isRest() || $this->_isAutomation()) {
// disable CSRF for REST access
if (isset($this->components['Security'])) {
$this->Security->csrfCheck = false;
}
$this->Security->csrfCheck = false;
if ($this->__loginByAuthKey() === false || $this->Auth->user() === null) {
if ($this->__loginByAuthKey() === null) {
$this->loadModel('Log');
@ -395,8 +397,6 @@ class AppController extends Controller
private function __loginByAuthKey()
{
if (Configure::read('Security.authkey_keep_session') && $this->Auth->user()) {
// Do not check authkey if session is establish and correct, just close session to allow multiple requests
session_write_close();
return true;
}

View File

@ -9,7 +9,7 @@ App::uses('AttachmentTool', 'Tools');
*/
class AttributesController extends AppController
{
public $components = array('Security', 'RequestHandler');
public $components = array('RequestHandler');
public $paginate = [
'limit' => 60,
@ -47,9 +47,8 @@ class AttributesController extends AppController
$this->Security->unlockedActions[] = 'getMassEditForm';
$this->Security->unlockedActions[] = 'search';
if ($this->request->action === 'add_attachment') {
$this->Security->disabledFields = array('values');
$this->Security->unlockedFields = array('values');
}
$this->Security->validatePost = true;
// convert uuid to id if present in the url and overwrite id field
if (isset($this->request->params->query['uuid'])) {

View File

@ -8,7 +8,6 @@ App::uses('AuditLog', 'Model');
class AuditLogsController extends AppController
{
public $components = [
'Security',
'RequestHandler',
];

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class AuthKeysController extends AppController
{
public $components = array(
'Security',
'CRUD',
'RequestHandler'
);

View File

@ -0,0 +1,84 @@
<?php
App::uses('SecurityComponent', 'Controller/Component');
/**
* @property SessionComponent $Session
*/
class BetterSecurityComponent extends SecurityComponent
{
/**
* Do not generate CSRF token. This make sense for REST calls and for calls that do not use tokens. So session
* will not be big with csrfLimit (by default 100) of token.
* @var bool
*/
public $doNotGenerateToken = false;
public function generateToken(CakeRequest $request)
{
if (isset($request->params['requested']) && $request->params['requested'] === 1) {
if ($this->Session->check('_Token')) {
$request->params['_Token'] = $this->Session->read('_Token');
}
return false;
}
if ($this->doNotGenerateToken) {
return true;
}
// No need to hash random data
$authKey = bin2hex(Security::randomBytes(16));
$token = array(
'key' => $authKey,
'allowedControllers' => $this->allowedControllers,
'allowedActions' => $this->allowedActions,
'unlockedFields' => array_merge($this->disabledFields, $this->unlockedFields),
'csrfTokens' => array(),
);
if ($this->Session->check('_Token')) {
$tokenData = $this->Session->read('_Token');
if (!empty($tokenData['csrfTokens']) && is_array($tokenData['csrfTokens'])) {
$token['csrfTokens'] = $this->_expireTokens($tokenData['csrfTokens']);
}
}
if ($this->csrfUseOnce || empty($token['csrfTokens'])) {
$token['csrfTokens'][$authKey] = strtotime($this->csrfExpires);
}
if (!$this->csrfUseOnce) {
$csrfTokens = array_keys($token['csrfTokens']);
$authKey = $csrfTokens[0];
$token['key'] = $authKey;
$token['csrfTokens'][$authKey] = strtotime($this->csrfExpires);
}
$this->Session->write('_Token', $token);
$request->params['_Token'] = array(
'key' => $token['key'],
'unlockedFields' => $token['unlockedFields'],
);
return true;
}
/**
* Avoid possible timing attacks by using `hash_equals` method to compare hashes.
* @param Controller $controller
* @return bool
*/
protected function _validatePost(Controller $controller)
{
$token = $this->_validToken($controller);
$hashParts = $this->_hashParts($controller);
$check = sha1(implode('', $hashParts));
if (hash_equals($token, $check)) {
return true;
}
$msg = self::DEFAULT_EXCEPTION_MESSAGE;
if (Configure::read('debug')) {
$msg = $this->_debugPostTokenNotMatching($controller, $hashParts);
}
throw new AuthSecurityException($msg);
}
}

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class CorrelationExclusionsController extends AppController
{
public $components = array(
'Security',
'CRUD',
'RequestHandler'
);

View File

@ -6,7 +6,7 @@ App::uses('AppController', 'Controller');
*/
class CorrelationsController extends AppController
{
public $components = array('Security', 'RequestHandler');
public $components = array('RequestHandler');
public function top()
{

View File

@ -154,6 +154,7 @@ class DashboardsController extends AppController
throw new MethodNotAllowedException(__('This endpoint can only be reached via POST requests.'));
}
$user = $this->Auth->user();
@session_write_close(); // allow concurrent AJAX requests (session hold lock by default)
if (empty($this->request->data['data'])) {
@ -164,10 +165,10 @@ class DashboardsController extends AppController
}
$value = $this->request->data['data'];
$valueConfig = json_decode($value['config'], true);
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $value['widget']);
$dashboardWidget = $this->Dashboard->loadWidget($user, $value['widget']);
$redis = $this->Dashboard->setupRedis();
$org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id');
$org_scope = $this->_isSiteAdmin() ? 0 : $user['org_id'];
$lookup_hash = hash('sha256', $value['widget'] . $value['config']);
$cacheKey = 'misp:dashboard:' . $org_scope . ':' . $lookup_hash;
$data = $redis->get($cacheKey);
@ -175,7 +176,7 @@ class DashboardsController extends AppController
$dashboardWidget->cacheLifetime = false;
}
if (empty($dashboardWidget->cacheLifetime) || empty($data)) {
$data = $dashboardWidget->handler($this->Auth->user(), $valueConfig);
$data = $dashboardWidget->handler($user, $valueConfig);
if (!empty($dashboardWidget->cacheLifetime)) {
$redis->setex($cacheKey, $dashboardWidget->cacheLifetime, json_encode(array('data' => $data)));
}

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class DecayingModelController extends AppController
{
public $components = array('Security' ,'RequestHandler');
public $components = array('RequestHandler');
public $paginate = array(
'limit' => 50,

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class DecayingModelMappingController extends AppController
{
public $components = array('Security' ,'RequestHandler');
public $components = array('RequestHandler');
public $paginate = array(
'limit' => 50,

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class EventGraphController extends AppController
{
public $components = array(
'Security',
'RequestHandler'
);

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class EventReportsController extends AppController
{
public $components = array(
'Security',
'AdminCrud',
'RequestHandler'
);

View File

@ -9,10 +9,8 @@ App::uses('Xml', 'Utility');
class EventsController extends AppController
{
public $components = array(
'Security',
'Email',
'RequestHandler',
'IOCImport',
'RequestHandler',
'IOCImport',
);
public $paginate = array(
@ -108,6 +106,10 @@ class EventsController extends AppController
}
$this->paginate = Set::merge($this->paginate, array('conditions' => $conditions));
}
if ($this->request->action === 'checkLocks') {
$this->Security->doNotGenerateToken = true;
}
}
/**
@ -4467,21 +4469,21 @@ class EventsController extends AppController
return new CakeResponse(array('body' => json_encode($json), 'status' => 200, 'type' => 'json'));
}
private function genDistributionGraph($id, $type = 'event', $extended = 0)
private function genDistributionGraph($id, $type = 'event', $extended = 0, $user = null)
{
$validTools = array('event');
if (!in_array($type, $validTools)) {
throw new MethodNotAllowedException(__('Invalid type.'));
}
App::uses('DistributionGraphTool', 'Tools');
$grapher = new DistributionGraphTool();
$this->loadModel('Server');
$servers = $this->Server->find('column', array(
'fields' => array('Server.name'),
));
$grapher->construct($this->Event, $servers, $this->Auth->user(), $extended);
App::uses('DistributionGraphTool', 'Tools');
$user = $user ?: $this->Auth->user();
$grapher = new DistributionGraphTool($this->Event, $servers, $user, $extended);
$json = $grapher->get_distributions_graph($id);
array_walk_recursive($json, function (&$item, $key) {
@ -4523,8 +4525,12 @@ class EventsController extends AppController
public function getDistributionGraph($id, $type = 'event')
{
// Close session without writing changes to them.
$user = $this->Auth->user();
session_abort();
$extended = isset($this->params['named']['extended']) ? 1 : 0;
$json = $this->genDistributionGraph($id, $type, $extended);
$json = $this->genDistributionGraph($id, $type, $extended, $user);
return $this->RestResponse->viewData($json, 'json');
}
@ -5463,17 +5469,20 @@ class EventsController extends AppController
public function checkLocks($id, $timestamp)
{
// Close session without writing changes to them.
$user = $this->Auth->user();
session_abort();
$event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => ['Event.id' => $id],
'fields' => ['Event.orgc_id', 'Event.timestamp'],
));
// Return empty response if event not found or user org is not owner
if (empty($event) || ($event['Event']['orgc_id'] != $this->Auth->user('org_id') && !$this->_isSiteAdmin())) {
if (empty($event) || ($event['Event']['orgc_id'] != $user['org_id'] && !$this->_isSiteAdmin())) {
return new CakeResponse(['status' => 204]);
}
$user = $this->Auth->user();
$this->loadModel('EventLock');
$locks = $this->EventLock->checkLock($user, $id);

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class FeedsController extends AppController
{
public $components = array(
'Security',
'CRUD',
'RequestHandler'
); // XXX ACL component

View File

@ -6,7 +6,7 @@ App::uses('AppController', 'Controller');
*/
class JobsController extends AppController
{
public $components = array('Security', 'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 20,

View File

@ -5,7 +5,6 @@ App::uses('AppController', 'Controller');
class LogsController extends AppController
{
public $components = array(
'Security',
'RequestHandler',
'AdminCrud' => array(
'crud' => array('index')

View File

@ -3,7 +3,6 @@ App::uses('AppController', 'Controller');
class ModulesController extends AppController
{
public $components = array(
'Security',
'RequestHandler'
);

View File

@ -6,7 +6,7 @@ App::uses('AppController', 'Controller');
*/
class ObjectReferencesController extends AppController
{
public $components = array('Security' ,'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 20,

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class ObjectTemplateElementsController extends AppController
{
public $components = array('Security' ,'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 60,

View File

@ -6,7 +6,7 @@ App::uses('AppController', 'Controller');
*/
class ObjectTemplatesController extends AppController
{
public $components = array('Security' ,'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 60,

View File

@ -10,7 +10,7 @@ class ObjectsController extends AppController
{
public $uses = 'MispObject';
public $components = array('Security' ,'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 20,

View File

@ -9,7 +9,6 @@ App::uses('AppController', 'Controller');
class PostsController extends AppController
{
public $components = array(
'Security',
'Session',
'RequestHandler'
);

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class RegexpController extends AppController
{
public $components = array('Security', 'RequestHandler', 'AdminCrud');
public $components = array('RequestHandler', 'AdminCrud');
public $paginate = array(
'limit' => 60,

View File

@ -5,7 +5,6 @@ App::uses('AppController', 'Controller');
class RestClientHistoryController extends AppController
{
public $components = array(
'Security',
'AdminCrud',
'RequestHandler'
);

View File

@ -10,7 +10,6 @@ App::uses('AppController', 'Controller');
class RolesController extends AppController
{
public $components = array(
'Security',
'Session',
'RequestHandler'
);

View File

@ -9,7 +9,7 @@ App::uses('SecurityAudit', 'Tools');
*/
class ServersController extends AppController
{
public $components = array('Security' ,'RequestHandler'); // XXX ACL component
public $components = array('RequestHandler'); // XXX ACL component
public $paginate = array(
'limit' => 60,

View File

@ -9,7 +9,7 @@ App::uses('AttachmentTool', 'Tools');
*/
class ShadowAttributesController extends AppController
{
public $components = array('Acl', 'Security', 'RequestHandler', 'Email');
public $components = array('RequestHandler');
public $paginate = array(
'limit' => 60,
@ -20,7 +20,6 @@ class ShadowAttributesController extends AppController
{
parent::beforeFilter();
$this->set('title_for_layout', 'Proposals');
$this->Security->validatePost = true;
// convert uuid to id if present in the url, and overwrite id field
if (isset($this->params->query['uuid'])) {

View File

@ -8,7 +8,6 @@ App::uses('AppController', 'Controller');
class TagCollectionsController extends AppController
{
public $components = array(
'Security',
'AdminCrud',
'RequestHandler'
);

View File

@ -6,7 +6,7 @@ App::uses('AppController', 'Controller');
*/
class TagsController extends AppController
{
public $components = array('Security' ,'RequestHandler');
public $components = array('RequestHandler');
public $paginate = array(
'limit' => 50,

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class TasksController extends AppController
{
public $components = array('Security' ,'RequestHandler', 'Session');
public $components = array('RequestHandler', 'Session');
public $paginate = array(
'limit' => 20,

View File

@ -4,7 +4,7 @@ App::uses('AppController', 'Controller');
class TemplateElementsController extends AppController
{
public $components = array('Security' ,'RequestHandler');
public $components = array('RequestHandler');
public $paginate = array(
'limit' => 50,

View File

@ -6,7 +6,7 @@ App::uses('File', 'Utility');
class TemplatesController extends AppController
{
public $components = array('Security' ,'RequestHandler', 'CRUD');
public $components = array('RequestHandler', 'CRUD');
public $paginate = array(
'limit' => 50,

View File

@ -7,7 +7,6 @@ App::uses('AppController', 'Controller');
class ThreadsController extends AppController
{
public $components = array(
'Security',
'RequestHandler',
'Session',
);

View File

@ -9,9 +9,7 @@ class UsersController extends AppController
public $newkey;
public $components = array(
'Security',
'Email',
'RequestHandler'
'RequestHandler'
);
public $paginate = array(
@ -1822,6 +1820,7 @@ class UsersController extends AppController
// shows some statistics about the instance
public function statistics($page = 'data')
{
$user = $this->Auth->user();
@session_write_close(); // loading this page can take long time, so we can close session
if (!$this->_isRest()) {
@ -1843,7 +1842,7 @@ class UsersController extends AppController
}
if ($page === 'data') {
$result = $this->__statisticsData($this->params['named']);
$result = $this->__statisticsData($user, $this->params['named']);
} elseif ($page === 'orgs') {
if (!$this->_isSiteAdmin() && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
throw new MethodNotAllowedException('This feature is currently disabled.');
@ -1860,9 +1859,9 @@ class UsersController extends AppController
$this->render('statistics_histogram');
}
} elseif ($page === 'sightings') {
$result = $this->__statisticsSightings($this->params['named']);
$result = $this->__statisticsSightings($user, $this->params['named']);
} elseif ($page === 'galaxyMatrix') {
$result = $this->__statisticsGalaxyMatrix($this->params['named']);
$result = $this->__statisticsGalaxyMatrix($user, $this->params['named']);
} else {
throw new NotFoundException("Invalid page");
}
@ -1872,7 +1871,7 @@ class UsersController extends AppController
}
}
private function __statisticsData($params = array())
private function __statisticsData(array $user, $params = array())
{
// set all of the data up for the heatmaps
$params = array(
@ -1881,8 +1880,8 @@ class UsersController extends AppController
'conditions' => array(),
'order' => ['name'],
);
if (!$this->_isSiteAdmin() && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
$params['conditions'] = array('Organisation.id' => $this->Auth->user('org_id'));
if (!$user['Role']['perm_site_admin'] && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
$params['conditions'] = array('Organisation.id' => $user['org_id']);
}
$orgs = $this->User->Organisation->find('list', $params);
@ -1907,9 +1906,7 @@ class UsersController extends AppController
$stats['attribute_count_month'] = $this->User->Event->Attribute->find('count', array('conditions' => array('Attribute.timestamp >' => $this_month, 'Attribute.deleted' => 0), 'recursive' => -1));
$stats['attributes_per_event'] = round($stats['attribute_count'] / $stats['event_count']);
$this->loadModel('Correlation');
$this->Correlation->recursive = -1;
$stats['correlation_count'] = $this->Correlation->find('count', array('recursive' => -1));
$stats['correlation_count'] = $this->User->Event->Attribute->Correlation->find('count', array('recursive' => -1));
$stats['correlation_count'] = $stats['correlation_count'] / 2;
$stats['proposal_count'] = $this->User->Event->ShadowAttribute->find('count', array('recursive' => -1, 'conditions' => array('deleted' => 0)));
@ -1946,10 +1943,10 @@ class UsersController extends AppController
$this->render('statistics_data');
}
private function __statisticsSightings($params = array())
private function __statisticsSightings(array $user, $params = array())
{
$this->loadModel('Sighting');
$conditions = ['Sighting.org_id' => $this->Auth->user('org_id')];
$conditions = ['Sighting.org_id' => $user['org_id']];
if (isset($params['timestamp'])) {
$conditions['Sighting.date_sighting >'] = $params['timestamp'];
}
@ -1999,7 +1996,6 @@ class UsersController extends AppController
private function __statisticsOrgs($params = array())
{
$this->loadModel('Organisation');
$conditions = array();
if (!isset($params['scope']) || $params['scope'] == 'local') {
$params['scope'] = 'local';
@ -2007,12 +2003,12 @@ class UsersController extends AppController
} elseif ($params['scope'] == 'external') {
$conditions['Organisation.local'] = 0;
}
$orgs = $this->Organisation->find('all', array(
'recursive' => -1,
'conditions' => $conditions,
'fields' => array('id', 'name', 'description', 'local', 'contacts', 'type', 'sector', 'nationality'),
$orgs = $this->User->Organisation->find('all', array(
'recursive' => -1,
'conditions' => $conditions,
'fields' => array('id', 'name', 'description', 'local', 'contacts', 'type', 'sector', 'nationality'),
));
$orgs = Set::combine($orgs, '{n}.Organisation.id', '{n}.Organisation');
$orgs = array_column(array_column($orgs, 'Organisation'), null, 'id');
$users = $this->User->find('all', array(
'group' => 'User.org_id',
'conditions' => array('User.org_id' => array_keys($orgs)),
@ -2170,7 +2166,7 @@ class UsersController extends AppController
}
}
private function __statisticsGalaxyMatrix($params = array())
private function __statisticsGalaxyMatrix(array $user, $params = array())
{
$this->loadModel('Event');
$this->loadModel('Galaxy');
@ -2186,7 +2182,7 @@ class UsersController extends AppController
'fields' => ['id', 'name'],
));
foreach ($organisations as $id => $foo) {
if (!$this->User->Organisation->canSee($this->Auth->user(), $id)) {
if (!$this->User->Organisation->canSee($user, $id)) {
unset($organisations[$id]);
}
}
@ -2225,7 +2221,7 @@ class UsersController extends AppController
}
$elementCounter = 0;
$renderView = '';
$final = $this->Event->restSearch($this->Auth->user(), 'attack', $filters, false, false, $elementCounter, $renderView);
$final = $this->Event->restSearch($user, 'attack', $filters, false, false, $elementCounter, $renderView);
$final = json_decode($final, true);
if (!empty($final)) {

View File

@ -11,7 +11,7 @@ class DistributionGraphTool
/** @var array */
private $__serverList;
public function construct(Event $eventModel, array $servers, array $user, $extended_view=0)
public function __construct(Event $eventModel, array $servers, array $user, $extended_view=0)
{
$this->__eventModel = $eventModel;
$this->__serverList = $servers;
@ -33,8 +33,6 @@ class DistributionGraphTool
];
}
$this->__json['distributionInfo'][5] = ""; // inherit event. Will be deleted afterward
return true;
}
private function __fetchAndAddDistributionInfo($elem)

View File

@ -2,16 +2,6 @@
class RandomTool
{
public function __construct()
{
// import compatibility library for PHP < 7.0
if (!function_exists('random_int')) {
if (file_exists(APP . 'Lib' . DS . 'random_compat' . DS . 'lib' . DS . 'random.php')) {
require_once(APP . 'Lib' . DS . 'random_compat' . DS . 'lib' . DS . 'random.php');
}
}
}
/**
* Generate a random string
*
@ -27,24 +17,19 @@ class RandomTool
* @param int $length - How long should our random string be?
* @param string $charset - A string of all possible characters to choose from
* @return string
* @throws Exception
*/
public function random_str($crypto_secure = true, $length = 32, $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
// Type checks:
if (!is_bool($crypto_secure)) {
throw new InvalidArgumentException(
'random_str - Argument 1 - expected a boolean'
);
throw new InvalidArgumentException('random_str - Argument 1 - expected a boolean');
}
if (!is_numeric($length)) {
throw new InvalidArgumentException(
'random_str - Argument 2 - expected an integer'
);
throw new InvalidArgumentException('random_str - Argument 2 - expected an integer');
}
if (!is_string($charset)) {
throw new InvalidArgumentException(
'random_str - Argument 3 - expected a string'
);
throw new InvalidArgumentException('random_str - Argument 3 - expected a string');
}
if ($length < 1) {
@ -53,25 +38,18 @@ class RandomTool
}
// Remove duplicate characters from $charset
$split = str_split($charset);
$charset = implode('', array_unique($split));
$charset = count_chars($charset, 3);
// This is the maximum index for all of the characters in the string $charset
$charset_max = strlen($charset) - 1;
if ($charset_max < 1) {
// Avoid letting users do: random_str($int, 'a'); -> 'aaaaa...'
throw new LogicException(
'random_str - Argument 3 - expected a string that contains at least 2 distinct characters'
);
throw new LogicException('random_str - Argument 3 - expected a string that contains at least 2 distinct characters');
}
// Now that we have good data, this is the meat of our function:
$random_str = '';
for ($i = 0; $i < $length; ++$i) {
if ($crypto_secure && function_exists('random_int')) {
$r = random_int(0, $charset_max);
} else {
$r = mt_rand(0, $charset_max);
}
$r = $crypto_secure ? random_int(0, $charset_max) : mt_rand(0, $charset_max);
$random_str .= $charset[$r];
}
return $random_str;

@ -1 +0,0 @@
Subproject commit 088c04e2f261c33bed6ca5245491cfca69195ccf

View File

@ -41,7 +41,6 @@ class Dashboard extends AppModel
$subDirectories = $customdir->read();
$found = false;
foreach ($subDirectories[0] as $subDir) {
$currentDir = new Folder(APP . 'Lib/Dashboard/' . $subDir);
if (file_exists(APP . 'Lib/Dashboard/Custom/' . $subDir . '/' . $name . '.php')) {
App::uses($name, 'Dashboard/Custom/' . $subDir);
$found = true;