/
__init__.py
336 lines (274 loc) · 12.2 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# The base classes for Anaconda TUI Spokes
#
# Copyright (C) (2012) Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): Martin Sivak <msivak@redhat.com>
#
from pyanaconda.ui.tui import simpleline as tui
from pyanaconda.ui.tui.tuiobject import TUIObject, YesNoDialog
from pyanaconda.ui.common import Spoke, StandaloneSpoke, NormalSpoke
from pyanaconda.users import validatePassword, cryptPassword
import re
from collections import namedtuple
from pyanaconda.iutil import setdeepattr, getdeepattr
from pyanaconda.i18n import N_, _
from pyanaconda.constants import PASSWORD_CONFIRM_ERROR_TUI, PW_ASCII_CHARS
from pyanaconda.constants import PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR
__all__ = ["TUISpoke", "EditTUISpoke", "EditTUIDialog", "EditTUISpokeEntry",
"StandaloneSpoke", "NormalTUISpoke"]
# Inherit abstract methods from Spoke
# pylint: disable=abstract-method
class TUISpoke(TUIObject, tui.Widget, Spoke):
"""Base TUI Spoke class implementing the pyanaconda.ui.common.Spoke API.
It also acts as a Widget so we can easily add it to Hub, where is shows
as a summary box with title, description and completed checkbox.
:param title: title of this spoke
:type title: unicode
:param category: category this spoke belongs to
:type category: string
"""
title = N_("Default spoke title")
def __init__(self, app, data, storage, payload, instclass):
if self.__class__ is TUISpoke:
raise TypeError("TUISpoke is an abstract class")
TUIObject.__init__(self, app, data)
tui.Widget.__init__(self)
Spoke.__init__(self, storage, payload, instclass)
@property
def status(self):
return _("testing status...")
@property
def completed(self):
return True
def refresh(self, args=None):
TUIObject.refresh(self, args)
return True
def input(self, args, key):
"""Handle the input, the base class just forwards it to the App level."""
return key
def render(self, width):
"""Render the summary representation for Hub to internal buffer."""
tui.Widget.render(self, width)
if self.mandatory and not self.completed:
key = "!"
elif self.completed:
key = "x"
else:
key = " "
# always set completed = True here; otherwise key value won't be
# displayed if completed (spoke value from above) is False
c = tui.CheckboxWidget(key=key, completed=True,
title=_(self.title), text=self.status)
c.render(width)
self.draw(c)
class NormalTUISpoke(TUISpoke, NormalSpoke):
pass
EditTUISpokeEntry = namedtuple("EditTUISpokeEntry", ["title", "attribute", "aux", "visible"])
# Inherit abstract methods from NormalTUISpoke
# pylint: disable=abstract-method
class EditTUIDialog(NormalTUISpoke):
"""Spoke/dialog used to read new value of textual or password data"""
title = N_("New value")
PASSWORD = re.compile(".*")
def __init__(self, app, data, storage, payload, instclass, policy_name=""):
if self.__class__ is EditTUIDialog:
raise TypeError("EditTUIDialog is an abstract class")
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
self.value = None
def refresh(self, args=None):
# Configure the password policy, if available. Otherwise use defaults.
self.policy = self.data.anaconda.pwpolicy.get_policy(policy_name)
if not self.policy:
self.policy = self.data.anaconda.PwPolicyData()
self._window = []
self.value = None
return True
def prompt(self, entry=None):
if not entry:
return None
if entry.aux == self.PASSWORD:
pw = self._app.raw_input(_("%s: ") % entry.title, hidden=True)
confirm = self._app.raw_input(_("%s (confirm): ") % entry.title, hidden=True)
if (pw and not confirm) or (confirm and not pw):
print(_("You must enter your root password and confirm it by typing"
" it a second time to continue."))
return None
if (pw != confirm):
print(_(PASSWORD_CONFIRM_ERROR_TUI))
return None
# If an empty password was provided, unset the value
if not pw:
self.value = ""
return None
valid, strength, message = validatePassword(pw, user=None, minlen=self.policy.minlen)
if not valid:
print(message)
return None
if strength < self.policy.minquality:
if self.policy.strict:
done_msg = ""
else:
done_msg = _("\nWould you like to use it anyway?")
if message:
error = _(PASSWORD_WEAK_WITH_ERROR) % (message, done_msg)
else:
error = _(PASSWORD_WEAK) % done_msg
if not self.policy.strict:
question_window = YesNoDialog(self._app, error)
self._app.switch_screen_modal(question_window)
if not question_window.answer:
return None
else:
print(error)
return None
if any(char not in PW_ASCII_CHARS for char in pw):
print(_("You have provided a password containing non-ASCII characters.\n"
"You may not be able to switch between keyboard layouts to login.\n"))
self.value = cryptPassword(pw)
return None
else:
return _("Enter new value for '%s' and press enter\n") % entry.title
def input(self, entry, key):
if entry.aux.match(key):
self.value = key
self.close()
return True
else:
print(_("You have provided an invalid user name: %s\n"
"Tip: Keep your user name shorter than 32 characters and do not use spaces.\n") % key)
return NormalTUISpoke.input(self, entry, key)
class OneShotEditTUIDialog(EditTUIDialog):
"""The same as EditTUIDialog, but closes automatically after
the value is read
"""
def prompt(self, entry=None):
ret = None
if entry:
ret = EditTUIDialog.prompt(self, entry)
if ret is None:
self.close()
return ret
# Inherit abstract methods from NormalTUISpoke
# pylint: disable=abstract-method
class EditTUISpoke(NormalTUISpoke):
"""Spoke with declarative semantics, it contains
a list of titles, attribute names and regexps
that specify the fields of an object the user
allowed to edit.
"""
# self.data's subattribute name
# empty string means __init__ will provide
# something else
edit_data = ""
# constants to be used in the aux field
# and mark the entry as a password or checkbox field
PASSWORD = EditTUIDialog.PASSWORD
CHECK = "check"
# list of fields in the format of named tuples like:
# EditTUISpokeEntry(title, attribute, aux, visible)
# title - Nontranslated title of the entry
# attribute - The edited object's attribute name
# aux - Compiled regular expression or one of the
# two constants from above.
# It will be used to check the value typed
# by user and to show the proper entry
# for password, text or checkbox.
# visible - True, False or a function that accepts
# two arguments - self and the edited object
# It is evaluated and used to display or
# hide this attribute's entry
edit_fields = [
]
def __init__(self, app, data, storage, payload, instclass, policy_name=""):
if self.__class__ is EditTUISpoke:
raise TypeError("EditTUISpoke is an abstract class")
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
self.dialog = OneShotEditTUIDialog(app, data, storage, payload, instclass, policy_name=policy_name)
# self.args should hold the object this Spoke is supposed
# to edit
self.args = None
@property
def visible_fields(self):
"""Get the list of currently visible entries"""
# it would be nice to have this a static list, but visibility of the
# entries often depends on the current state of the spoke and thus
# changes dynamically
ret = []
for entry in self.edit_fields:
if callable(entry.visible) and entry.visible(self, self.args):
ret.append(entry)
elif not callable(entry.visible) and entry.visible:
ret.append(entry)
return ret
def refresh(self, args=None):
NormalTUISpoke.refresh(self, args)
if args:
self.args = args
elif self.edit_data:
self.args = self.data
for key in self.edit_data.split("."):
self.args = getattr(self.args, key)
def _prep_text(i, entry):
number = tui.TextWidget("%2d)" % i)
title = tui.TextWidget(_(entry.title))
value = getdeepattr(self.args, entry.attribute)
value = tui.TextWidget(value)
return tui.ColumnWidget([(3, [number]), (None, [title, value])], 1)
def _prep_check(i, entry):
number = tui.TextWidget("%2d)" % i)
value = getdeepattr(self.args, entry.attribute)
ch = tui.CheckboxWidget(title=_(entry.title), completed=bool(value))
return tui.ColumnWidget([(3, [number]), (None, [ch])], 1)
def _prep_password(i, entry):
number = tui.TextWidget("%2d)" % i)
title = tui.TextWidget(_(entry.title))
value = ""
if len(getdeepattr(self.args, entry.attribute)) > 0:
value = _("Password set.")
value = tui.TextWidget(value)
return tui.ColumnWidget([(3, [number]), (None, [title, value])], 1)
for idx, entry in enumerate(self.visible_fields):
entry_type = entry.aux
if entry_type == self.PASSWORD:
w = _prep_password(idx+1, entry)
elif entry_type == self.CHECK:
w = _prep_check(idx+1, entry)
else:
w = _prep_text(idx+1, entry)
self._window.append(w)
return True
def input(self, args, key):
try:
idx = int(key) - 1
if idx >= 0 and idx < len(self.visible_fields):
if self.visible_fields[idx].aux == self.CHECK:
setdeepattr(self.args, self.visible_fields[idx].attribute,
not getdeepattr(self.args, self.visible_fields[idx][1]))
self.app.redraw()
self.apply()
else:
self.app.switch_screen_modal(self.dialog, self.visible_fields[idx])
if self.dialog.value is not None:
setdeepattr(self.args, self.visible_fields[idx].attribute,
self.dialog.value)
self.apply()
return True
except ValueError:
pass
return NormalTUISpoke.input(self, args, key)
class StandaloneTUISpoke(TUISpoke, StandaloneSpoke):
pass