new: [communities] Added support for requesting access for known communities

- site admins can list the misp-project maintained community list
- request access to any of the communities
pull/5081/head
iglocska 2019-08-29 09:24:33 +02:00
parent 58148c0aa3
commit feb7fe82dd
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
7 changed files with 573 additions and 3 deletions

View File

@ -0,0 +1,154 @@
<?php
App::uses('AppController', 'Controller');
class CommunitiesController extends AppController
{
public $components = array('Session', 'RequestHandler');
public function beforeFilter()
{
parent::beforeFilter();
}
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999
);
public function index()
{
$paramsToHarvest = array('context', 'value');
foreach ($paramsToHarvest as $param) {
if (!empty($this->params['named'][$param])) {
${$param} = $this->params['named'][$param];
} else if ($this->request->is('post') && !empty($this->request->data[$param])) {
${$param} = trim($this->request->data[$param]);
} else if ($param === 'context') {
${$param} = 'vetted';
}
}
$filterData = array(
'request' => $this->request,
'paramArray' => array('context', 'value'),
'named_params' => $this->params['named']
);
$exception = false;
$filters = $this->_harvestParameters($filterData, $exception);
if (empty($filters['context'])) {
$filters['context'] = 'vetted';
}
if (!empty($value)) {
$value = strtolower($value);
} else {
$value = false;
}
$community_list = $this->Community->getCommunityList($context, $value);
//foreach ($community)
if ($this->_isRest()) {
return $this->RestResponse->viewData($community_list, $this->response->type());
}
App::uses('CustomPaginationTool', 'Tools');
$customPagination = new CustomPaginationTool();
$customPagination->truncateAndPaginate($community_list, $this->params, $this->modelClass, true);
$this->set('community_list', $community_list);
$this->set('context', $filters['context']);
$this->set('passedArgs', json_encode($this->passedArgs, true));
}
public function view($id)
{
$community = $this->Community->getCommunity($id);
if ($this->_isRest()) {
return $this->RestResponse->viewData($community, $this->response->type());
} else {
$this->set('community', $community);
}
}
public function requestAccess($id)
{
$community = $this->Community->getCommunity($id);
$this->loadModel('User');
$gpgkey = $this->User->find('first', array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'recursive' => -1,
'fields' => array('User.gpgkey')
));
if (!$this->request->is('post')) {
$this->request->data['Server']['email'] = $this->Auth->user('email');
$this->request->data['Server']['org_name'] = $this->Auth->user('Organisation')['name'];
$this->request->data['Server']['org_uuid'] = $this->Auth->user('Organisation')['uuid'];
$this->request->data['Server']['gpgkey'] = $gpgkey['User']['gpgkey'];
} else {
if (empty($this->request->data['Server'])) {
$this->request->data = array('Server' => $this->request->data);
}
$body = sprintf(
'To whom it may concern,
On behalf of my organisation (%s - %s),
I would hereby like to request %saccess to your MISP community:
%s
A brief description of my organisation:
%s
My e-mail address that I wish to use as my username:
%s
%s%s
Thank you in advance!',
$this->request->data['Server']['org_name'],
$this->request->data['Server']['org_uuid'],
empty($this->request->data['Server']['sync']) ? '' : 'synchronisation ',
$community['community_name'],
$this->request->data['Server']['org_description'],
$this->request->data['Server']['email'],
empty($this->request->data['Server']['gpgkey']) ? '' : sprintf(
'%sThe associated PGP key:%s%s%s',
PHP_EOL,
PHP_EOL,
$this->request->data['Server']['gpgkey'],
PHP_EOL
),
empty($this->request->data['Server']['message']) ? '' : sprintf(
'%sAdditional information:%s%s%s',
PHP_EOL,
PHP_EOL,
$this->request->data['Server']['message'],
PHP_EOL
)
);
$imgPath = APP . WEBROOT_DIR . DS . 'img' . DS . 'orgs' . DS;
$possibleFields = array('id', 'name');
$image = false;
App::uses('File', 'Utility');
foreach ($possibleFields as $field) {
if (isset($options[$field])) {
$file = new File($imgPath . $options[$field] . 'png');
if ($file->exists()) {
$image = $file->read();
break;
}
}
}
if (!empty($image)) {
$params['attachments']['logo.png'] = $image;
}
if (!empty($gpgkey)) {
$params['attachments']['requestor.asc'] = $gpgkey;
}
$params = array();
$params['to'] = $community['email'];
$params['reply-to'] = $this->request->data['Server']['email'];
$params['requestor_gpgkey'] = $this->request->data['Server']['gpgkey'];
$params['gpgkey'] = $community['pgp_key'];
$params['body'] = $body;
$params['subject'] = '[' . $community['community_name'] . '] Requesting MISP access';
$result = $this->User->sendEmailExternal($this->Auth->user(), $params);
}
$this->set('community', $community);
}
}

View File

@ -2089,4 +2089,31 @@ misp.direct_call(relative_path, body)
}
}
}
public function listCommunities()
{
$server_file = new File(APP . 'files/server-metadata/defaults.json');
if (!$server_file->exists()) {
throw new NotFoundException(__('Default community list not found.'));
}
$server_list = $server_file->read();
if (empty($server_list)) {
throw new NotFoundException(__('Default community list empty.'));
}
try {
$server_list = json_decode($server_list, true);
} catch (Exception $e) {
throw new NotFoundException(__('Default community list not in the expected format.'));
}
if ($this->__isRest()) {
return $this->RestResponse->viewData($server_list, $this->response->type());
} else {
$this->set('server_list', $server_list);
}
}
public function viewCommunity()
{
}
}

97
app/Model/Community.php Normal file
View File

@ -0,0 +1,97 @@
<?php
App::uses('AppModel', 'Model');
class Community extends AppModel
{
public $useTable = false;
public $recursive = -1;
public $actsAs = array(
'Containable',
);
public $validate = array(
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
return true;
}
public function getCommunityList($context, $value)
{
$community_file = new File(APP . 'files/community-metadata/defaults.json');
if (!$community_file->exists()) {
throw new NotFoundException(__('Default community list not found.'));
}
$community_list = $community_file->read();
if (empty($community_list)) {
throw new NotFoundException(__('Default community list empty.'));
}
try {
$community_list = json_decode($community_list, true);
} catch (Exception $e) {
throw new NotFoundException(__('Default community list not in the expected format.'));
}
$fieldsToCheck = array('community_name', 'community_uuid', 'description', 'url', 'sector', 'nationality', 'type_of_community', 'org_uuid', 'org_name', 'rules');
foreach ($community_list as $k => $v) {
if ($v['misp_project_vetted'] === ($context === 'vetted')) {
$community_list[$k]['id'] = $k + 1;
$community_list[$k]['Org'] = array('uuid' => $v['org_uuid'], 'name' => $v['org_name']);
} else {
unset($community_list[$k]);
continue;
}
if (!empty($value)) {
$found = false;
foreach ($fieldsToCheck as $field) {
if (strpos(strtolower($v[$field]), $value) !== false) {
$found = true;
continue;
}
}
if (!$found) {
unset($community_list[$k]);
}
}
}
$community_list = array_values($community_list);
return $community_list;
}
public function getCommunity($id)
{
$community_file = new File(APP . 'files/community-metadata/defaults.json');
if (!$community_file->exists()) {
throw new NotFoundException(__('Default community list not found.'));
}
$community_list = $community_file->read();
if (empty($community_list)) {
throw new NotFoundException(__('Default community list empty.'));
}
try {
$community_list = json_decode($community_list, true);
} catch (Exception $e) {
throw new NotFoundException(__('Default community list not in the expected format.'));
}
foreach ($community_list as $k => $v) {
$community_list[$k]['id'] = $k + 1;
$community_list[$k]['Org'] = array('uuid' => $v['org_uuid'], 'name' => $v['org_name']);
}
$community = false;
$lookupField = 'id';
if (Validation::uuid($id)) {
$lookupField = 'uuid';
}
foreach ($community_list as $s) {
if ($s[$lookupField === 'uuid' ? 'community_uuid' : 'id'] === $id) {
$community = $s;
}
}
if (empty($community)) {
throw new NotFoundException(__('Community not found.'));
}
return $community;
}
}

View File

@ -739,6 +739,64 @@ class User extends AppModel
return $users;
}
public function sendEmailExternal($user, $params)
{
$this->Log = ClassRegistry::init('Log');
if (Configure::read('MISP.disable_emailing')) {
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
'action' => 'email',
'title' => 'Email to ' . $user['email'] . ', titled "' . $params['subject'] . '" failed. Reason: Emailing is currently disabled on this instance.',
'change' => null,
));
return true;
}
$params['body'] = str_replace('\n', PHP_EOL, $params['body']);
$Email = new CakeEmail();
$recipient = array('User' => array('email' => $params['to']));
$failed = false;
if (!empty($params['gpgkey'])) {
$recipient['User']['gpgkey'] = $params['gpgkey'];
$encryptionResult = $this->__encryptUsingGPG($Email, $params['body'], $params['subject'], $recipient);
if (isset($encryptionResult['failed'])) {
$failed = true;
}
if (isset($encryptionResult['failureReason'])) {
$failureReason = $encryptionResult['failureReason'];
}
}
if (!$failed) {
$replyToLog = '';
$user = array('User' => $user);
$attachments = array();
$Email->replyTo($params['reply-to']);
if (!empty($params['requestor_gpgkey'])) {
$attachments['gpgkey.asc'] = array(
'data' => $params['requestor_gpgkey']
);
}
$Email->from(Configure::read('MISP.email'));
$Email->returnPath(Configure::read('MISP.email'));
$Email->to($params['to']);
$Email->subject($params['subject']);
$Email->emailFormat('text');
if (!empty($params['attachments'])) {
foreach ($params['attachments'] as $key => $value) {
$attachments[$k] = array('data' => $value);
}
}
$Email->attachments($attachments);
$result = $Email->send($params['body']);
$Email->reset();
return $result;
}
return false;
}
// all e-mail sending is now handled by this method
// Just pass the user ID in an array that is the target of the e-mail along with the message body and the alternate message body if the message cannot be encrypted
// the remaining two parameters are the e-mail subject and a secondary user object which will be used as the replyto address if set. If it is set and an encryption key for the replyTo user exists, then his/her public key will also be attached
@ -833,16 +891,21 @@ class User extends AppModel
return false;
}
private function __finaliseAndSendEmail($replyToUser, &$Email, &$replyToLog, $user, $subject, $body)
private function __finaliseAndSendEmail($replyToUser, &$Email, &$replyToLog, $user, $subject, $body, $additionalAttachments = false)
{
// If the e-mail is sent on behalf of a user, then we want the target user to be able to respond to the sender
// For this reason we should also attach the public key of the sender along with the message (if applicable)
$attachments = array();
if ($replyToUser != false) {
$Email->replyTo($replyToUser['User']['email']);
if (!empty($replyToUser['User']['gpgkey'])) {
$Email->attachments(array('gpgkey.asc' => array('data' => $replyToUser['User']['gpgkey'])));
$attachments['gpgkey.asc'] = array(
'data' => $replyToUser['User']['gpgkey']
);
} elseif (!empty($replyToUser['User']['certif_public'])) {
$Email->attachments(array($replyToUser['User']['email'] . '.pem' => array('data' => $replyToUser['User']['certif_public'])));
$attachments[$replyToUser['User']['email'] . '.pem'] = array(
'data' => $replyToUser['User']['certif_public']
);
}
$replyToLog = 'from ' . $replyToUser['User']['email'];
}
@ -851,6 +914,12 @@ class User extends AppModel
$Email->to($user['User']['email']);
$Email->subject($subject);
$Email->emailFormat('text');
if (!empty($additionalAttachments)) {
foreach ($additionalAttachments as $key => $value) {
$attachments[$k] = array('data' => $value);
}
}
$Email->attachments($attachments);
$result = $Email->send($body);
$Email->reset();
return $result;

View File

@ -0,0 +1,101 @@
<?php
echo '<div class="index">';
echo $this->element('/genericElements/IndexTable/index_table', array(
'data' => array(
'data' => $community_list,
'top_bar' => array(
'children' => array(
array(
'type' => 'simple',
'children' => array(
array(
'active' => $context === 'vetted',
'url' => $baseurl . '/communities/index/context:vetted',
'text' => __('Vetted by the MISP-project team'),
),
array(
'active' => $context === 'unvetted',
'url' => $baseurl . '/communities/index/context:unvetted',
'text' => __('Unvetted'),
)
)
),
array(
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
)
)
),
'fields' => array(
array(
'name' => __('Id'),
'sort' => 'id',
'class' => 'short',
'data_path' => 'id',
),
array(
'name' => __('Vetted'),
'element' => 'boolean',
'class' => 'short',
'data_path' => 'misp_project_vetted',
),
array(
'name' => __('Host org'),
'sort' => 'org_name',
'class' => 'short',
'element' => 'org',
'data_path' => 'Org',
),
array(
'name' => __('Community name'),
'sort' => 'community_name',
'class' => 'short',
'data_path' => 'community_name',
),
array(
'name' => __('Description'),
'data_path' => 'description',
)
),
'title' => __('Communities index'),
'description' => __('You can find a list of communities below that chose to advertise their existence to the general MISP user-base.\nRequesting access to any of those communities is of course no guarantee of being permitted access, it is simply meant to simplify to means of finding the various communities that one may be eligible for. Get in touch with the misp-project maintainers if you would like your community to be included in the list.'),
'actions' => array(
array(
'url' => '/communities/view',
'url_params_data_paths' => array(
'community_uuid'
),
'icon' => 'eye'
),
array(
'url' => '/communities/requestAccess/%s',
'url_params_data_paths' => array(
'community_uuid'
),
'icon' => 'comments'
)
)
)
));
echo '</div>';
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'sync', 'menuItem' => 'list_communities'));
?>
<script type="text/javascript">
var passedArgsArray = <?php echo $passedArgs; ?>;
if (passedArgsArray['context'] === undefined) {
passedArgsArray['context'] = 'pending';
}
$(document).ready(function() {
$('#quickFilterButton').click(function() {
runIndexQuickFilter('/context:' + passedArgsArray['context']);
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter('/context:' + passedArgsArray['context']);
}
});
});
</script>

View File

@ -0,0 +1,62 @@
<div class="attributes form">
<?php
echo $this->Form->create('Server', array('id', 'url' => '/communities/requestAccess/' . $community['community_uuid']));
echo sprintf(
'<fieldset><legend>%s</legend>%s</fieldset>%s',
'Request access to ' . h($community['community_name']),
(
$this->Form->input('email', array(
'label' => __('Requestor E-mail address'),
'div' => 'input clear',
'class' => 'input-xxlarge'
)) .
$this->Form->input('org_name', array(
'label' => __('Organisation name'),
'div' => 'input clear',
'class' => 'input-xxlarge'
)) .
$this->Form->input('org_uuid', array(
'label' => __('Organisation uuid'),
'div' => 'input clear',
'class' => 'input-xxlarge'
)) .
$this->Form->input('org_description', array(
'label' => __('Description of the requestor organisation'),
'div' => 'input clear',
'type' => 'textarea',
'class' => 'input-xxlarge'
)) .
$this->Form->input('message', array(
'label' => __('Message to the community host organisation'),
'div' => 'input clear',
'type' => 'textarea',
'class' => 'input-xxlarge'
)) .
$this->Form->input('gpgkey', array(
'label' => __('PGP public key'),
'div' => 'input clear',
'type' => 'textarea',
'class' => 'input-xxlarge'
)) .
$this->element('/genericElements/Forms/clear') .
$this->Form->input('sync', array(
'label' => __('Request sync access'),
'type' => 'checkbox'
))
),
$this->Form->button('Submit', array(
'class' => 'btn btn-primary',
'div' => 'input clear',
)) .
$this->Form->end()
);
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'sync', 'menuItem' => 'request_community_access'));
?>
<script type="text/javascript">
$(document).ready(function() {
});
</script>
<?php echo $this->Js->writeBuffer();

View File

@ -0,0 +1,60 @@
<div class="communities view">
<?php
$table_data = array();
$table_data[] = array('key' => __('Id'), 'value' => $community['id']);
$table_data[] = array('key' => __('UUID'), 'value' => $community['community_uuid']);
$table_data[] = array('key' => __('Name'), 'value' => $community['community_name']);
$table_data[] = array('key' => __('Host organisation'), 'value' => $community['org_name'] . '(' . $community['org_uuid'] . ')');
$table_data[] = array(
'key' => __('Vetted by MISP-project'),
'html' => sprintf(
'<dd><span class="%s bold">%s</span></dd>',
$community['misp_project_vetted'] ? 'green' : 'red',
$community['misp_project_vetted'] ? __('Yes') : __('No')
)
);
$optional_fields = array(
'type_of_community', 'description', 'rules', 'email', 'sector', 'nationality', 'eligibility', 'pgp_key'
);
foreach ($optional_fields as $field) {
if (!empty($community[$field])) {
$table_data[] = array('key' => Inflector::humanize($field), 'value' => $community[$field]);
}
}
//misp-project.org/org-logos/uuid.png
echo sprintf(
'<div class="row-fluid"><div class="span8" style="margin:0px;">%s</div></div>',
sprintf(
'%s<h2>%s</h2>%s',
sprintf(
'<img src="https://misp-project.org/org-logos/%s.png" title="%s" aria-label="%s"/>',
h($community['org_uuid']),
h($community['org_name']),
h($community['org_name'])
),
__('Community ') . h($community['community_name']),
$this->element('genericElements/viewMetaTable', array('table_data' => $table_data))
)
);
echo sprintf(
'<a href="%s%s%s" class="btn btn-primary">%s</a>',
$baseurl,
'/communities/requestAccess/',
h($community['community_uuid']),
__('Request Access')
);
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'sync', 'menuItem' => 'view_community'));
?>
<script type="text/javascript">
<?php
$startingTab = 'description';
if (!$local) $startingTab = 'events';
?>
$(document).ready(function () {
organisationViewContent('<?php echo $startingTab; ?>', '<?php echo h($id);?>');
});
</script>