From f2c2a63c1903a8d0dbceff62f3da35cd4262fbb8 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 5 Aug 2025 13:10:58 +0300 Subject: [PATCH 1/3] Enable People Picker for Self Service --- app/config/schema/schema.json | 1 + app/resources/locales/en_US/field.po | 6 +++ .../Component/RegistryAuthComponent.php | 44 +++++++++++++++++-- app/templates/EnrollmentFlows/fields.inc | 1 + 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index 86ea747fe..e8841843b 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -636,6 +636,7 @@ "authz_cou_id": { "type": "integer", "foreignkey": { "table": "cous", "column": "id" }}, "authz_group_id": { "type": "integer", "foreignkey": { "table": "groups", "column": "id" }}, "collect_enrollee_email": { "type": "boolean" }, + "enable_person_find": { "type": "boolean" }, "redirect_on_duplicate": { "type": "string", "size": 256 }, "redirect_on_finalize": { "type": "string", "size": 256 }, "finalization_message_template_id": { "type": "integer", "foreignkey": { "table": "message_templates", "column": "id" }}, diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index 56d7fb893..44d063edc 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -498,6 +498,12 @@ msgstr "Petitioner Authorization" msgid "EnrollmentFlows.collect_enrollee_email" msgstr "Collect Enrollee Email" +msgid "EnrollmentFlows.enable_person_find" +msgstr "Enable People Picker for Self Service" + +msgid "EnrollmentFlows.enable_person_find.desc" +msgstr "Enable people picker for self-service enrollments, see Registry Technical Manual for privacy considerations" + msgid "EnrollmentFlows.finalization_message_template_id" msgstr "Finalization Message Template" diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index 5df86abcc..861447400 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -325,7 +325,7 @@ protected function calculatePermission(string $action, ?int $id=null): bool { * Obtain the permission set for this request. * * @since COmanage Registry v5.0.0 - * @param int $id Subject ID, if applicable + * @param int|null $id Subject ID, if applicable * @return array Array of actions and authorized roles */ @@ -386,6 +386,19 @@ protected function calculatePermissions(?int $id=null): array { // Pull the table's permission definitions $permissions = $this->getTablePermissions($table, $id); + + // Calculate people picker permissions on the fly for an enrollment flow/petition + if( + $reqAction == 'pick' + && $modelsName == 'People' + && !empty($controller->getRequest()->getQuery('petition_id')) + ) { + // We need to check if this is part of an Enrollment Flow + $isPicker = $this->isAuthenticatedUserPicker((int)$controller->getRequest()->getQuery('petition_id')); + if($isPicker) { + $permissions['table']['pick'][] = 'authenticatedUser'; + } + } if($id) { $readOnlyActions = ['view']; @@ -719,8 +732,8 @@ public function getPersonID(int $coId): ?int { * Obtain the set of permissions as provided by the table. * * @since COmanage Registry v5.0.0 - * @param table $table Cake Table - * @param int $id Entity ID, if applicable + * @param table $table Cake Table + * @param int|null $id Entity ID, if applicable * @return array Table permissions */ @@ -852,12 +865,35 @@ public function isApprover(int $petitionId): bool { public function isAuthenticatedUser(): bool { return !empty($this->authenticatedUser); } + + /** + * Determine if the current authenticated user is allowed to use the person picker functionality + * for a given petition. + * + * @param int $petitionId ID of the petition to check + * @return bool True if the user can use the person picker, false otherwise + * @since COmanage Registry v5.2.0 + */ + + protected function isAuthenticatedUserPicker(int $petitionId): bool + { + if (empty($petitionId)) { + return false; + } + + $Petitions = TableRegistry::getTableLocator()->get('Petitions'); + + // Pull the Petition to find its CO + $petition = $Petitions->get($petitionId, ['contain' => 'EnrollmentFlows']); + + return $this->authenticatedUser && $petition->enrollment_flow->enable_person_find; + } /** * Determine if the current user is a CO Administrator. * * @since COmanage Registry v5.0.0 - * @param int $coId CO ID + * @param int|null $coId CO ID * @return bool True if the current user is a CO Administrator */ diff --git a/app/templates/EnrollmentFlows/fields.inc b/app/templates/EnrollmentFlows/fields.inc index 411633f7c..33d25752c 100644 --- a/app/templates/EnrollmentFlows/fields.inc +++ b/app/templates/EnrollmentFlows/fields.inc @@ -90,6 +90,7 @@ if($vv_action == 'add' || $vv_action == 'edit') { foreach (['authz_cou_id', 'authz_group_id', 'collect_enrollee_email', + 'enable_person_find', 'redirect_on_duplicate', 'redirect_on_finalize', 'finalization_message_template_id' From 3aa685ac78f1e907cbd6346c55c4dfd430b68c33 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 20 Aug 2025 12:54:18 +0300 Subject: [PATCH 2/3] Move enable people picker in Enrollment Flow step configuration --- app/resources/locales/en_US/field.po | 6 +- app/src/Controller/ApiV2Controller.php | 56 ++++++++++++++++++- .../Component/RegistryAuthComponent.php | 36 ------------ app/src/Controller/PeopleController.php | 1 + app/src/View/Helper/FieldHelper.php | 32 +++++++++++ app/src/View/Helper/PetitionHelper.php | 2 +- app/templates/CoSettings/fields.inc | 52 ++++++++--------- app/templates/EnrollmentFlowSteps/fields.inc | 16 ++++++ app/templates/EnrollmentFlows/fields.inc | 1 - 9 files changed, 132 insertions(+), 70 deletions(-) diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index 44d063edc..e9c99c678 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -456,6 +456,9 @@ msgstr "Limit Global Search Scope" msgid "CoSettings.search_global_limited_models.desc" msgstr "If true, Global Search will only search Names, Email Addresses, and Identifiers. This may result in faster searches for larger deployments." +msgid "EnrollmentFlowSteps.enable_person_find" +msgstr "Enable People Picker for Self Service" + msgid "EnrollmentFlowSteps.actor_type" msgstr "Actor Type" @@ -498,9 +501,6 @@ msgstr "Petitioner Authorization" msgid "EnrollmentFlows.collect_enrollee_email" msgstr "Collect Enrollee Email" -msgid "EnrollmentFlows.enable_person_find" -msgstr "Enable People Picker for Self Service" - msgid "EnrollmentFlows.enable_person_find.desc" msgstr "Enable people picker for self-service enrollments, see Registry Technical Manual for privacy considerations" diff --git a/app/src/Controller/ApiV2Controller.php b/app/src/Controller/ApiV2Controller.php index 435b156f7..d41eef857 100644 --- a/app/src/Controller/ApiV2Controller.php +++ b/app/src/Controller/ApiV2Controller.php @@ -29,14 +29,15 @@ namespace App\Controller; -use Cake\Controller\Controller; -use InvalidArgumentException; +use App\Lib\Enum\EnrollmentAuthzEnum; use Cake\Chronos\Chronos; +use Cake\Controller\Controller; +use Cake\Event\EventInterface; use Cake\Http\Exception\BadRequestException; use Cake\Log\Log; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; - +use InvalidArgumentException; use \App\Lib\Enum\ProvisioningContextEnum; use \App\Lib\Enum\SuspendableStatusEnum; @@ -410,4 +411,53 @@ public function view($id = null) { public function pick() { $this->dispatchIndex(mode: 'picker'); } + + /** + * Indicate whether this Controller will handle some or all authnz. + * + * @param EventInterface $event Cake event, ie: from beforeFilter + * @return string "no", "open", "authz", "yes", or "notauth" + * @since COmanage Registry v5.2.0 + */ + public function willHandleAuth(\Cake\Event\EventInterface $event): string + { + $request = $this->getRequest(); + $reqAction = $request->getParam('action'); + $session = $request->getSession(); + $mode = 'no'; + + $auth = $session->read('Auth'); + + // Calculate people picker permissions on the fly for an enrollment flow/petition + if( + $this->name == 'People' + && $reqAction == 'pick' + && !empty($request->getQuery('petition_id')) + ) { + $petitionId = (int)$request->getQuery('petition_id'); + // We need to check if this is part of an Enrollment Flow + $Petitions = TableRegistry::getTableLocator()->get('Petitions'); + + // Pull the Petition to find its CO + $petition = $Petitions->get($petitionId, ['contain' => ['EnrollmentFlows' => ['EnrollmentFlowSteps']]]); + + // We need to check the Petitioner Authorization. + $hasAuthorizedUser = false; + if ($petition->enrollment_flow->authz_type == EnrollmentAuthzEnum::AuthUser) { + $hasAuthorizedUser = !empty($auth['external']['user']); + } else { + // This will be the no authorization use case. + $hasAuthorizedUser = true; + } + + foreach ($petition->enrollment_flow->enrollment_flow_steps as $step) { + if ($step->plugin == 'CoreEnroller.AttributeCollectors') { + $mode = $hasAuthorizedUser && $step->enable_person_find ? 'yes' : 'no'; + } + } + } + + // Apply standard behavior + return $mode; + } } \ No newline at end of file diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index 861447400..f1098d43e 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -386,19 +386,6 @@ protected function calculatePermissions(?int $id=null): array { // Pull the table's permission definitions $permissions = $this->getTablePermissions($table, $id); - - // Calculate people picker permissions on the fly for an enrollment flow/petition - if( - $reqAction == 'pick' - && $modelsName == 'People' - && !empty($controller->getRequest()->getQuery('petition_id')) - ) { - // We need to check if this is part of an Enrollment Flow - $isPicker = $this->isAuthenticatedUserPicker((int)$controller->getRequest()->getQuery('petition_id')); - if($isPicker) { - $permissions['table']['pick'][] = 'authenticatedUser'; - } - } if($id) { $readOnlyActions = ['view']; @@ -865,29 +852,6 @@ public function isApprover(int $petitionId): bool { public function isAuthenticatedUser(): bool { return !empty($this->authenticatedUser); } - - /** - * Determine if the current authenticated user is allowed to use the person picker functionality - * for a given petition. - * - * @param int $petitionId ID of the petition to check - * @return bool True if the user can use the person picker, false otherwise - * @since COmanage Registry v5.2.0 - */ - - protected function isAuthenticatedUserPicker(int $petitionId): bool - { - if (empty($petitionId)) { - return false; - } - - $Petitions = TableRegistry::getTableLocator()->get('Petitions'); - - // Pull the Petition to find its CO - $petition = $Petitions->get($petitionId, ['contain' => 'EnrollmentFlows']); - - return $this->authenticatedUser && $petition->enrollment_flow->enable_person_find; - } /** * Determine if the current user is a CO Administrator. diff --git a/app/src/Controller/PeopleController.php b/app/src/Controller/PeopleController.php index 5d529a590..f88d7fbcb 100644 --- a/app/src/Controller/PeopleController.php +++ b/app/src/Controller/PeopleController.php @@ -30,6 +30,7 @@ namespace App\Controller; // XXX not doing anything with Log yet +use Cake\Event\EventInterface; use Cake\Log\Log; use Cake\ORM\TableRegistry; use http\QueryString; diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index 5e674a06c..12e3a508f 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -32,6 +32,8 @@ use App\Lib\Enum\DateTypeEnum; use App\Lib\Util\StringUtilities; use Cake\I18n\FrozenTime; +use Cake\ORM\Table; +use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; use Cake\View\Helper; use DOMDocument; @@ -548,6 +550,36 @@ public function getReqFields(): array return $this->reqFields; } + /** + * Get reference to the Table Object + * + * @param string $tableName + * + * @return Table + * @since COmanage Registry v5.2.0 + */ + public function getTable(string $tableName): Table + { + return TableRegistry::getTableLocator()->get($tableName); + } + + /** + * Get attribute collectors configuration for a specific enrollment flow step + * + * @param int $stepId The ID of the enrollment flow step + * @return array The attribute collectors configuration array containing enrollment attributes + * @since COmanage Registry v5.2.0 + */ + public function getAttributeCollectorsForStep(int $stepId): array + { + $AttributeCollectors = $this->getTable('CoreEnroller.AttributeCollectors'); + return $AttributeCollectors->find() + ->where(['enrollment_flow_step_id' => $stepId]) + ->contain(['EnrollmentAttributes']) + ->first() + ->toArray(); + } + /** * For the records that have a value like co_x.value, and we want to handle * the value separate from the field diff --git a/app/src/View/Helper/PetitionHelper.php b/app/src/View/Helper/PetitionHelper.php index be426a30d..c4ac48521 100644 --- a/app/src/View/Helper/PetitionHelper.php +++ b/app/src/View/Helper/PetitionHelper.php @@ -76,7 +76,7 @@ public function populateAutoViewVars(): void { // XXX Find the co id foreach ( - $this->enrollmentAttributesTable->calculateAutoViewVars($this->petition?->enrollment_flow?->co_id,$this->entity) as $vvar => $value + $this->enrollmentAttributesTable->calculateAutoViewVars($this->petition?->enrollment_flow?->co_id, $this->entity) as $vvar => $value ) { $this->getView()->set($vvar, $value); } diff --git a/app/templates/CoSettings/fields.inc b/app/templates/CoSettings/fields.inc index 4b51f4165..75231447b 100644 --- a/app/templates/CoSettings/fields.inc +++ b/app/templates/CoSettings/fields.inc @@ -96,32 +96,32 @@ if($vv_action == 'edit') { 'fieldName' => 'search_global_limited_models' ]]); - print $this->element('form/listItem', [ - 'arguments' => [ - 'fieldName' => 'person_picker_display_fields', - 'labelIsTextOnly' => true, - 'groupedControls' => [ - // each key is the fieldName of the control we are going to create - 'person_picker_email_address_type_id' => [ - 'fieldOptions' => [ - 'label' => __d('field', 'mail'), - 'empty' => '(' . __d('operation', 'all') . ')', -// 'all' => '(' . __d('operation', 'all') . ')' - ], - ], - 'person_picker_identifier_type_id' => [ - 'fieldOptions' => [ - 'label' => __d('field', 'identifier'), - 'empty' => '(' . __d('operation', 'all') . ')', -// 'all' => '(' . __d('operation', 'all') . ')', - ], - ], - 'person_picker_display_types' => [ - 'singleRowItem' => true, - 'fieldLabel' => __d('field', 'CoSettings.person_picker_display_types') - ] - ], - ]]); +// print $this->element('form/listItem', [ +// 'arguments' => [ +// 'fieldName' => 'person_picker_display_fields', +// 'labelIsTextOnly' => true, +// 'groupedControls' => [ +// // each key is the fieldName of the control we are going to create +// 'person_picker_email_address_type_id' => [ +// 'fieldOptions' => [ +// 'label' => __d('field', 'mail'), +// 'empty' => '(' . __d('operation', 'all') . ')', +//// 'all' => '(' . __d('operation', 'all') . ')' +// ], +// ], +// 'person_picker_identifier_type_id' => [ +// 'fieldOptions' => [ +// 'label' => __d('field', 'identifier'), +// 'empty' => '(' . __d('operation', 'all') . ')', +//// 'all' => '(' . __d('operation', 'all') . ')', +// ], +// ], +// 'person_picker_display_types' => [ +// 'singleRowItem' => true, +// 'fieldLabel' => __d('field', 'CoSettings.person_picker_display_types') +// ] +// ], +// ]]); print $this->element('form/listItem', [ 'arguments' => [ diff --git a/app/templates/EnrollmentFlowSteps/fields.inc b/app/templates/EnrollmentFlowSteps/fields.inc index ffe5137ec..8b831543b 100644 --- a/app/templates/EnrollmentFlowSteps/fields.inc +++ b/app/templates/EnrollmentFlowSteps/fields.inc @@ -69,6 +69,22 @@ if($vv_action == 'add' || $vv_action == 'edit') { ]]); } + // The people picker enable checkbox is only useful for the attribute collection plugin. + if ($vv_obj->plugin === 'CoreEnroller.AttributeCollectors') { + $attributeCollectorConfig = $this->Field->getAttributeCollectorsForStep( $vv_obj->id); + $sponsorAttribute = array_filter( + $attributeCollectorConfig["enrollment_attributes"], + fn($enrollmentAttribute) => $enrollmentAttribute['attribute'] == 'sponsor_person_id' + ); + print $this->element('form/listItem', [ + 'arguments' => [ + 'fieldName' => 'enable_person_find', + 'fieldOptions' => [ + 'readonly' => empty($sponsorAttribute) + ] + ]]); + } + print $this->element('form/listItem', [ 'arguments' => [ 'fieldName' => 'actor_type', diff --git a/app/templates/EnrollmentFlows/fields.inc b/app/templates/EnrollmentFlows/fields.inc index 33d25752c..411633f7c 100644 --- a/app/templates/EnrollmentFlows/fields.inc +++ b/app/templates/EnrollmentFlows/fields.inc @@ -90,7 +90,6 @@ if($vv_action == 'add' || $vv_action == 'edit') { foreach (['authz_cou_id', 'authz_group_id', 'collect_enrollee_email', - 'enable_person_find', 'redirect_on_duplicate', 'redirect_on_finalize', 'finalization_message_template_id' From 45963793d4e42c8a84eb2fbc90c66395ab43d13f Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 20 Aug 2025 16:52:37 +0300 Subject: [PATCH 3/3] Enrollment Flow Steps enable person find --- app/config/schema/schema.json | 2 +- app/resources/locales/en_US/field.po | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index e8841843b..4be5748c6 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -636,7 +636,6 @@ "authz_cou_id": { "type": "integer", "foreignkey": { "table": "cous", "column": "id" }}, "authz_group_id": { "type": "integer", "foreignkey": { "table": "groups", "column": "id" }}, "collect_enrollee_email": { "type": "boolean" }, - "enable_person_find": { "type": "boolean" }, "redirect_on_duplicate": { "type": "string", "size": 256 }, "redirect_on_finalize": { "type": "string", "size": 256 }, "finalization_message_template_id": { "type": "integer", "foreignkey": { "table": "message_templates", "column": "id" }}, @@ -662,6 +661,7 @@ "plugin": {}, "ordr": {}, "actor_type": { "type": "string", "size": 2 }, + "enable_person_find": { "type": "boolean" }, "approver_group_id": { "type": "integer", "foreignkey": { "table": "groups", "column": "id" }}, "message_template_id": {}, "redirect_on_handoff": { "type": "string", "size": 256 }, diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index e9c99c678..6107cfac1 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -459,6 +459,9 @@ msgstr "If true, Global Search will only search Names, Email Addresses, and Iden msgid "EnrollmentFlowSteps.enable_person_find" msgstr "Enable People Picker for Self Service" +msgid "EnrollmentFlowSteps.enable_person_find.desc" +msgstr "Enable people picker for self-service enrollments, see Registry Technical Manual for privacy considerations" + msgid "EnrollmentFlowSteps.actor_type" msgstr "Actor Type" @@ -501,9 +504,6 @@ msgstr "Petitioner Authorization" msgid "EnrollmentFlows.collect_enrollee_email" msgstr "Collect Enrollee Email" -msgid "EnrollmentFlows.enable_person_find.desc" -msgstr "Enable people picker for self-service enrollments, see Registry Technical Manual for privacy considerations" - msgid "EnrollmentFlows.finalization_message_template_id" msgstr "Finalization Message Template"