1From 3540ddcc7448dc784b65c74424c8a25132cb8534 Mon Sep 17 00:00:00 2001
2From: Hongxu Jia <hongxu.jia@windriver.com>
3Date: Tue, 31 Jul 2018 17:24:47 +0800
4Subject: [PATCH] support authentication for kickstart
5
6While download kickstart file from web server,
7we support basic/digest authentication.
8
9Add KickstartAuthError to report authentication failure,
10which the invoker could parse this specific error.
11
12Upstream-Status: inappropriate [oe specific]
13
14Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
15
16---
17 pykickstart/errors.py | 17 +++++++++++++++++
18 pykickstart/load.py   | 34 ++++++++++++++++++++++++++++------
19 pykickstart/parser.py |  4 ++--
20 3 files changed, 47 insertions(+), 8 deletions(-)
21
22diff --git a/pykickstart/errors.py b/pykickstart/errors.py
23index 8294f59a..3d20bf82 100644
24--- a/pykickstart/errors.py
25+++ b/pykickstart/errors.py
26@@ -32,6 +32,9 @@ This module exports several exception classes:
27     KickstartVersionError - An exception for errors relating to unsupported
28                             syntax versions.
29
30+    KickstartAuthError - An exception for errors relating to authentication
31+                         failed while downloading kickstart from web server
32+
33 And some warning classes:
34
35     KickstartWarning - A generic warning class.
36@@ -125,3 +128,17 @@ class KickstartDeprecationWarning(KickstartParseWarning, DeprecationWarning):
37     """A class for warnings occurring during parsing related to using deprecated
38        commands and options.
39     """
40+
41+class KickstartAuthError(KickstartError):
42+    """An exception for errors relating to authentication failed while
43+       downloading kickstart from web server
44+    """
45+    def __init__(self, msg):
46+        """Create a new KickstartAuthError exception instance with the
47+           descriptive message val. val should be the return value of
48+           formatErrorMsg.
49+        """
50+        KickstartError.__init__(self, msg)
51+
52+    def __str__(self):
53+        return self.value
54diff --git a/pykickstart/load.py b/pykickstart/load.py
55index 30e2fcfa..b984876d 100644
56--- a/pykickstart/load.py
57+++ b/pykickstart/load.py
58@@ -18,9 +18,12 @@
59 # with the express permission of Red Hat, Inc.
60 #
61 import requests
62+from requests.auth import HTTPDigestAuth
63+from requests.auth import HTTPBasicAuth
64+
65 import shutil
66
67-from pykickstart.errors import KickstartError
68+from pykickstart.errors import KickstartError, KickstartAuthError
69 from pykickstart.i18n import _
70 from requests.exceptions import SSLError, RequestException
71
72@@ -28,7 +31,7 @@ _is_url = lambda location: '://' in location  # RFC 3986
73
74 SSL_VERIFY = True
75
76-def load_to_str(location):
77+def load_to_str(location, user=None, passwd=None):
78     '''Load a destination URL or file into a string.
79     Type of input is inferred automatically.
80
81@@ -39,7 +42,7 @@ def load_to_str(location):
82     Raises: KickstartError on error reading'''
83
84     if _is_url(location):
85-        return _load_url(location)
86+        return _load_url(location, user=user, passwd=passwd)
87     else:
88         return _load_file(location)
89
90@@ -69,11 +72,30 @@ def load_to_file(location, destination):
91         _copy_file(location, destination)
92         return destination
93
94-def _load_url(location):
95-    '''Load a location (URL or filename) and return contents as string'''
96+def _get_auth(location, user=None, passwd=None):
97+
98+    auth = None
99+    request = requests.get(location, verify=SSL_VERIFY)
100+    if request.status_code == requests.codes.unauthorized:
101+        if user is None or passwd is None:
102+            log.info("Require Authentication")
103+            raise KickstartAuthError("Require Authentication.\nAppend 'ksuser=<username> kspasswd=<password>' to boot command")
104
105+        reasons = request.headers.get("WWW-Authenticate", "").split()
106+        if reasons:
107+            auth_type = reasons[0]
108+        if auth_type == "Basic":
109+            auth = HTTPBasicAuth(user, passwd)
110+        elif auth_type == "Digest":
111+            auth=HTTPDigestAuth(user, passwd)
112+
113+    return auth
114+
115+def _load_url(location, user=None, passwd=None):
116+    '''Load a location (URL or filename) and return contents as string'''
117+    auth = _get_auth(location, user=user, passwd=passwd)
118     try:
119-        request = requests.get(location, verify=SSL_VERIFY)
120+        request = requests.get(location, verify=SSL_VERIFY, auth=auth)
121     except SSLError as e:
122         raise KickstartError(_('Error securely accessing URL "%s"') % location + ': {e}'.format(e=str(e)))
123     except RequestException as e:
124diff --git a/pykickstart/parser.py b/pykickstart/parser.py
125index b23e54f1..e10f06b5 100644
126--- a/pykickstart/parser.py
127+++ b/pykickstart/parser.py
128@@ -796,7 +796,7 @@ class KickstartParser(object):
129         i = PutBackIterator(s.splitlines(True) + [""])
130         self._stateMachine(i)
131
132-    def readKickstart(self, f, reset=True):
133+    def readKickstart(self, f, reset=True, username=None, password=None):
134         """Process a kickstart file, given by the filename f."""
135         if reset:
136             self._reset()
137@@ -817,7 +817,7 @@ class KickstartParser(object):
138         self.currentdir[self._includeDepth] = cd
139
140         try:
141-            s = load_to_str(f)
142+            s = load_to_str(f, user=username, passwd=password)
143         except KickstartError as e:
144             raise KickstartError(_("Unable to open input kickstart file: %s") % str(e), lineno=0)
145
146