URL: https://github.com/freeipa/freeipa/pull/433 Author: LiptonB Title: #433: csrgen: Allow some certificate fields to be specified by the user Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/433/head:pr433 git checkout pr433
From bf80c8e7c5ae1e67a497b06819ef56bfedc4339a Mon Sep 17 00:00:00 2001 From: Ben Lipton <blip...@redhat.com> Date: Thu, 28 Jul 2016 16:21:44 -0400 Subject: [PATCH 1/3] csrgen: Implement fields that prompt user for data Allows some data to be user-specified rather than coming out of the database. The provided data can be formatted with jinja2 rules just as database values can. https://fedorahosted.org/freeipa/ticket/4899 --- ipaclient/csrgen.py | 35 ++++++++++++++++++++-- ipaclient/csrgen/rules/dataEmailUserSpecified.json | 16 ++++++++++ ipaclient/plugins/csrgen.py | 10 +++++-- ipatests/test_ipaclient/test_csrgen.py | 11 +++---- 4 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 ipaclient/csrgen/rules/dataEmailUserSpecified.json diff --git a/ipaclient/csrgen.py b/ipaclient/csrgen.py index 0f52a8b..67442f6 100644 --- a/ipaclient/csrgen.py +++ b/ipaclient/csrgen.py @@ -372,8 +372,9 @@ def __init__(self, rule_provider, formatter_class=OpenSSLFormatter): self.rule_provider = rule_provider self.formatter = formatter_class() - def csr_config(self, principal, config, profile_id): - render_data = {'subject': principal, 'config': config} + def csr_config(self, principal, config, userdata, profile_id): + render_data = { + 'subject': principal, 'config': config, 'userdata': userdata} rules = self.rule_provider.rules_for_profile(profile_id) template = self.formatter.build_template(rules) @@ -387,6 +388,36 @@ def csr_config(self, principal, config, profile_id): return config + def get_user_prompts(self, profile_id): + prompts = {} + rules = self.rule_provider.rules_for_profile(profile_id) + + for field_mapping in rules: + for rule in field_mapping.data_rules: + if 'prompt' in rule.options: + try: + var = rule.options['data_source'] + except KeyError: + raise errors.CSRTemplateError(reason=_( + 'Certificate mapping rule %(rule)s has a prompt' + ' but no data_source set') % {'rule': rule.name}) + if var in prompts: + raise errors.CSRTemplateError(reason=_( + 'More than one data rule in this profile prompts' + ' for the %(item)s data item') % {'item': var}) + var_parts = var.split('.') + if len(var_parts) != 2 or var_parts[0] != 'userdata': + raise errors.CSRTemplateError( + reason=_( + 'Format of variable name in rule %(rule)s is' + ' incorrect. Rules that prompt for data must' + ' use a variable "userdata.<var_name>"') % + {'rule': rule.name}) + + prompts[var_parts[1]] = rule.options['prompt'] + + return prompts + class CSRLibraryAdaptor(object): def get_subject_public_key_info(self): diff --git a/ipaclient/csrgen/rules/dataEmailUserSpecified.json b/ipaclient/csrgen/rules/dataEmailUserSpecified.json new file mode 100644 index 0000000..3fb2fb1 --- /dev/null +++ b/ipaclient/csrgen/rules/dataEmailUserSpecified.json @@ -0,0 +1,16 @@ +{ + "rules": [ + { + "helper": "openssl", + "template": "email = {{userdata.email}}" + }, + { + "helper": "certutil", + "template": "email:{{userdata.email|quote}}" + } + ], + "options": { + "data_source": "userdata.email", + "prompt": "Email address" + } +} diff --git a/ipaclient/plugins/csrgen.py b/ipaclient/plugins/csrgen.py index 568a79f..6503f57 100644 --- a/ipaclient/plugins/csrgen.py +++ b/ipaclient/plugins/csrgen.py @@ -90,6 +90,9 @@ def execute(self, *args, **options): if not backend.isconnected(): backend.connect() + generator = csrgen.CSRGenerator(csrgen.FileRuleProvider()) + prompts = generator.get_user_prompts(profile_id, helper) + try: if principal.is_host: principal_obj = api.Command.host_show( @@ -106,9 +109,12 @@ def execute(self, *args, **options): principal_obj = principal_obj['result'] config = api.Command.config_show()['result'] - generator = csrgen.CSRGenerator(csrgen.FileRuleProvider()) + userdata = {} + for name, prompt in prompts.items(): + userdata[name] = self.Backend.textui.prompt(prompt) - csr_config = generator.csr_config(principal_obj, config, profile_id) + csr_config = generator.csr_config( + principal_obj, config, userdata, profile_id) request_info = base64.b64encode(csrgen_ffi.build_requestinfo( csr_config.encode('utf8'), public_key_info)) diff --git a/ipatests/test_ipaclient/test_csrgen.py b/ipatests/test_ipaclient/test_csrgen.py index d0798f8..85bd75c 100644 --- a/ipatests/test_ipaclient/test_csrgen.py +++ b/ipatests/test_ipaclient/test_csrgen.py @@ -176,7 +176,8 @@ def test_userCert_OpenSSL(self, generator): ], } - script = generator.csr_config(principal, config, 'userCert') + script = generator.csr_config( + principal, config, {}, 'userCert') with open(os.path.join( CSR_DATA_DIR, 'configs', 'userCert.conf')) as f: expected_script = f.read() @@ -195,7 +196,7 @@ def test_caIPAserviceCert_OpenSSL(self, generator): } script = generator.csr_config( - principal, config, 'caIPAserviceCert') + principal, config, {}, 'caIPAserviceCert') with open(os.path.join( CSR_DATA_DIR, 'configs', 'caIPAserviceCert.conf')) as f: expected_script = f.read() @@ -212,7 +213,7 @@ def test_optionalAttributeMissing(self, generator): rule_provider, formatter_class=IdentityFormatter) script = generator.csr_config( - principal, {}, 'example') + principal, {}, {}, 'example') assert script == '\n' def test_twoDataRulesOneMissing(self, generator): @@ -225,7 +226,7 @@ def test_twoDataRulesOneMissing(self, generator): generator = csrgen.CSRGenerator( rule_provider, formatter_class=IdentityFormatter) - script = generator.csr_config(principal, {}, 'example') + script = generator.csr_config(principal, {}, {}, 'example') assert script == ',testuser\n' def test_requiredAttributeMissing(self): @@ -239,4 +240,4 @@ def test_requiredAttributeMissing(self): with pytest.raises(errors.CSRTemplateError): _script = generator.csr_config( - principal, {}, 'example') + principal, {}, {}, 'example') From e8be6c83740b8b182b070890d36b43cf85912e75 Mon Sep 17 00:00:00 2001 From: Ben Lipton <blip...@redhat.com> Date: Mon, 29 Aug 2016 12:36:17 -0400 Subject: [PATCH 2/3] csrgen: Run user prompts through gettext before displaying Currently doesn't change anything because the strings are not translated. Need to find a way to include them in the translation files. https://fedorahosted.org/freeipa/ticket/4899 --- ipaclient/csrgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipaclient/csrgen.py b/ipaclient/csrgen.py index 67442f6..fc2ada3 100644 --- a/ipaclient/csrgen.py +++ b/ipaclient/csrgen.py @@ -414,7 +414,7 @@ def get_user_prompts(self, profile_id): ' use a variable "userdata.<var_name>"') % {'rule': rule.name}) - prompts[var_parts[1]] = rule.options['prompt'] + prompts[var_parts[1]] = _(rule.options['prompt']) return prompts From d8ee8b09b50a0c70282137ab49234823f3c5352b Mon Sep 17 00:00:00 2001 From: Ben Lipton <blip...@redhat.com> Date: Wed, 14 Sep 2016 13:16:51 -0400 Subject: [PATCH 3/3] tests: Add tests for handling of user-specified data https://fedorahosted.org/freeipa/ticket/4899 --- ipatests/test_ipaclient/test_csrgen.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ipatests/test_ipaclient/test_csrgen.py b/ipatests/test_ipaclient/test_csrgen.py index 85bd75c..c260795 100644 --- a/ipatests/test_ipaclient/test_csrgen.py +++ b/ipatests/test_ipaclient/test_csrgen.py @@ -7,6 +7,7 @@ from ipaclient import csrgen from ipalib import errors +from ipalib.text import _ BASE_DIR = os.path.dirname(__file__) CSR_DATA_DIR = os.path.join(BASE_DIR, 'data', 'test_csrgen') @@ -241,3 +242,30 @@ def test_requiredAttributeMissing(self): with pytest.raises(errors.CSRTemplateError): _script = generator.csr_config( principal, {}, {}, 'example') + + def test_get_user_prompts(self): + rule_provider = StubRuleProvider() + rule_provider.data_rule.options = { + 'data_source': 'userdata.nickname', 'prompt': "Nickname"} + generator = csrgen.CSRGenerator( + rule_provider, formatter_class=IdentityFormatter) + + prompts = generator.get_user_prompts('example') + + expected_prompts = {'nickname': _('Nickname')} + assert prompts == expected_prompts + + def test_userdata_included(self): + principal = {'uid': 'testuser'} + userdata = {'nickname': 'mynick'} + rule_provider = StubRuleProvider() + rule_provider.data_rule.template = 'nickname:{{userdata.nickname}}' + rule_provider.data_rule.options = { + 'data_source': 'userdata.nickname', 'prompt': "Nickname"} + generator = csrgen.CSRGenerator( + rule_provider, formatter_class=IdentityFormatter) + + script = generator.csr_config( + principal, {}, userdata, 'example') + expected_script = 'nickname:mynick\n' + assert script == expected_script
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code