Skip to content

Commit

Permalink
CFM-166_REST_API_username_prefix_should_not_be_editable (#23)
Browse files Browse the repository at this point in the history
* REST API username prefix should be non-modifiable

* Remove api specific error messages.Remove rules for username validation.

Co-authored-by: Ioannis Igoumenos <ioigoume@admin.grnet.gr>
  • Loading branch information
Ioannis and Ioannis Igoumenos authored May 12, 2022
1 parent 68b0e7c commit 93bba04
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 74 deletions.
9 changes: 3 additions & 6 deletions app/resources/locales/en_US/error.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@
msgid "api.object"
msgstr "Did not find \"{0}\" object in request"

msgid "api.username.prefix"
msgstr "API username must begin with \"{0}\""

msgid "api.username.suffix"
msgstr "API username requires a suffix"

msgid "auth.api.expired"
msgstr "API User \"{0}\" has expired"

Expand Down Expand Up @@ -108,6 +102,9 @@ msgstr "Invalid character found"
msgid "input.invalid.email"
msgstr "The provided value is not a valid email address"

msgid "input.invalid.prefix"
msgstr "The provided value is not valid. \"{0}\" prefix is required"

msgid "input.invalid.url"
msgstr "The provided value is not a valid URL"

Expand Down
54 changes: 46 additions & 8 deletions app/src/Lib/Traits/ValidationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
namespace App\Lib\Traits;

use Cake\Core\Configure;
use Cake\Database\Schema\TableSchemaInterface;
use Cake\ORM\TableRegistry;
use Cake\Validation\Validator;

Expand Down Expand Up @@ -67,20 +68,34 @@ public function registerPrimaryKeyValidation(Validator $validator, array $primar
* Register validation rules for the provided field, as a string.
*
* @since COmanage Registry v5.0.0
* @param Validator $validator Cake Validator
* @param Schema $schema Cake Schema
* @param string $field Field name
* @param bool $required Whether this field is required
* @param Validator $validator Cake Validator
* @param TableSchemaInterface $schema Cake Schema
* @param string $field Field name
* @param bool $required Whether this field is required
* @return Validator Cake Validator
*/

public function registerStringValidation(Validator $validator, $schema, string $field, bool $required): Validator {
$validator->add($field, [
public function registerStringValidation(Validator $validator,
TableSchemaInterface $schema,
string $field,
bool $required,
string $prefix = ''): Validator {
$rules = [
'size' => ['rule' => ['validateMaxLength', ['column' => $schema->getColumn($field)]],
'provider' => 'table'],
'filter' => ['rule' => ['validateInput'],
'provider' => 'table']
]);
];

if(!empty($prefix)) {
$rules['prefix'] = [
'rule' => ['validatePrefix'],
'pass' => [$prefix],
'provider' => 'table'
];
}

$validator->add($field, $rules);

if($required) {
$validator->notEmptyString($field);
Expand Down Expand Up @@ -301,4 +316,27 @@ public function validateTimeZone($value, array $context) {

return true;
}
}

/**
* Determine if a string submitted from a form has a valid prefix.
*
* @since COmanage Registry v5.0.0
* @param string $value Value to validate
* @param string $prefix Prefix value
* @param array $context Validation context
* @return mixed True if $value validates, or an error string otherwise
*/

public function validatePrefix(string $value, string $prefix, array $context) {
$coid = $context['data']['co_id'] ?? '';
if($prefix === "co_id") {
$prefix = !empty($coid) ? "co_" . $coid . "." : "";
}

if (!preg_match('/^' . $prefix . '(?:.*)/m', $value)) {
return __d('error', 'input.invalid.prefix', [$prefix]);
}

return true;
}
}
83 changes: 27 additions & 56 deletions app/src/Model/Table/ApiUsersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@

namespace App\Model\Table;

use \Cake\Auth\FallbackPasswordHasher;
use \Cake\Chronos\Chronos;
use \Cake\ORM\Query;
use \Cake\ORM\RulesChecker;
use \Cake\ORM\Table;
use \Cake\ORM\TableRegistry;
use \Cake\Validation\Validator;
use \App\Lib\Enum\SuspendableStatusEnum;
use \App\Lib\Random\RandomString;
use ArrayObject;
use Cake\Auth\FallbackPasswordHasher;
use Cake\Chronos\Chronos;
use Cake\Event\EventInterface;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Validation\Validator;
use App\Lib\Enum\SuspendableStatusEnum;
use App\Lib\Random\RandomString;

class ApiUsersTable extends Table {
use \App\Lib\Traits\AutoViewVarsTrait;
Expand Down Expand Up @@ -94,6 +95,22 @@ public function initialize(array $config): void {
]
]);
}

/**
* Add namespace prefix to username
*
* @since COmanage Registry v5.0.0
* @param EventInterface $event beforeMarshal event
* @param ArrayObject $data Entity data
* @param ArrayObject $options Callback options
*/

public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
{
if (isset($data['username'])) {
$data['username'] = "co_" . $data['co_id'] . "." . $data['username'];
}
}

/**
* Define business rules to supplement the default trait implementation.
Expand All @@ -109,13 +126,6 @@ public function buildTableRules(RulesChecker $rules): RulesChecker {
// rule building.

$rules->add(function($entity, $options) use($rules) {
// AR-ApiUser-3 For namespacing purposes, API Users are named with a prefix consisting of the string "co_#.".
$ret = $this->ruleIsUsernameValid($entity, $options);

if($ret !== true) {
// Return the error message
return $ret;
}

// AR-ApiUser-3 API usernames must be unique across the entire platform.
$rule = $rules->isUnique(['username'], __d('error', 'exists', [__d('controller', 'ApiUsers', [1])]));
Expand Down Expand Up @@ -175,45 +185,6 @@ public function getUserPrivilege(string $username) {

return false;
}

/**
* Application Rule to determine if the current entity username is valid.
*
* @since COmanage Registry v5.0.0
* @param Entity $entity Entity to be validated
* @param array $options Application rule options
* @return boolean true if the Rule check passes, false otherwise
*/

public function ruleIsUsernameValid($entity, $options) {
// We need to pull the CO data to check the name

if(!$entity->co_id) {
return __d('error', 'coid');
}

$Cos = TableRegistry::getTableLocator()->get('Cos');

$co = $Cos->get($entity->co_id);

if(!$co) {
return __d('error', 'notfound', [__d('controller', 'cos', [1])]);
}

$prefix = "co_" . $co->id . ".";

// Return false if the prefix doesn't match the CO ID
if(strncmp($entity->username, $prefix, strlen($prefix))) {
return __d('error', 'api.username.prefix', [$prefix]);
}

// Or if there's nothing after the dot
if(strlen($entity->username) == strlen($prefix)) {
return __d('error', 'api.username.suffix');
}

return true;
}

/**
* Validate an API Key.
Expand Down Expand Up @@ -309,7 +280,7 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->notEmptyString('co_id');

$this->registerStringValidation($validator, $schema, 'username', true);
$this->registerStringValidation($validator, $schema, 'username', true, 'co_id');

$validator->add('api_key', [
'length' => ['rule' => ['validateMaxLength', ['column' => $schema->getColumn('api_key')]],
Expand Down
40 changes: 37 additions & 3 deletions app/src/View/Helper/FieldHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ public function banner(string $info) {
* @param string $fieldName Form field
* @param array $options FormHelper control options
* @param string $labelText Label text (fieldName language key used by default)
* @param array $config Custom FormHelper configuration options
* @return string HTML for control
*/

public function control(string $fieldName,
array $options=[],
string $labelText=null) {
string $labelText=null,
array $config=[]){
$coptions = $options;
$coptions['label'] = false;
$coptions['readonly'] = !$this->editable || (isset($options['readonly']) && $options['readonly']);
Expand All @@ -84,7 +86,16 @@ public function control(string $fieldName,

// Generate HTML for the control itself
$liClass = "";


// Remove prefix from field value
if(isset($config['prefix'], $this->getView()->get('vv_obj')->$fieldName)) {
$vv_obj = $this->getView()->get('vv_obj');
$fieldValue = $vv_obj->$fieldName;
$fieldValueTemp = str_replace($config['prefix'], '', $fieldValue);
$vv_obj->$fieldName = $fieldValueTemp;
$this->getView()->set('vv_obj', $vv_obj);
}

// Handle datetime controls specially
if($fieldName == 'valid_from' || $fieldName == 'valid_through') {
// Append the timezone to the label
Expand Down Expand Up @@ -198,7 +209,9 @@ public function control(string $fieldName,

return $this->startLine($liClass)
. $this->formNameDiv($fieldName, $labelText)
. $this->formInfoDiv($controlCode)
. ( !empty($config['prefix']) ?
$this->formInfoWithPrefixDiv($controlCode, $config['prefix']) :
$this->formInfoDiv($controlCode) )
. $this->endLine();
}

Expand Down Expand Up @@ -239,6 +252,27 @@ protected function formInfoDiv(string $content) {
' . $content . '
</div>';
}

/**
* Generate a form info (control, value) box with a non editable prefix.
*
* @since COmanage Registry v5.0.0
* @param string $content Content HTML
* @param string $prefix Prefix value
* @return string Form Info HTML
*/

protected function formInfoWithPrefixDiv(string $context, string $prefix) {
$div = '<div class="field-info">' . PHP_EOL
. '<div class="input-group mb-3">' . PHP_EOL
. '<div class="input-group-prepend">' . PHP_EOL
. '<span class="input-group-text" id="basic-addon3">' . $prefix . '</span>'
. '</div>' . PHP_EOL
. $context
. '</div></div>';

return $div;
}

/**
* Generate a form name (label, description) box.
Expand Down
2 changes: 1 addition & 1 deletion app/templates/ApiUsers/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ if($vv_action == 'add' || $vv_action == 'edit') {
}

// AR-ApiUser-3 For namespacing purposes, API Users are named with a prefix consisting of the string "co_#.".
print $this->Field->control('username', ['default' => "co_" . $vv_cur_co->id .'.']);
print $this->Field->control('username', [], null, ['prefix' => "co_" . $vv_cur_co->id .'.']);

// We link to the "Generate" button on edit only
$generateLink = [];
Expand Down
12 changes: 12 additions & 0 deletions app/webroot/css/co-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,18 @@ ul.fields li.fields-datepicker .field-info {
display: flex;
align-items: center;
}
.field-info .input-group {
flex-wrap: unset;
}
.field-info .input-group .input.text {
width: inherit;
}
.field-info .input-group .input-group-text{
border-radius: unset;
font-size: inherit;
line-height: inherit;
padding: .25rem .75rem;
}
.cm-time-picker-panel {
position: absolute;
z-index: 100;
Expand Down

0 comments on commit 93bba04

Please sign in to comment.