Skip to content

Commit

Permalink
Merge pull request #79 from TheDMSGroup/ENG-368-documentation-auth
Browse files Browse the repository at this point in the history
[ENG-368] Soft authentication for all source documentation.
  • Loading branch information
heathdutton authored Jul 3, 2018
2 parents 6333311 + fd9082e commit 0bbf4bb
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 144 deletions.
18 changes: 17 additions & 1 deletion Controller/Api/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ public function handlerAction(
) {
$start = microtime(true);

// Handle documentation authentication.
if ($request->get('documentationAuthAttempt')) {
if (!$sourceId) {
$sourceId = (int) $request->get('sourceId');
}

/** @var \MauticPlugin\MauticContactSourceBundle\Model\Api $ApiModel */
$ApiModel = $this->get('mautic.contactsource.model.api');
$ApiModel
->setRequest($request)
->setSourceId((int) $sourceId)
->setCampaignId((int) $campaignId)
->handleInputPublic();

return $this->redirect($this->request->getUri());
}

$object = strtolower($object);
if ('contact' == $object) {
/** @var \MauticPlugin\MauticContactSourceBundle\Model\Api $ApiModel */
Expand All @@ -83,7 +100,6 @@ public function handlerAction(
'statusCode' => Codes::HTTP_NOT_IMPLEMENTED,
];
}

$result['time'] = [
'completed' => new \DateTime(),
'duration' => microtime(true) - $start,
Expand Down
8 changes: 4 additions & 4 deletions Controller/ContactSourceAccessTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ protected function checkContactSourceAccess($contactSourceId, $action, $isPlugin
return $this->notFound('mautic.contact.error.notfound');
}
} elseif (!$this->get('mautic.security')->hasEntityAccess(
'contactSource:contactSources:'.$action.'own',
'contactSource:contactSources:'.$action.'other',
'contactsource:items:'.$action.'own',
'contactsource:items:'.$action.'other',
$contactSource->getPermissionUser()
)
) {
Expand Down Expand Up @@ -116,8 +116,8 @@ protected function checkAllAccess($action, $limit)

foreach ($contactSources as $contactSource) {
if (!$this->get('mautic.security')->hasEntityAccess(
'contactSource:contactSources:'.$action.'own',
'contactSource:contactSources:'.$action.'other',
'contactsource:items:'.$action.'own',
'contactsource:items:'.$action.'other',
$contactSource['createdBy']
)
) {
Expand Down
56 changes: 25 additions & 31 deletions Controller/PublicController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,12 @@
namespace MauticPlugin\MauticContactSourceBundle\Controller;

use Mautic\CoreBundle\Controller\CommonController;
use MauticPlugin\MauticContactSourceBundle\Model\Api;
use MauticPlugin\MauticContactSourceBundle\Entity\ContactSource;
use Symfony\Component\HttpFoundation\Request;

class PublicController extends CommonController
{
// @todo - Add documentation autogenerator.
public function getDocumentationAction($sourceId = null, $campaignId = null)
{
// @todo - Check Source existence and published status.

// @todo - Check if documentation is turned on, if not 403.

// @todo - Get list of assigned and published Campaigns.

// @todo - Get list of Source+Campaign required fields.

// @todo - Get list of Source+Campaign limits.

// @todo - Get sync status (async/sync).

// @todo - Generate document.

return $this->render(
'MauticContactSourceBundle:Documentation:details.html.php',
[
'documentation' => 'documentation to go here',
]
);
}
use ContactSourceAccessTrait;

/**
* @param Request $request
Expand Down Expand Up @@ -95,21 +72,20 @@ public function handlerAction(
$parameters['global']['domain'] = $this->get('mautic.helper.core_parameters')->getParameter('site_url');
}
$parameters['global']['domain'] = rtrim('/', $parameters['global']['domain']);
$parameters['source'] = $result['source'];
$parameters['source'] = isset($result['source']) ? $result['source'] : null;
$parameters['sourceId'] = $sourceId;
$parameters['FieldList'] = $ApiModel->getAllowedFields(false);

if (!isset($result['campaign']['name'])) {
$parameters['authenticated'] = $result['authenticated'];
if (!isset($result['campaign']['name']) && isset($result['source']['name'])) {
// No valid campaign specified, should show the listing of all campaigns.
$view = 'MauticContactSourceBundle:Documentation:details.html.php';
$parameters['title'] = $this->translator->trans(
'mautic.contactsource.api.docs.source_title',
[
'%source%' => $result['source']['name'],
]
);
} elseif (isset($result['campaign']['name'])) {
} elseif (isset($result['campaign']['name']) && isset($result['source']['name'])) {
// Valid campaign is specified, should include hash or direct link to that campaign.
$view = 'MauticContactSourceBundle:Documentation:details.html.php';
$parameters['title'] = $this->translator->trans(
'mautic.contactsource.api.docs.campaign_title',
[
Expand All @@ -124,6 +100,24 @@ public function handlerAction(
$this->notFound('mautic.contactsource.api.docs.not_found');
}

// Attempt auth by permissions (assuming logged in user).
if (!$parameters['authenticated']) {
$anonymous = $this->get('mautic.security')->isAnonymous();
if (!$anonymous) {
$contactSource = $this->checkContactSourceAccess($sourceId, 'view');
if ($contactSource instanceof ContactSource) {
$parameters['authenticated'] = true;
}
}
}

if (!$parameters['authenticated']) {
$parameters['title'] = $this->translator->trans('mautic.contactsource.api.docs.auth_title');
$view = 'MauticContactSourceBundle:Documentation:auth.html.php';
} else {
$view = 'MauticContactSourceBundle:Documentation:details.html.php';
}

return $this->render($view, $parameters);
}
}
12 changes: 11 additions & 1 deletion Entity/ContactSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,22 @@ public function setPublishDown($publishDown)
}

/**
* @param array $list
* @param $list
*
* @return $this
*/
public function setCampaignList($list)
{
$this->campaignList = $list;

return $this;
}

/**
* @return int
*/
public function getPermissionUser()
{
return $this->getCreatedBy();
}
}
126 changes: 75 additions & 51 deletions Model/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ class Api
/** @var Session */
protected $session;

/** @var bool */
protected $authenticated = false;

/**
* Api constructor.
*
Expand Down Expand Up @@ -264,10 +267,13 @@ public function setCampaignId($campaignId = null)
public function handleInputPublic()
{
try {
$this->parseToken();
$this->parseSourceId();
$this->parseSource();
$this->parseSourceCampaignSettings();
$this->validateToken();
$this->parseCampaignId();
$this->parseCampaign();
$this->parseSourceCampaignSettings();
} catch (\Exception $exception) {
$this->handleException($exception);
}
Expand Down Expand Up @@ -415,6 +421,67 @@ private function parseCampaign()
return $this;
}

/**
* Ensure the required parameters were provided and not empty while parsing.
*
* @throws ContactSourceException
*/
private function parseToken()
{
// There are many ways to send a simple token... Let's support them all to be friendly to our Sources.
$this->token = trim($this->request->get('token'));
if (!$this->token) {
$this->token = trim($this->request->headers->get('token'));
if (!$this->token) {
$this->token = trim($this->request->headers->get('X-Auth-Token'));
if (!$this->token) {
$bearer = $this->request->headers->get('authorization');
if ($bearer) {
$this->token = trim(str_ireplace('Bearer ', '', $bearer));
}
// Re-use the last token provided for this user for this source.
if (!$this->token && $this->sourceId) {
$tokens = $this->session->get('mautic.contactSource.tokens');
if ($tokens && isset($tokens[$this->sourceId])) {
$this->token = $tokens[$this->sourceId];
}
}
}
}
}
if (!$this->token) {
throw new ContactSourceException(
'The token was not supplied. Please provide your authentication token.',
Codes::HTTP_UNAUTHORIZED,
null,
Stat::TYPE_INVALID,
'token'
);
}
}

/**
* @throws ContactSourceException
*/
private function validateToken()
{
if ($this->token !== $this->contactSource->getToken()) {
throw new ContactSourceException(
'The token specified is invalid. Please request a new token.',
Codes::HTTP_UNAUTHORIZED,
null,
Stat::TYPE_INVALID,
'token'
);
}
if ($this->sourceId) {
$tokens = $this->session->get('mautic.contactSource.tokens', []);
$tokens[$this->sourceId] = $this->token;
$this->session->set('mautic.contactSource.tokens', $tokens);
}
$this->authenticated = true;
}

/**
* @param \Exception $exception
*/
Expand Down Expand Up @@ -509,10 +576,10 @@ private function handleInputPrivate()
{
$this->parseVerbosity();
$this->parseFieldsProvided();
$this->parseSourceId();
$this->parseCampaignId();
$this->parseToken();
$this->parseSourceId();
$this->parseSource();
$this->parseCampaignId();
$this->validateToken();
$this->parseSourceCampaignSettings();
$this->parseCampaign();
Expand Down Expand Up @@ -623,54 +690,6 @@ private function parseCampaignId()
}
}

/**
* Ensure the required parameters were provided and not empty while parsing.
*
* @throws ContactSourceException
*/
private function parseToken()
{
// There are many ways to send a simple token... Let's support them all to be friendly to our Sources.
$this->token = trim($this->request->get('token'));
if (!$this->token) {
$this->token = trim($this->request->headers->get('token'));
if (!$this->token) {
$this->token = trim($this->request->headers->get('X-Auth-Token'));
if (!$this->token) {
$bearer = $this->request->headers->get('authorization');
if ($bearer) {
$this->token = trim(str_ireplace('Bearer ', '', $bearer));
}
}
if (!$this->token) {
throw new ContactSourceException(
'The token was not supplied. Please provide your authentication token.',
Codes::HTTP_UNAUTHORIZED,
null,
Stat::TYPE_INVALID,
'token'
);
}
}
}
}

/**
* @throws ContactSourceException
*/
private function validateToken()
{
if ($this->token !== $this->contactSource->getToken()) {
throw new ContactSourceException(
'The token specified is invalid. Please request a new token.',
Codes::HTTP_UNAUTHORIZED,
null,
Stat::TYPE_INVALID,
'token'
);
}
}

/**
* Generate a new contact entity (not yet saved so that we can use it for validations).
*
Expand Down Expand Up @@ -1461,6 +1480,11 @@ public function getResult()
$result['attribution'] = $this->attribution;
}

// Authentication.
if ($this->verbose) {
$result['authenticated'] = $this->authenticated;
}

// Campaign.
if ($this->campaign) {
$result['campaign'] = [];
Expand Down
1 change: 1 addition & 0 deletions Translations/en_US/messages.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mautic.campaign.source.limit.status = "Status"
mautic.contactsource = "Sources"
mautic.contactsource.api.add_campaign.bad_request = "The Campaign and Source are already linked."
mautic.contactsource.api.add_campaign.not_found = "The Campaign ID provided does not exist."
mautic.contactsource.api.docs.auth_title = "Please provide your token to see full details."
mautic.contactsource.api.docs.assistance = "Assistance Email"
mautic.contactsource.api.docs.assistance.tooltip = "An email address to use for requesting assistance with source APIs. Leave blank to avoid offering assistance."
mautic.contactsource.api.docs.campaign_title = "API Documentation for %source% (campaign: %campaign%)"
Expand Down
Loading

0 comments on commit 0bbf4bb

Please sign in to comment.