diff --git a/app/resources/locales/en_US/error.po b/app/resources/locales/en_US/error.po index edf321951..5d1a9cb65 100644 --- a/app/resources/locales/en_US/error.po +++ b/app/resources/locales/en_US/error.po @@ -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" @@ -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" diff --git a/app/src/Lib/Traits/ValidationTrait.php b/app/src/Lib/Traits/ValidationTrait.php index e31881c50..9b25b7f68 100644 --- a/app/src/Lib/Traits/ValidationTrait.php +++ b/app/src/Lib/Traits/ValidationTrait.php @@ -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; @@ -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); @@ -301,4 +316,27 @@ public function validateTimeZone($value, array $context) { return true; } -} \ No newline at end of file + + /** + * 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; + } +} diff --git a/app/src/Model/Table/ApiUsersTable.php b/app/src/Model/Table/ApiUsersTable.php index c1b97ce43..2b2eca51f 100644 --- a/app/src/Model/Table/ApiUsersTable.php +++ b/app/src/Model/Table/ApiUsersTable.php @@ -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; @@ -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. @@ -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])])); @@ -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. @@ -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')]], diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index bbf752b45..0da6e5aa5 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -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']); @@ -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 @@ -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(); } @@ -239,6 +252,27 @@ protected function formInfoDiv(string $content) { ' . $content . ' '; } + + /** + * 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 = '