From 58a94be4285f4206f3fa25d77514e6fa34edcecc Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 12 Feb 2018 09:31:52 -0500 Subject: [PATCH] Omit placeholder vars with survey password defaults WFJT nodes & schedules (launch configs) will accept POST/PATCH/PUT with variables in extra_data that have $encrypted$ for their value if a valid survey default exists. In this case, the variable is simply removed from the extra_data. This is done so that it does not affect pre-existing value substitution for $encrypted$ values from the config itself --- awx/api/serializers.py | 21 +++++++++++++++---- .../serializers/test_workflow_serializers.py | 18 ++++++++++++++++ docs/prompting.md | 3 +++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 71bafdb518..39b4255bde 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3150,19 +3150,27 @@ class LaunchConfigurationBaseSerializer(BaseSerializer): elif self.instance: ujt = self.instance.unified_job_template - # Insert survey_passwords to track redacted variables + # Replace $encrypted$ submissions with db value if exists + # build additional field survey_passwords to track redacted variables if 'extra_data' in attrs: extra_data = parse_yaml_or_json(attrs.get('extra_data', {})) if hasattr(ujt, 'survey_password_variables'): + # Prepare additional field survey_passwords for save password_dict = {} for key in ujt.survey_password_variables(): if key in extra_data: password_dict[key] = REPLACE_STR if not self.instance or password_dict != self.instance.survey_passwords: - attrs['survey_passwords'] = password_dict + attrs['survey_passwords'] = password_dict.copy() + # Force dict type (cannot preserve YAML formatting if passwords are involved) if not isinstance(attrs['extra_data'], dict): attrs['extra_data'] = parse_yaml_or_json(attrs['extra_data']) + # Encrypt the extra_data for save, only current password vars in JT survey encrypt_dict(attrs['extra_data'], password_dict.keys()) + # For any raw $encrypted$ string, either + # - replace with existing DB value + # - raise a validation error + # - remove key from extra_data if survey default is present if self.instance: db_extra_data = parse_yaml_or_json(self.instance.extra_data) else: @@ -3170,8 +3178,13 @@ class LaunchConfigurationBaseSerializer(BaseSerializer): for key in password_dict.keys(): if attrs['extra_data'].get(key, None) == REPLACE_STR: if key not in db_extra_data: - raise serializers.ValidationError( - _('Provided variable {} has no database value to replace with.').format(key)) + element = ujt.pivot_spec(ujt.survey_spec)[key] + if 'default' in element and element['default']: + attrs['survey_passwords'].pop(key, None) + attrs['extra_data'].pop(key, None) + else: + raise serializers.ValidationError( + {"extra_data": _('Provided variable {} has no database value to replace with.').format(key)}) else: attrs['extra_data'][key] = db_extra_data[key] diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py index 49fb2da4e7..4753e343cb 100644 --- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py @@ -215,6 +215,24 @@ class TestWorkflowJobTemplateNodeSerializerSurveyPasswords(): assert 'var1' in attrs['survey_passwords'] assert attrs['extra_data']['var1'] == '$encrypted$foooooo' + def test_accept_password_default(self, jt, mocker): + ''' + If user provides "$encrypted$" without a corresponding DB value for the + node, but survey question has a default, then variables are accepted + with that particular var omitted so on launch time the default takes effect + ''' + serializer = WorkflowJobTemplateNodeSerializer() + wfjt = WorkflowJobTemplate(name='fake-wfjt') + jt.survey_spec['spec'][0]['default'] = '$encrypted$bar' + attrs = serializer.validate({ + 'unified_job_template': jt, + 'workflow_job_template': wfjt, + 'extra_data': {'var1': '$encrypted$'} + }) + assert 'survey_passwords' in attrs + assert attrs['survey_passwords'] == {} + assert attrs['extra_data'] == {} + @mock.patch('awx.api.serializers.WorkflowJobTemplateNodeSerializer.get_related', lambda x,y: {}) class TestWorkflowJobNodeSerializerGetRelated(): diff --git a/docs/prompting.md b/docs/prompting.md index 24d83ec876..487fd992f9 100644 --- a/docs/prompting.md +++ b/docs/prompting.md @@ -240,6 +240,9 @@ of what happened. - survey password durability - schedule has survey password answers from WFJT survey - WFJT node has answers to different password questions from JT survey + - Saving with "$encrypted$" value will either + - become a no-op, removing the key if a valid question default exists + - replace with the database value if question was previously answered - final job it spawns has both answers encrypted - POST to associate credential to WFJT node - requires admin to WFJT and execute to JT