URL: https://github.com/freeipa/freeipa/pull/1867
Author: tiran
 Title: #1867: Load certificate files as binary data
Action: opened

PR body:
"""
In Python 3, cryptography requires certificate data to be binary. Even
PEM encoded files are treated as binary content.

certmap-match and cert-find were loading certificates as text files. A
new BinaryFile type loads files as binary content.

Fixes: https://pagure.io/freeipa/issue/7520
Signed-off-by: Christian Heimes <chei...@redhat.com>
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/1867/head:pr1867
git checkout pr1867
From 505d4749cd15b32c3d1aa1bfe4020ab7d33ba40d Mon Sep 17 00:00:00 2001
From: Christian Heimes <chei...@redhat.com>
Date: Fri, 27 Apr 2018 12:29:17 +0200
Subject: [PATCH] Load certificate files as binary data

In Python 3, cryptography requires certificate data to be binary. Even
PEM encoded files are treated as binary content.

certmap-match and cert-find were loading certificates as text files. A
new BinaryFile type loads files as binary content.

Fixes: https://pagure.io/freeipa/issue/7520
Signed-off-by: Christian Heimes <chei...@redhat.com>
---
 ipaclient/plugins/cert.py         |  4 ++--
 ipaclient/plugins/certmap.py      |  4 ++--
 ipalib/cli.py                     | 19 ++++++++++++-------
 ipalib/parameters.py              | 16 ++++++++++++++--
 ipatests/test_cmdline/test_cli.py | 12 ++++++++++++
 5 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/ipaclient/plugins/cert.py b/ipaclient/plugins/cert.py
index a8d1e2127c..6d453d0d6e 100644
--- a/ipaclient/plugins/cert.py
+++ b/ipaclient/plugins/cert.py
@@ -27,7 +27,7 @@
 from ipalib import errors
 from ipalib import x509
 from ipalib import util
-from ipalib.parameters import File, Flag, Str
+from ipalib.parameters import BinaryFile, File, Flag, Str
 from ipalib.plugable import Registry
 from ipalib.text import _
 
@@ -196,7 +196,7 @@ class cert_remove_hold(MethodOverride):
 @register(override=True, no_fail=True)
 class cert_find(MethodOverride):
     takes_options = (
-        File(
+        BinaryFile(
             'file?',
             label=_("Input filename"),
             doc=_('File to load the certificate from.'),
diff --git a/ipaclient/plugins/certmap.py b/ipaclient/plugins/certmap.py
index 981ba292f6..31db991971 100644
--- a/ipaclient/plugins/certmap.py
+++ b/ipaclient/plugins/certmap.py
@@ -4,7 +4,7 @@
 
 from ipaclient.frontend import MethodOverride
 from ipalib import errors, x509
-from ipalib.parameters import File
+from ipalib.parameters import BinaryFile
 from ipalib.plugable import Registry
 from ipalib.text import _
 
@@ -14,7 +14,7 @@
 @register(override=True, no_fail=True)
 class certmap_match(MethodOverride):
     takes_args = (
-        File(
+        BinaryFile(
             'file?',
             label=_("Input file"),
             doc=_("File to load the certificate from"),
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 3fde581974..8d89c8f518 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -56,7 +56,7 @@
                            NoSuchNamespaceError, ValidationError, NotFound,
                            NotConfiguredError, PromptFailed)
 from ipalib.constants import CLI_TAB, LDAP_GENERALIZED_TIME_FORMAT
-from ipalib.parameters import File, Str, Enum, Any, Flag
+from ipalib.parameters import File, BinaryFile, Str, Enum, Any, Flag
 from ipalib.text import _
 from ipalib import api  # pylint: disable=unused-import
 from ipapython.dnsutil import DNSName
@@ -1333,7 +1333,7 @@ def load_files(self, cmd, kw):
         3) the webUI will use a different way of loading files
         """
         for p in cmd.params():
-            if isinstance(p, File):
+            if isinstance(p, (File, BinaryFile)):
                 # FIXME: this only reads the first file
                 raw = None
                 if p.name in kw:
@@ -1342,9 +1342,8 @@ def load_files(self, cmd, kw):
                     else:
                         fname = kw[p.name]
                     try:
-                        f = open(fname, 'r')
-                        raw = f.read()
-                        f.close()
+                        with open(fname, p.open_mode) as f:
+                            raw = f.read()
                     except IOError as e:
                         raise ValidationError(
                             name=to_cli(p.cli_name),
@@ -1352,14 +1351,20 @@ def load_files(self, cmd, kw):
                         )
                 elif p.stdin_if_missing:
                     try:
-                        raw = sys.stdin.read()
+                        if six.PY3 and p.type is bytes:
+                            raw = sys.stdin.buffer.read()
+                        else:
+                            raw = sys.stdin.read()
                     except IOError as e:
                         raise ValidationError(
                             name=to_cli(p.cli_name), error=e.args[1]
                         )
 
                 if raw:
-                    kw[p.name] = self.Backend.textui.decode(raw)
+                    if p.type is bytes:
+                        kw[p.name] = raw
+                    else:
+                        kw[p.name] = self.Backend.textui.decode(raw)
                 elif p.required:
                     raise ValidationError(
                         name=to_cli(p.cli_name), error=_('No file to read')
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index 902ae3401b..62d555129b 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -1753,17 +1753,29 @@ def _validate_scalar(self, value, index=None):
 
 
 class File(Str):
-    """
-    File parameter type.
+    """Text file parameter type.
 
     Accepts file names and loads their content into the parameter value.
     """
+    open_mode = 'r'
+    kwargs = Data.kwargs + (
+        # valid for CLI, other backends (e.g. webUI) can ignore this
+        ('stdin_if_missing', bool, False),
+        ('noextrawhitespace', bool, False),
+    )
+
+
+class BinaryFile(Bytes):
+    """Binary file parameter type
+    """
+    open_mode = 'rb'
     kwargs = Data.kwargs + (
         # valid for CLI, other backends (e.g. webUI) can ignore this
         ('stdin_if_missing', bool, False),
         ('noextrawhitespace', bool, False),
     )
 
+
 class DateTime(Param):
     """
     DateTime parameter type.
diff --git a/ipatests/test_cmdline/test_cli.py b/ipatests/test_cmdline/test_cli.py
index e1305b9beb..2b9d859d4e 100644
--- a/ipatests/test_cmdline/test_cli.py
+++ b/ipatests/test_cmdline/test_cli.py
@@ -3,12 +3,14 @@
 import shlex
 import subprocess
 import sys
+import tempfile
 import unittest
 
 import six
 from six import StringIO
 
 from ipatests import util
+from ipatests.test_ipalib.test_x509 import goodcert_headers
 from ipalib import api, errors
 import pytest
 
@@ -312,6 +314,16 @@ def test_without_options():
         if not adtrust_is_enabled:
             mockldap.del_entry(adtrust_dn)
 
+    def test_certfind(self):
+        with tempfile.NamedTemporaryFile() as f:
+            f.write(goodcert_headers)
+            f.flush()
+            self.check_command(
+                'cert_find --file={}'.format(f.name),
+                'cert_find',
+                file=goodcert_headers
+            )
+
 
 def test_cli_fsencoding():
     # https://pagure.io/freeipa/issue/5887
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org

Reply via email to