URL: https://github.com/freeipa/freeipa/pull/433 Author: LiptonB Title: #433: csrgen: Allow some certificate fields to be specified by the user Action: opened
PR body: """ These patches allow CSR generation rules to contain a "prompt," which will cause data to be requested from the user and interpolated into the CSR. The second commit runs the prompt through gettext. As I asked about [here](https://www.redhat.com/archives/freeipa-devel/2016-August/msg00823.html), I'm not sure if this is useful because the prompt strings in the rule files won't be recognized as translatable. But I decided to include the commit for discussion. """ 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 96f4e25a4770bd2076390301adcee53d55086fa2 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 --- install/share/csrgen/Makefile.am | 1 + .../share/csrgen/rules/dataEmailUserSpecified.json | 16 ++++++++++ ipaclient/csrgen.py | 36 ++++++++++++++++++++-- ipaclient/plugins/csrgen.py | 9 ++++-- ipatests/test_ipaclient/test_csrgen.py | 15 ++++----- 5 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 install/share/csrgen/rules/dataEmailUserSpecified.json diff --git a/install/share/csrgen/Makefile.am b/install/share/csrgen/Makefile.am index 12c62c4..ad4412e 100644 --- a/install/share/csrgen/Makefile.am +++ b/install/share/csrgen/Makefile.am @@ -10,6 +10,7 @@ ruledir = $(IPA_DATA_DIR)/csrgen/rules rule_DATA = \ rules/dataDNS.json \ rules/dataEmail.json \ + rules/dataEmailUserSpecified.json \ rules/dataHostCN.json \ rules/dataUsernameCN.json \ rules/dataSubjectBase.json \ diff --git a/install/share/csrgen/rules/dataEmailUserSpecified.json b/install/share/csrgen/rules/dataEmailUserSpecified.json new file mode 100644 index 0000000..3fb2fb1 --- /dev/null +++ b/install/share/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/csrgen.py b/ipaclient/csrgen.py index 96100ae..2c1c5fc 100644 --- a/ipaclient/csrgen.py +++ b/ipaclient/csrgen.py @@ -345,8 +345,9 @@ class CSRGenerator(object): def __init__(self, rule_provider): self.rule_provider = rule_provider - def csr_script(self, principal, config, profile_id, helper): - render_data = {'subject': principal, 'config': config} + def csr_script(self, principal, config, userdata, profile_id, helper): + render_data = { + 'subject': principal, 'config': config, 'userdata': userdata} formatter = self.FORMATTERS[helper]() rules = self.rule_provider.rules_for_profile(profile_id, helper) @@ -360,3 +361,34 @@ def csr_script(self, principal, config, profile_id, helper): 'Template error when formatting certificate data')) return script + + def get_user_prompts(self, profile_id, helper): + prompts = {} + syntax_rules = [] + rules = self.rule_provider.rules_for_profile(profile_id, helper) + + 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.CertificateMappingError(reason=_( + 'Certificate mapping rule %(rule)s has a prompt' + ' but no data_source set') % {'rule': rule.name}) + if var in prompts: + raise errors.CertificateMappingError(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.CertificateMappingError( + 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 diff --git a/ipaclient/plugins/csrgen.py b/ipaclient/plugins/csrgen.py index 0669a47..d480946 100644 --- a/ipaclient/plugins/csrgen.py +++ b/ipaclient/plugins/csrgen.py @@ -82,6 +82,9 @@ def execute(self, *args, **options): if not backend.isconnected(): backend.connect() + generator = CSRGenerator(FileRuleProvider()) + prompts = generator.get_user_prompts(profile_id, helper) + try: if principal.is_host: principal_obj = api.Command.host_show( @@ -98,10 +101,12 @@ def execute(self, *args, **options): principal_obj = principal_obj['result'] config = api.Command.config_show()['result'] - generator = CSRGenerator(FileRuleProvider()) + userdata = {} + for name, prompt in prompts.items(): + userdata[name] = self.Backend.textui.prompt(prompt) script = generator.csr_script( - principal_obj, config, profile_id, helper) + principal_obj, config, userdata, profile_id, helper) result = {} if 'out' in options: diff --git a/ipatests/test_ipaclient/test_csrgen.py b/ipatests/test_ipaclient/test_csrgen.py index 556f8e0..b056042 100644 --- a/ipatests/test_ipaclient/test_csrgen.py +++ b/ipatests/test_ipaclient/test_csrgen.py @@ -197,7 +197,8 @@ def test_userCert_OpenSSL(self, generator): ], } - script = generator.csr_script(principal, config, 'userCert', 'openssl') + script = generator.csr_script( + principal, config, {}, 'userCert', 'openssl') with open(os.path.join( CSR_DATA_DIR, 'scripts', 'userCert_openssl.sh')) as f: expected_script = f.read() @@ -215,7 +216,7 @@ def test_userCert_Certutil(self, generator): } script = generator.csr_script( - principal, config, 'userCert', 'certutil') + principal, config, {}, 'userCert', 'certutil') with open(os.path.join( CSR_DATA_DIR, 'scripts', 'userCert_certutil.sh')) as f: @@ -235,7 +236,7 @@ def test_caIPAserviceCert_OpenSSL(self, generator): } script = generator.csr_script( - principal, config, 'caIPAserviceCert', 'openssl') + principal, config, {}, 'caIPAserviceCert', 'openssl') with open(os.path.join( CSR_DATA_DIR, 'scripts', 'caIPAserviceCert_openssl.sh')) as f: expected_script = f.read() @@ -254,7 +255,7 @@ def test_caIPAserviceCert_Certutil(self, generator): } script = generator.csr_script( - principal, config, 'caIPAserviceCert', 'certutil') + principal, config, {}, 'caIPAserviceCert', 'certutil') with open(os.path.join( CSR_DATA_DIR, 'scripts', 'caIPAserviceCert_certutil.sh')) as f: expected_script = f.read() @@ -270,7 +271,7 @@ def test_optionalAttributeMissing(self, generator): generator = IdentityCSRGenerator(rule_provider) script = generator.csr_script( - principal, {}, 'example', 'identity') + principal, {}, {}, 'example', 'identity') assert script == '\n' def test_twoDataRulesOneMissing(self, generator): @@ -282,7 +283,7 @@ def test_twoDataRulesOneMissing(self, generator): 'data2', '{{subject.uid}}', {'data_source': 'subject.uid'})) generator = IdentityCSRGenerator(rule_provider) - script = generator.csr_script(principal, {}, 'example', 'identity') + script = generator.csr_script(principal, {}, {}, 'example', 'identity') assert script == ',testuser\n' def test_requiredAttributeMissing(self): @@ -295,4 +296,4 @@ def test_requiredAttributeMissing(self): with pytest.raises(errors.CSRTemplateError): _script = generator.csr_script( - principal, {}, 'example', 'identity') + principal, {}, {}, 'example', 'identity') From 23ef577f4c6d860d4ba4faeedb25621ba62ce261 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 2c1c5fc..3800c8d 100644 --- a/ipaclient/csrgen.py +++ b/ipaclient/csrgen.py @@ -389,6 +389,6 @@ def get_user_prompts(self, profile_id, helper): ' 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 a5f19d013e02762dbe6d4529b274fc08a37055f7 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 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ipatests/test_ipaclient/test_csrgen.py b/ipatests/test_ipaclient/test_csrgen.py index b056042..e3ebdbe 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') @@ -297,3 +298,27 @@ def test_requiredAttributeMissing(self): with pytest.raises(errors.CSRTemplateError): _script = generator.csr_script( principal, {}, {}, 'example', 'identity') + + def test_get_user_prompts(self): + rule_provider = StubRuleProvider() + rule_provider.data_rule.options = { + 'data_source': 'userdata.nickname', 'prompt': "Nickname"} + generator = IdentityCSRGenerator(rule_provider) + + prompts = generator.get_user_prompts('example', 'identity') + + 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 = IdentityCSRGenerator(rule_provider) + + script = generator.csr_script( + principal, {}, userdata, 'example', 'identity') + expected_script = 'nickname:mynick'
-- 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