mirror of https://github.com/MISP/MISP
new: [auth] Allow to enforce auth plugin authentication
parent
56159d4791
commit
2c7d6e4466
|
@ -73,6 +73,9 @@ class AppController extends Controller
|
|||
|
||||
protected $_legacyParams = array();
|
||||
|
||||
/** @var User */
|
||||
public $User;
|
||||
|
||||
public function __construct($id = false, $table = null, $ds = null)
|
||||
{
|
||||
parent::__construct($id, $table, $ds);
|
||||
|
@ -154,8 +157,8 @@ class AppController extends Controller
|
|||
|
||||
$this->set('ajax', $this->request->is('ajax'));
|
||||
$this->set('queryVersion', $this->__queryVersion);
|
||||
$this->loadModel('User');
|
||||
$auth_user_fields = $this->User->describeAuthFields();
|
||||
$this->User = ClassRegistry::init('User');
|
||||
|
||||
$language = Configure::read('MISP.language');
|
||||
if (!empty($language) && $language !== 'eng') {
|
||||
Configure::write('Config.language', $language);
|
||||
|
@ -174,18 +177,19 @@ class AppController extends Controller
|
|||
$this->Server->serverSettingsSaveValue('MISP.uuid', CakeText::uuid());
|
||||
}
|
||||
// check if Apache provides kerberos authentication data
|
||||
$authUserFields = $this->User->describeAuthFields();
|
||||
$envvar = Configure::read('ApacheSecureAuth.apacheEnv');
|
||||
if (isset($_SERVER[$envvar])) {
|
||||
if ($envvar && isset($_SERVER[$envvar])) {
|
||||
$this->Auth->className = 'ApacheSecureAuth';
|
||||
$this->Auth->authenticate = array(
|
||||
'Apache' => array(
|
||||
// envvar = field returned by Apache if user is authenticated
|
||||
'fields' => array('username' => 'email', 'envvar' => $envvar),
|
||||
'userFields' => $auth_user_fields
|
||||
'userFields' => $authUserFields,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$this->Auth->authenticate['Form']['userFields'] = $auth_user_fields;
|
||||
$this->Auth->authenticate[AuthComponent::ALL]['userFields'] = $authUserFields;
|
||||
}
|
||||
if (!empty($this->params['named']['disable_background_processing'])) {
|
||||
Configure::write('MISP.background_jobs', 0);
|
||||
|
@ -1152,13 +1156,24 @@ class AppController extends Controller
|
|||
$this->redirect($targetRoute);
|
||||
}
|
||||
|
||||
protected function _loadAuthenticationPlugins() {
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _loadAuthenticationPlugins()
|
||||
{
|
||||
// load authentication plugins from Configure::read('Security.auth')
|
||||
$auth = Configure::read('Security.auth');
|
||||
|
||||
if (!$auth) return;
|
||||
|
||||
if (!$auth) {
|
||||
return;
|
||||
}
|
||||
if (!is_array($auth)) {
|
||||
throw new Exception("`Security.auth` config value must be array.");
|
||||
}
|
||||
$this->Auth->authenticate = array_merge($auth, $this->Auth->authenticate);
|
||||
// Disable Form authentication
|
||||
if (Configure::read('Security.auth_enforced')) {
|
||||
unset($this->Auth->authenticate['Form']);
|
||||
}
|
||||
if ($this->Auth->startup($this)) {
|
||||
$user = $this->Auth->user();
|
||||
if ($user) {
|
||||
|
|
|
@ -1139,25 +1139,9 @@ class UsersController extends AppController
|
|||
if (!empty($this->request->data['User']['email'])) {
|
||||
if ($this->Bruteforce->isBlocklisted($_SERVER['REMOTE_ADDR'], $this->request->data['User']['email'])) {
|
||||
$expire = Configure::check('SecureAuth.expire') ? Configure::read('SecureAuth.expire') : 300;
|
||||
throw new ForbiddenException('You have reached the maximum number of login attempts. Please wait ' . Configure::read('SecureAuth.expire') . ' seconds and try again.');
|
||||
throw new ForbiddenException('You have reached the maximum number of login attempts. Please wait ' . $expire . ' seconds and try again.');
|
||||
}
|
||||
}
|
||||
// Check the length of the user's authkey
|
||||
$userPass = $this->User->find('first', array(
|
||||
'conditions' => array('User.email' => $this->request->data['User']['email']),
|
||||
'fields' => array('User.password'),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (!empty($userPass) && strlen($userPass['User']['password']) == 40) {
|
||||
$this->AdminSetting = ClassRegistry::init('AdminSetting');
|
||||
$db_version = $this->AdminSetting->find('all', array('conditions' => array('setting' => 'db_version')));
|
||||
$versionRequirementMet = $this->User->checkVersionRequirements($db_version[0]['AdminSetting']['value'], '2.4.77');
|
||||
if ($versionRequirementMet) {
|
||||
$passwordToSave = $this->request->data['User']['password'];
|
||||
}
|
||||
unset($this->Auth->authenticate['Form']['passwordHasher']);
|
||||
$this->Auth->constructAuthenticate();
|
||||
}
|
||||
}
|
||||
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
|
||||
$user = $this->Auth->identify($this->request, $this->response);
|
||||
|
@ -1166,6 +1150,9 @@ class UsersController extends AppController
|
|||
return $this->redirect('email_otp');
|
||||
}
|
||||
}
|
||||
$formLoginEnabled = isset($this->Auth->authenticate['Form']);
|
||||
$this->set('formLoginEnabled', $formLoginEnabled);
|
||||
|
||||
if ($this->Auth->login()) {
|
||||
$this->_postlogin();
|
||||
} else {
|
||||
|
|
|
@ -2542,19 +2542,6 @@ class AppModel extends Model
|
|||
$this->elasticSearchClient = $client;
|
||||
}
|
||||
|
||||
public function checkVersionRequirements($versionString, $minVersion)
|
||||
{
|
||||
$version = explode('.', $versionString);
|
||||
$minVersion = explode('.', $minVersion);
|
||||
if (count($version) > $minVersion) {
|
||||
return true;
|
||||
}
|
||||
if (count($version) == 1) {
|
||||
return $minVersion <= $version;
|
||||
}
|
||||
return ($version[0] >= $minVersion[0] && $version[1] >= $minVersion[1] && $version[2] >= $minVersion[2]);
|
||||
}
|
||||
|
||||
// generate a generic subquery - options needs to include conditions
|
||||
public function subQueryGenerator($model, $options, $lookupKey, $negation = false)
|
||||
{
|
||||
|
|
|
@ -1343,6 +1343,14 @@ class Server extends AppModel
|
|||
'test' => 'testBool',
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'auth_enforced' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('This optional can be enabled if external auth provider is used and when set to true, it will disable default form authentication.'),
|
||||
'value' => false,
|
||||
'errorMessage' => '',
|
||||
'test' => 'testBool',
|
||||
'type' => 'boolean',
|
||||
],
|
||||
'rest_client_enable_arbitrary_urls' => array(
|
||||
'level' => 0,
|
||||
'description' => __('Enable this setting if you wish for users to be able to query any arbitrary URL via the rest client. Keep in mind that queries are executed by the MISP server, so internal IPs in your MISP\'s network may be reachable.'),
|
||||
|
|
|
@ -862,18 +862,19 @@ class User extends AppModel
|
|||
return $gpgTool->fetchGpgKey($fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fields that should be fetched from database.
|
||||
* @return array
|
||||
*/
|
||||
public function describeAuthFields()
|
||||
{
|
||||
$fields = array();
|
||||
$fields = array_merge($fields, array_keys($this->getColumnTypes()));
|
||||
foreach ($fields as $k => $field) {
|
||||
if (in_array($field, array('gpgkey', 'certif_public'))) {
|
||||
unset($fields[$k]);
|
||||
}
|
||||
}
|
||||
$fields = array_values($fields);
|
||||
$relatedModels = array_keys($this->belongsTo);
|
||||
foreach ($relatedModels as $relatedModel) {
|
||||
$fields = $this->schema();
|
||||
// Do not include keys, because they are big and usually not necessary
|
||||
unset($fields['gpgkey']);
|
||||
unset($fields['certif_public']);
|
||||
$fields = array_keys($fields);
|
||||
|
||||
foreach ($this->belongsTo as $relatedModel => $foo) {
|
||||
$fields[] = $relatedModel . '.*';
|
||||
}
|
||||
return $fields;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
</div>
|
||||
<?php
|
||||
endif;
|
||||
if ($formLoginEnabled):
|
||||
echo $this->Form->create('User');
|
||||
?>
|
||||
<legend><?php echo __('Login');?></legend>
|
||||
|
@ -52,6 +53,7 @@
|
|||
<?= $this->Form->button(__('Login'), array('class' => 'btn btn-primary')); ?>
|
||||
<?php
|
||||
echo $this->Form->end();
|
||||
endif;
|
||||
if (Configure::read('ApacheShibbAuth') == true) {
|
||||
echo '<div class="clear"></div><a class="btn btn-info" href="/Shibboleth.sso/Login">Login with SAML</a>';
|
||||
}
|
||||
|
@ -65,7 +67,7 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(function() {
|
||||
$('#UserLoginForm').submit(function(event) {
|
||||
event.preventDefault()
|
||||
submitLoginForm()
|
||||
|
|
|
@ -778,7 +778,45 @@ class TestSecurity(unittest.TestCase):
|
|||
self.admin_misp_connector.delete_user(user)
|
||||
self.admin_misp_connector.delete_organisation(json_response["User"]["org_id"])
|
||||
|
||||
def __shibb_login(self, headers: dict):
|
||||
def test_shibb_form_login(self):
|
||||
with MISPComplexSetting(self.__default_shibb_config()):
|
||||
# Form login should still works when no header provided
|
||||
self.assertTrue(login(url, self.test_usr.email, self.test_usr_password))
|
||||
|
||||
def test_shibb_api_login(self):
|
||||
with MISPComplexSetting(self.__default_shibb_config()):
|
||||
PyMISP(url, self.test_usr.authkey)
|
||||
|
||||
def test_shibb_enforced_existing_user(self):
|
||||
config = self.__default_shibb_config()
|
||||
config["Security"]["auth_enforced"] = True
|
||||
with MISPComplexSetting(config):
|
||||
r = self.__shibb_login({
|
||||
"Email-Tag": self.test_usr.email,
|
||||
"Federation-Tag": self.test_org.name,
|
||||
"Group-Tag": "user",
|
||||
})
|
||||
r.raise_for_status()
|
||||
json_response = r.json()
|
||||
self.assertEqual(self.test_usr.email, json_response["User"]["email"])
|
||||
self.assertEqual(3, int(json_response["User"]["role_id"]))
|
||||
self.assertEqual(self.test_org.name, json_response["Organisation"]["name"])
|
||||
|
||||
def test_shibb_enforced_form_login(self):
|
||||
config = self.__default_shibb_config()
|
||||
config["Security"]["auth_enforced"] = True
|
||||
with MISPComplexSetting(config):
|
||||
# Form login should not work when shibb is enforced, because form doesn't exists
|
||||
with self.assertRaises(IndexError):
|
||||
login(url, self.test_usr.email, self.test_usr_password)
|
||||
|
||||
def test_shibb_enforced_api_login(self):
|
||||
config = self.__default_shibb_config()
|
||||
config["Security"]["auth_enforced"] = True
|
||||
with MISPComplexSetting(config):
|
||||
PyMISP(url, self.test_usr.authkey)
|
||||
|
||||
def __shibb_login(self, headers: dict) -> requests.Response:
|
||||
session = requests.Session()
|
||||
session.headers.update(headers)
|
||||
|
||||
|
@ -786,7 +824,11 @@ class TestSecurity(unittest.TestCase):
|
|||
if 500 <= r.status_code < 600:
|
||||
raise Exception(r)
|
||||
|
||||
return session.get(url + "/users/view/me.json")
|
||||
r = session.get(url + "/users/view/me.json")
|
||||
if 500 <= r.status_code < 600:
|
||||
raise Exception(r)
|
||||
|
||||
return r
|
||||
|
||||
def __create_user(self, org_id: int = None, role_id: Union[int, ROLE] = None) -> MISPUser:
|
||||
if isinstance(role_id, ROLE):
|
||||
|
|
Loading…
Reference in New Issue