Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API changes to allow for public user. #1890

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions classes/Rest/Controllers/BaseControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Rest\Controllers;

use Rest\Exceptions\BadTokenException;
use Rest\Exceptions\EmptyTokenException;
use DateTime;
use Models\Services\Tokens;
use Rest\Utilities\Authentication;
Expand Down Expand Up @@ -221,6 +223,7 @@ public function authorize(Request $request, array $requirements = array())
throw new UnauthorizedHttpException('xdmod', self::EXCEPTION_MESSAGE);
}


$authorized = $user->hasAcls($requirements);
if ($authorized === false && !$isPublicUser) {
throw new AccessDeniedHttpException(self::EXCEPTION_MESSAGE);
Expand Down Expand Up @@ -760,7 +763,8 @@ protected function getTimestamp($date, $paramName = 'date', $format = 'Y-m-d')
*
* @param Request $request
* @return \XDUser
* @throws BadRequestHttpException if the provided token is empty, or there is not a provided token.
* @throws EmptyTokenException if the provided token is empty, or there is not a provided token.
* @throws BadTokenException if the provided token is in an invalid format.
* @throws \Exception if the user's token from the db does not validate against the provided token.
*/
protected function authenticateToken($request)
Expand All @@ -787,20 +791,21 @@ protected function authenticateToken($request)

// If it's still empty, then no token == no access.
if (empty($rawToken)) {
throw new UnauthorizedHttpException(
throw new EmptyTokenException(
Tokens::HEADER_KEY,
'No Token Provided.'
);
}

}

// We expect the token to be in the form /^(\d+).(.*)$/ so just make sure it at least has the required delimiter.
$delimPosition = strpos($rawToken, Tokens::DELIMITER);
if ($delimPosition === false) {
throw new UnauthorizedHttpException(
throw new BadTokenException(
Tokens::HEADER_KEY,
'Invalid token format.'
);

}

$userId = substr($rawToken, 0, $delimPosition);
Expand Down
146 changes: 146 additions & 0 deletions classes/Rest/Controllers/WarehouseControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use CCR\DB;
use CCR\Log;
use Configuration\XdmodConfiguration;
use DataWarehouse;
use DataWarehouse\Data\BatchDataset;
use DataWarehouse\Export\RealmManager;
use DataWarehouse\Query\Exceptions\AccessDeniedException;
Expand All @@ -26,6 +27,9 @@
use DataWarehouse\Access\Usage;
use stdClass;

use Rest\Exceptions\BadTokenException;
use Rest\Exceptions\EmptyTokenException;

/**
* @SuppressWarnings(PHPMD.StaticAccess)
**/
Expand Down Expand Up @@ -336,6 +340,9 @@ public function setupRoutes(Application $app, ControllerCollection $controller)

$controller
->get("$root/raw-data", "$current::getRawData");

$controller
->get("$root/search/dw_descripter", "$current::getDwDescipter");
nsafwan marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -2494,4 +2501,143 @@ private function validateRawDataFilterParam(
$filterValuesArray = explode(',', $filterValuesStr);
return $filterValuesArray;
}


public function getDwDescipter(Request $request, Application $app){
nsafwan marked this conversation as resolved.
Show resolved Hide resolved
$user = null;
try {
$user = $this->authenticateToken($request);
} catch (EmptyTokenException $e) {
$user = $this->getUserFromRequest($request);
} catch (Exception $e) {
throw new BadTokenException('xdmod', "An error was encountered while attempting to process the requested authorization procedure.");
}

$roles = $user->getAllRoles(true);

$roleDescriptors = array();
foreach ($roles as $activeRole) {
$shortRole = $activeRole;
$us_pos = strpos($shortRole, '_');
if ($us_pos > 0){
$shortRole = substr($shortRole, 0, $us_pos);
}

if (array_key_exists($shortRole, $roleDescriptors)) {
continue;
}

$realms = array();
$groupByObjects = array();
$realmObjects = Realms::getRealmObjectsForUser($user);
$query_descripter_realms = Acls::getQueryDescripters($user);

foreach($query_descripter_realms as $query_descripter_realm => $query_descripter_groups){

$category = DataWarehouse::getCategoryForRealm($query_descripter_realm);
if ($category === null) {
continue;
}

$seenstats = array();

$realmObject = $realmObjects[$query_descripter_realm];
$realmDisplay = $realmObject->getDisplay();
$realms[$query_descripter_realm] = array(
'text' => $query_descripter_realm,
'category' => $realmDisplay,
'dimensions' => array(),
'metrics' => array(),
);

foreach($query_descripter_groups as $query_descripter_group) {
foreach ($query_descripter_group as $query_descripter) {
if ($query_descripter->getDisableMenu()) {
continue;
}

$groupByName = $query_descripter->getGroupByName();
$group_by_object = $query_descripter->getGroupByInstance();
$permittedStatistics = $group_by_object->getRealm()->getStatisticIds();

$groupByObjects[$query_descripter_realm . '_' . $groupByName] = array(
'object' => $group_by_object,
'permittedStats' => $permittedStatistics);
$realms[$query_descripter_realm]['dimensions'][$groupByName] = array(
'text' => $groupByName == 'none' ? 'None' : $group_by_object->getName(),
'info' => $group_by_object->getHtmlDescription()
);

$stats = array_diff($permittedStatistics, $seenstats);
if (empty($stats)) {
continue;
}

$statsObjects = $query_descripter->getStatisticsClasses($stats);
foreach ($statsObjects as $realm_group_by_statistic => $statistic_object) {

if ( ! $statistic_object->showInMetricCatalog() ) {
continue;
}

$semStatId = \Realm\Realm::getStandardErrorStatisticFromStatistic(
$realm_group_by_statistic
);
$realms[$query_descripter_realm]['metrics'][$realm_group_by_statistic] =
array(
'text' => $statistic_object->getName(),
'info' => $statistic_object->getHtmlDescription(),
'std_err' => in_array($semStatId, $permittedStatistics),
'hidden_groupbys' => $statistic_object->getHiddenGroupBys()
);
$seenstats[] = $realm_group_by_statistic;
}
}
}

$texts = array();
foreach($realms[$query_descripter_realm]['metrics'] as $key => $row)
{
$texts[$key] = $row['text'];
}
array_multisort($texts, SORT_ASC, $realms[$query_descripter_realm]['metrics']);
}
$texts = array();
foreach($realms as $key => $row)
{
$texts[$key] = $row['text'];
}
array_multisort($texts, SORT_ASC, $realms);

$roleDescriptors[$shortRole] = array('totalCount'=> 1, 'data' => array(array( 'realms' => $realms)));
}

$combinedRealmDescriptors = array();
foreach ($roleDescriptors as $roleDescriptor) {
foreach ($roleDescriptor['data'][0]['realms'] as $realm => $realmDescriptor) {
if (!isset($combinedRealmDescriptors[$realm])) {
$combinedRealmDescriptors[$realm] = array(
'metrics' => array(),
'dimensions' => array(),
'text' => $realmDescriptor['text'],
'category' => $realmDescriptor['category'],
);
}

$combinedRealmDescriptors[$realm]['metrics'] += $realmDescriptor['metrics'];
$combinedRealmDescriptors[$realm]['dimensions'] += $realmDescriptor['dimensions'];
}
}

return $app->json(
[ 'success' => true,
'totalCount' => 1,
'data' => array(
array(
'realms' => $combinedRealmDescriptors,
),
),
]
);
}
}
11 changes: 5 additions & 6 deletions classes/Rest/Controllers/WarehouseExportControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use CCR\DB;
use CCR\Log;
use Rest\Exceptions\BadTokenException;
use Rest\Exceptions\EmptyTokenException;
use DataWarehouse\Data\RawStatisticsConfiguration;
use DataWarehouse\Export\FileManager;
use DataWarehouse\Export\QueryHandler;
Expand Down Expand Up @@ -97,15 +99,12 @@ public function getRealms(Request $request, Application $app)
// to the normal session authentication if a token is not provided.
try {
$user = $this->authenticateToken($request);
} catch (EmptyTokenException $e) {
$user = $this->getUserFromRequest($request);
} catch (Exception $e) {
// NOOP
throw new BadTokenException('xdmod', "An error was encountered while attempting to process the requested authorization procedure.");
}

if ($user === null) {
$user = $this->authorize($request);
}


$config = RawStatisticsConfiguration::factory();

$realms = array_map(
Expand Down
23 changes: 23 additions & 0 deletions classes/Rest/Exceptions/BadTokenException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rest\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;

class BadTokenException extends HttpException
{
/**
* Constructor.
*
* @param string $challenge WWW-Authenticate challenge string
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('WWW-Authenticate' => $challenge);

parent::__construct(401, $message, $previous, $headers, $code);
}
}
23 changes: 23 additions & 0 deletions classes/Rest/Exceptions/EmptyTokenException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rest\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;

class EmptyTokenException extends HttpException
{
/**
* Constructor.
*
* @param string $challenge WWW-Authenticate challenge string
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('WWW-Authenticate' => $challenge);

parent::__construct(401, $message, $previous, $headers, $code);
}
}
58 changes: 58 additions & 0 deletions tests/integration/lib/Rest/WarehouseControllerProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -868,4 +868,62 @@ public function provideGetRawData()
}
return $tests;
}


/**
* @dataProvider provideGetDwDescriptor
*/

public function testGetDwDescriptor($role, $tokenType)
{
parent::runTokenAuthTest(
$role,
$tokenType,
[
'path' => 'rest/warehouse/search/dw_descripter',
'method' => 'get',
'params' => null,
'data' => null,
'endpoint_type' => 'rest',
'authentication_type' => 'token_optional',
'wantPublicUser' => true
],
parent::validateSuccessResponse(function ($body, $assertMessage) {
$this->assertSame(1, $body['totalCount'], $assertMessage);
foreach (['Jobs', 'Cloud', 'ResourceSpecifications', 'Storage'] as $realmName) {
$realm = $body['data'][0]['realms'][$realmName];
foreach (['metrics', 'dimensions'] as $key) {
$this->assertArrayHasKey(
$key,
$realm,
$assertMessage . ": {$key} should be present in {$realmName}"
);
foreach ($realm[$key] as $elementName => $element) {
foreach (['text', 'info'] as $string) {
$this->assertIsString(
$element[$string],
$assertMessage . ": {$string} of {$elementName} in {$key} should be a string"
);
$this->assertNotEmpty(
$element[$string],
$assertMessage . ": {$string} of {$elementName} in {$key} should not be empty"
);
}
}
}
}
})
);
}

public function provideGetDwDescriptor() {
return [
['pub', 'empty_token'],
['pub', 'malformed_token'],
['usr', 'invalid_token'],
['usr', 'expired_token'],
['usr', 'revoked_token'],
['usr', 'valid_token']
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ public function testGetRealmsTokenAuth($role, $tokenType) {
'params' => null,
'data' => null,
'endpoint_type' => 'rest',
'authentication_type' => 'token_optional'
'authentication_type' => 'token_optional',
'wantPublicUser' => true
nsafwan marked this conversation as resolved.
Show resolved Hide resolved
],
parent::validateSuccessResponse(function ($body, $assertMessage) {
$this->assertSame(3, $body['total'], $assertMessage);
Expand Down
9 changes: 8 additions & 1 deletion tests/integration/lib/TokenAuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,14 @@ public function runTokenAuthTest(
)
];
} elseif ('rest' === $input['endpoint_type']) {
$output = parent::validateAuthorizationErrorResponse(401);
// If token is empty and we want Public user, test that it returns a success response.
nsafwan marked this conversation as resolved.
Show resolved Hide resolved
if (true === $input['wantPublicUser'] && 'empty_token' === $tokenType) {
$output = $this->validateSuccessResponse(function ($body, $assertMessage) {
parent::assertSame(true, $body['success'], $assertMessage);
});
} else {
$output = parent::validateAuthorizationErrorResponse(401);
}
} else {
throw new Exception(
"Unknown value for endpoint_type:"
Expand Down