SCAutolib 3.3.5__tar.gz → 3.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of SCAutolib might be problematic. Click here for more details.
- {scautolib-3.3.5/SCAutolib.egg-info → scautolib-3.4.1}/PKG-INFO +12 -2
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/cli_commands.py +182 -35
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/controller.py +49 -34
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/CA.py +3 -17
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/file.py +6 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/gui.py +81 -24
- {scautolib-3.3.5 → scautolib-3.4.1/SCAutolib.egg-info}/PKG-INFO +12 -2
- {scautolib-3.3.5 → scautolib-3.4.1}/setup.py +1 -1
- {scautolib-3.3.5 → scautolib-3.4.1}/LICENSE +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/MANIFEST.in +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/README.md +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/__init__.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/enums.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/exceptions.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/isDistro.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/__init__.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/authselect.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/card.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/log.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/models/user.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/ca.cnf +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/gnome_disable_welcome +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/softhsm2.conf +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/sssd.conf-10 +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/sssd.conf-8or9 +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/user.cnf +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/virt_cacard.service +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/templates/virtcacard.cil +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib/utils.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib.egg-info/SOURCES.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib.egg-info/dependency_links.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib.egg-info/entry_points.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib.egg-info/requires.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/SCAutolib.egg-info/top_level.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/requirements.txt +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/setup.cfg +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_ca.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_card.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_cli.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_controller.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_file.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_openssl_conf.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_sssd_conf.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_user.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/test/test_utils.py +0 -0
- {scautolib-3.3.5 → scautolib-3.4.1}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: SCAutolib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: Python library for automation tests of smart cards using virtualization.
|
|
5
5
|
Home-page: https://github.com/redhat-qe-security/SCAutolib
|
|
6
6
|
Author: Pavel Yadlouski
|
|
@@ -32,6 +32,16 @@ Requires-Dist: pandas; extra == "graphical"
|
|
|
32
32
|
Requires-Dist: numpy; extra == "graphical"
|
|
33
33
|
Requires-Dist: pytesseract; extra == "graphical"
|
|
34
34
|
Requires-Dist: keyboard; extra == "graphical"
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: author-email
|
|
37
|
+
Dynamic: classifier
|
|
38
|
+
Dynamic: description
|
|
39
|
+
Dynamic: description-content-type
|
|
40
|
+
Dynamic: home-page
|
|
41
|
+
Dynamic: provides-extra
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
Dynamic: summary
|
|
35
45
|
|
|
36
46
|
# Smart Card Automation library (SCAutolib)
|
|
37
47
|
Test automation library for Smart Cards.
|
|
@@ -6,15 +6,53 @@ import click
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from sys import exit
|
|
8
8
|
|
|
9
|
+
from collections import OrderedDict
|
|
10
|
+
|
|
9
11
|
from SCAutolib import logger, exceptions, schema_user
|
|
10
12
|
from SCAutolib.controller import Controller
|
|
11
13
|
from SCAutolib.enums import ReturnCode
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
def check_conf_path(conf):
|
|
17
|
+
return click.Path(exists=True, resolve_path=True)(conf)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# In Help output, force the subcommand list to match the order
|
|
21
|
+
# listed in this file. Solution was found here:
|
|
22
|
+
# https://github.com/pallets/click/issues/513#issuecomment-301046782
|
|
23
|
+
class NaturalOrderGroup(click.Group):
|
|
24
|
+
"""
|
|
25
|
+
Command group trying to list subcommands in the order they were added.
|
|
26
|
+
Example use::
|
|
27
|
+
|
|
28
|
+
@click.group(cls=NaturalOrderGroup)
|
|
29
|
+
|
|
30
|
+
If passing dict of commands from other sources, ensure they are of type
|
|
31
|
+
OrderedDict and properly ordered, otherwise order of them will be random
|
|
32
|
+
and newly added will come to the end.
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, name=None, commands=None, **attrs):
|
|
35
|
+
if commands is None:
|
|
36
|
+
commands = OrderedDict()
|
|
37
|
+
elif not isinstance(commands, OrderedDict):
|
|
38
|
+
commands = OrderedDict(commands)
|
|
39
|
+
click.Group.__init__(self, name=name,
|
|
40
|
+
commands=commands,
|
|
41
|
+
**attrs)
|
|
42
|
+
|
|
43
|
+
def list_commands(self, ctx):
|
|
44
|
+
"""
|
|
45
|
+
List command names as they are in commands dict.
|
|
46
|
+
|
|
47
|
+
If the dict is OrderedDict, it will preserve the order commands
|
|
48
|
+
were added.
|
|
49
|
+
"""
|
|
50
|
+
return self.commands.keys()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.group(cls=NaturalOrderGroup)
|
|
15
54
|
@click.option("--conf", "-c",
|
|
16
55
|
default="./conf.json",
|
|
17
|
-
type=click.Path(exists=True, resolve_path=True),
|
|
18
56
|
show_default=True,
|
|
19
57
|
help="Path to JSON configuration file.")
|
|
20
58
|
@click.option('--force', "-f", is_flag=True, default=False, show_default=True,
|
|
@@ -29,35 +67,13 @@ def cli(ctx, force, verbose, conf):
|
|
|
29
67
|
logger.setLevel(verbose)
|
|
30
68
|
ctx.ensure_object(dict) # Create a dict to store the context
|
|
31
69
|
ctx.obj["FORCE"] = force # Store the force option in the context
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@click.option("--ca-type", "-t",
|
|
37
|
-
required=False,
|
|
38
|
-
default='all',
|
|
39
|
-
type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
|
|
40
|
-
show_default=True,
|
|
41
|
-
help="Type of the CA to be configured. If not set, all CA's "
|
|
42
|
-
"from the config file would be configured")
|
|
43
|
-
@click.pass_context
|
|
44
|
-
def setup_ca(ctx, ca_type):
|
|
45
|
-
"""
|
|
46
|
-
Configure the CA's in the config file. If more than one CA is
|
|
47
|
-
specified, specified CA type would be configured.
|
|
48
|
-
"""
|
|
49
|
-
cnt = ctx.obj["CONTROLLER"]
|
|
50
|
-
if ca_type == 'all':
|
|
51
|
-
cnt.setup_local_ca(force=ctx.obj["FORCE"])
|
|
52
|
-
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
|
|
53
|
-
elif ca_type == 'local':
|
|
54
|
-
cnt.setup_local_ca(force=ctx.obj["FORCE"])
|
|
55
|
-
elif ca_type == 'ipa':
|
|
56
|
-
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
|
|
57
|
-
exit(ReturnCode.SUCCESS.value)
|
|
70
|
+
parsed_conf = None
|
|
71
|
+
if ctx.invoked_subcommand != "gui":
|
|
72
|
+
parsed_conf = check_conf_path(conf)
|
|
73
|
+
ctx.obj["CONTROLLER"] = Controller(parsed_conf)
|
|
58
74
|
|
|
59
75
|
|
|
60
|
-
@
|
|
76
|
+
@cli.command()
|
|
61
77
|
@click.option("--gdm", "-g",
|
|
62
78
|
required=False,
|
|
63
79
|
default=False,
|
|
@@ -85,7 +101,32 @@ def prepare(ctx, gdm, install_missing, graphical):
|
|
|
85
101
|
exit(ReturnCode.SUCCESS.value)
|
|
86
102
|
|
|
87
103
|
|
|
88
|
-
@
|
|
104
|
+
@cli.command()
|
|
105
|
+
@click.option("--ca-type", "-t",
|
|
106
|
+
required=False,
|
|
107
|
+
default='all',
|
|
108
|
+
type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
|
|
109
|
+
show_default=True,
|
|
110
|
+
help="Type of the CA to be configured. If not set, all CA's "
|
|
111
|
+
"from the config file would be configured")
|
|
112
|
+
@click.pass_context
|
|
113
|
+
def setup_ca(ctx, ca_type):
|
|
114
|
+
"""
|
|
115
|
+
Configure the CA's in the config file. If more than one CA is
|
|
116
|
+
specified, specified CA type would be configured.
|
|
117
|
+
"""
|
|
118
|
+
cnt = ctx.obj["CONTROLLER"]
|
|
119
|
+
if ca_type == 'all':
|
|
120
|
+
cnt.setup_local_ca(force=ctx.obj["FORCE"])
|
|
121
|
+
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
|
|
122
|
+
elif ca_type == 'local':
|
|
123
|
+
cnt.setup_local_ca(force=ctx.obj["FORCE"])
|
|
124
|
+
elif ca_type == 'ipa':
|
|
125
|
+
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
|
|
126
|
+
exit(ReturnCode.SUCCESS.value)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@cli.command()
|
|
89
130
|
@click.argument("name",
|
|
90
131
|
required=True,
|
|
91
132
|
default=None)
|
|
@@ -154,7 +195,7 @@ def setup_user(ctx, name, card_dir, card_type, passwd, pin, user_type):
|
|
|
154
195
|
exit(ReturnCode.SUCCESS.value)
|
|
155
196
|
|
|
156
197
|
|
|
157
|
-
@
|
|
198
|
+
@cli.command()
|
|
158
199
|
@click.pass_context
|
|
159
200
|
def cleanup(ctx):
|
|
160
201
|
"""
|
|
@@ -165,7 +206,113 @@ def cleanup(ctx):
|
|
|
165
206
|
exit(ReturnCode.SUCCESS.value)
|
|
166
207
|
|
|
167
208
|
|
|
168
|
-
cli.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
209
|
+
@cli.group(cls=NaturalOrderGroup, chain=True)
|
|
210
|
+
@click.option("--install-missing", "-i",
|
|
211
|
+
required=False,
|
|
212
|
+
default=False,
|
|
213
|
+
is_flag=True,
|
|
214
|
+
help="Install missing packages")
|
|
215
|
+
@click.pass_context
|
|
216
|
+
def gui(ctx, install_missing):
|
|
217
|
+
""" Run GUI Test commands """
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@gui.command()
|
|
222
|
+
def init():
|
|
223
|
+
""" Initialize GUI for testing """
|
|
224
|
+
return "init"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@gui.command()
|
|
228
|
+
@click.option("--no",
|
|
229
|
+
required=False,
|
|
230
|
+
default=False,
|
|
231
|
+
is_flag=True,
|
|
232
|
+
help="Reverse the action")
|
|
233
|
+
@click.argument("name")
|
|
234
|
+
def assert_text(name, no):
|
|
235
|
+
""" Check if a word is found on the screen """
|
|
236
|
+
if no:
|
|
237
|
+
return f"assert_no_text:{name}"
|
|
238
|
+
return f"assert_text:{name}"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@gui.command()
|
|
242
|
+
@click.argument("name")
|
|
243
|
+
def click_on(name):
|
|
244
|
+
""" Click on object containing word """
|
|
245
|
+
return f"click_on:{name}"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@gui.command()
|
|
249
|
+
@click.option("--no",
|
|
250
|
+
required=False,
|
|
251
|
+
default=False,
|
|
252
|
+
is_flag=True,
|
|
253
|
+
help="Reverse the action")
|
|
254
|
+
def check_home_screen(no):
|
|
255
|
+
""" Check if screen appears to be the home screen """
|
|
256
|
+
if no:
|
|
257
|
+
return "check_no_home_screen"
|
|
258
|
+
return "check_home_screen"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@gui.command()
|
|
262
|
+
@click.argument("keys")
|
|
263
|
+
def kb_send(keys):
|
|
264
|
+
""" Send key(s) to keyboard """
|
|
265
|
+
return f"kb_send:{keys}"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@gui.command()
|
|
269
|
+
@click.argument("keys")
|
|
270
|
+
def kb_write(keys):
|
|
271
|
+
""" Send string to keyboard """
|
|
272
|
+
return f"kb_write:{keys}"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@gui.command()
|
|
276
|
+
def done():
|
|
277
|
+
""" cleanup after testing """
|
|
278
|
+
return "done"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@gui.result_callback()
|
|
282
|
+
@click.pass_context
|
|
283
|
+
def run_all(ctx, actions, install_missing):
|
|
284
|
+
""" Run all cli actions in order """
|
|
285
|
+
ctx.obj["CONTROLLER"].setup_graphical(install_missing, True)
|
|
286
|
+
|
|
287
|
+
from SCAutolib.models.gui import GUI
|
|
288
|
+
gui = GUI(from_cli=True)
|
|
289
|
+
for action in actions:
|
|
290
|
+
if "init" in action:
|
|
291
|
+
gui.__enter__()
|
|
292
|
+
if "assert_text" in action:
|
|
293
|
+
assert_text = action.split(":", 1)[1]
|
|
294
|
+
gui.assert_text(assert_text)
|
|
295
|
+
if "assert_no_text" in action:
|
|
296
|
+
assert_text = action.split(":", 1)[1]
|
|
297
|
+
gui.assert_no_text(assert_text)
|
|
298
|
+
if "click_on" in action:
|
|
299
|
+
click_on = action.split(":", 1)[1]
|
|
300
|
+
gui.click_on(click_on)
|
|
301
|
+
if "check_home_screen" in action:
|
|
302
|
+
gui.check_home_screen()
|
|
303
|
+
if "check_no_home_screen" in action:
|
|
304
|
+
gui.check_home_screen(False)
|
|
305
|
+
if "kb_send" in action:
|
|
306
|
+
params = action.split(":", 1)[1].split()[0]
|
|
307
|
+
gui.kb_send(params)
|
|
308
|
+
if "kb_write" in action:
|
|
309
|
+
params = action.split(":", 1)[1].split()[0]
|
|
310
|
+
gui.kb_write(params)
|
|
311
|
+
gui.kb_send('enter')
|
|
312
|
+
if "done" in action:
|
|
313
|
+
gui.__exit__(None, None, None)
|
|
314
|
+
ctx.obj["CONTROLLER"].cleanup()
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
cli()
|
|
@@ -34,7 +34,7 @@ class Controller:
|
|
|
34
34
|
def conf_path(self):
|
|
35
35
|
return self._lib_conf_path
|
|
36
36
|
|
|
37
|
-
def __init__(self, config: Union[Path, str], params: {} = None):
|
|
37
|
+
def __init__(self, config: Union[Path, str] = None, params: {} = None):
|
|
38
38
|
"""
|
|
39
39
|
Constructor will parse and check input configuration file. If some
|
|
40
40
|
required fields in the configuration are missing, CLI parameters
|
|
@@ -55,15 +55,18 @@ class Controller:
|
|
|
55
55
|
# Check params
|
|
56
56
|
|
|
57
57
|
# Parse config file
|
|
58
|
-
self.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
self.lib_conf = None
|
|
59
|
+
if config:
|
|
60
|
+
self._lib_conf_path = config.absolute() if isinstance(config, Path) \
|
|
61
|
+
else Path(config).absolute()
|
|
62
|
+
|
|
63
|
+
with self._lib_conf_path.open("r") as f:
|
|
64
|
+
tmp_conf = json.load(f)
|
|
65
|
+
if tmp_conf is None:
|
|
66
|
+
raise exceptions.SCAutolibException(
|
|
67
|
+
"Data are not loaded correctly.")
|
|
68
|
+
self.lib_conf = self._validate_configuration(tmp_conf, params)
|
|
69
|
+
|
|
67
70
|
self.users = []
|
|
68
71
|
for d in (LIB_DIR, LIB_BACKUP, LIB_DUMP, LIB_DUMP_USERS, LIB_DUMP_CAS,
|
|
69
72
|
LIB_DUMP_CARDS, LIB_DUMP_CONFS):
|
|
@@ -147,12 +150,6 @@ class Controller:
|
|
|
147
150
|
|
|
148
151
|
packages = ["opensc", "httpd", "sssd", "sssd-tools", "gnutls-utils",
|
|
149
152
|
"openssl", "nss-tools"]
|
|
150
|
-
if gdm:
|
|
151
|
-
packages.append("gdm")
|
|
152
|
-
|
|
153
|
-
if graphical:
|
|
154
|
-
# ffmpeg-free is in EPEL repo
|
|
155
|
-
packages += ["tesseract", "ffmpeg-free"]
|
|
156
153
|
|
|
157
154
|
# Prepare for virtual cards
|
|
158
155
|
if any(c["card_type"] == CardType.virtual
|
|
@@ -166,7 +163,7 @@ class Controller:
|
|
|
166
163
|
run("dnf -y copr enable jjelen/vsmartcard{0}".format(extra_args))
|
|
167
164
|
|
|
168
165
|
# Add IPA packages if needed
|
|
169
|
-
if any([u["user_type"]
|
|
166
|
+
if any([u["user_type"] == UserType.ipa
|
|
170
167
|
for u in self.lib_conf["users"]]):
|
|
171
168
|
packages += self._general_steps_for_ipa()
|
|
172
169
|
|
|
@@ -181,21 +178,7 @@ class Controller:
|
|
|
181
178
|
raise exceptions.SCAutolibException(msg)
|
|
182
179
|
|
|
183
180
|
if graphical:
|
|
184
|
-
|
|
185
|
-
run(['dnf', 'groupinstall', 'Server with GUI', '-y',
|
|
186
|
-
'--allowerasing'])
|
|
187
|
-
run(['pip', 'install', 'python-uinput'])
|
|
188
|
-
else:
|
|
189
|
-
# Fedora doesn't have server with GUI group so installed gdm
|
|
190
|
-
# manually and also python3-uinput should be installed from RPM
|
|
191
|
-
run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
|
|
192
|
-
# disable subscription message
|
|
193
|
-
run(['systemctl', '--global', 'mask',
|
|
194
|
-
'org.gnome.SettingsDaemon.Subscription.target'])
|
|
195
|
-
# disable welcome message
|
|
196
|
-
self.dconf_file.create()
|
|
197
|
-
self.dconf_file.save()
|
|
198
|
-
run('dconf update')
|
|
181
|
+
self.setup_graphical(install_missing, gdm)
|
|
199
182
|
|
|
200
183
|
if not isDistro('fedora'):
|
|
201
184
|
run(['dnf', 'groupinstall', "Smart Card Support", '-y',
|
|
@@ -216,6 +199,38 @@ class Controller:
|
|
|
216
199
|
dump_to_json(user.User(username="root",
|
|
217
200
|
password=self.lib_conf["root_passwd"]))
|
|
218
201
|
|
|
202
|
+
def setup_graphical(self, install_missing: bool, gdm: bool):
|
|
203
|
+
packages = ["gcc", "tesseract", "ffmpeg-free"]
|
|
204
|
+
|
|
205
|
+
if gdm:
|
|
206
|
+
packages.append("gdm")
|
|
207
|
+
|
|
208
|
+
missing = _check_packages(packages)
|
|
209
|
+
if install_missing and missing:
|
|
210
|
+
_install_packages(missing)
|
|
211
|
+
elif missing:
|
|
212
|
+
msg = "Can't continue with graphical. Some packages are missing: " \
|
|
213
|
+
f"{', '.join(missing)}"
|
|
214
|
+
logger.critical(msg)
|
|
215
|
+
raise exceptions.SCAutolibException(msg)
|
|
216
|
+
|
|
217
|
+
if not isDistro('fedora'):
|
|
218
|
+
run(['dnf', 'groupinstall', 'Server with GUI', '-y',
|
|
219
|
+
'--allowerasing'])
|
|
220
|
+
run(['pip', 'install', 'python-uinput'])
|
|
221
|
+
else:
|
|
222
|
+
# Fedora doesn't have server with GUI group so installed gdm
|
|
223
|
+
# manually and also python3-uinput should be installed from RPM
|
|
224
|
+
run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
|
|
225
|
+
# disable subscription message
|
|
226
|
+
run(['systemctl', '--global', 'mask',
|
|
227
|
+
'org.gnome.SettingsDaemon.Subscription.target'])
|
|
228
|
+
# disable welcome message
|
|
229
|
+
if not self.dconf_file.exists():
|
|
230
|
+
self.dconf_file.create()
|
|
231
|
+
self.dconf_file.save()
|
|
232
|
+
run('dconf update')
|
|
233
|
+
|
|
219
234
|
def setup_local_ca(self, force: bool = False):
|
|
220
235
|
"""
|
|
221
236
|
Setup local CA based on configuration from the configuration file. All
|
|
@@ -548,9 +563,9 @@ class Controller:
|
|
|
548
563
|
logger.debug("idm:DL1 module is installed")
|
|
549
564
|
|
|
550
565
|
if isDistro('fedora'):
|
|
551
|
-
return ["freeipa-client"]
|
|
566
|
+
return ["e2fsprogs", "freeipa-client"]
|
|
552
567
|
else:
|
|
553
|
-
return ["ipa-client"]
|
|
568
|
+
return ["e2fsprogs", "ipa-client"]
|
|
554
569
|
|
|
555
570
|
def get_user_dict(self, name):
|
|
556
571
|
"""
|
|
@@ -9,7 +9,7 @@ import json
|
|
|
9
9
|
import os
|
|
10
10
|
import python_freeipa
|
|
11
11
|
from cryptography import x509
|
|
12
|
-
from hashlib import
|
|
12
|
+
from hashlib import sha256
|
|
13
13
|
from pathlib import Path, PosixPath
|
|
14
14
|
from python_freeipa import exceptions
|
|
15
15
|
from python_freeipa.client_meta import ClientMeta
|
|
@@ -578,8 +578,6 @@ class IPAServerCA(BaseCA):
|
|
|
578
578
|
f.write(cnt)
|
|
579
579
|
logger.info(
|
|
580
580
|
"IPA server is added to /etc/resolv.conf as first nameserver")
|
|
581
|
-
run("chattr -i /etc/resolv.conf")
|
|
582
|
-
logger.info("File /etc/resolv.conf is blocked for editing")
|
|
583
581
|
|
|
584
582
|
with open("/etc/resolv.conf", "r") as f:
|
|
585
583
|
logger.debug(f"New resolv.conf\n{f.read()}")
|
|
@@ -608,18 +606,6 @@ class IPAServerCA(BaseCA):
|
|
|
608
606
|
from invoke import Responder
|
|
609
607
|
from fabric.connection import Connection
|
|
610
608
|
|
|
611
|
-
class __PKeyChild(paramiko.PKey):
|
|
612
|
-
"""This child class is need to fix SSH connection with MD5 algorithm
|
|
613
|
-
in FIPS mode
|
|
614
|
-
|
|
615
|
-
This is just workaround until PR in paramiko would be accepted
|
|
616
|
-
https://github.com/paramiko/paramiko/issues/396. After this PR is
|
|
617
|
-
merged, delete this class
|
|
618
|
-
"""
|
|
619
|
-
|
|
620
|
-
def get_fingerprint_improved(self):
|
|
621
|
-
return md5(self.asbytes(), usedforsecurity=False).digest()
|
|
622
|
-
|
|
623
609
|
kinitpass = Responder(
|
|
624
610
|
pattern=f"Password for admin@{self._ipa_server_realm}: ",
|
|
625
611
|
response=f"{self._ipa_server_admin_passwd}\n")
|
|
@@ -628,11 +614,11 @@ class IPAServerCA(BaseCA):
|
|
|
628
614
|
with Connection(self._ipa_server_ip, user="root",
|
|
629
615
|
connect_kwargs={
|
|
630
616
|
"password": self._ipa_server_root_passwd}) as c:
|
|
631
|
-
# Delete this block when PR in paramiko will be accepted
|
|
617
|
+
# TODO Delete this block when PR in paramiko will be accepted
|
|
632
618
|
# https://github.com/paramiko/paramiko/issues/396
|
|
633
619
|
#### noqa:E266
|
|
634
620
|
paramiko.PKey.get_fingerprint = \
|
|
635
|
-
|
|
621
|
+
lambda x: sha256(x.asbytes()).digest()
|
|
636
622
|
c.client = paramiko.SSHClient()
|
|
637
623
|
c.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
638
624
|
#### noqa:E266
|
|
@@ -92,6 +92,12 @@ class File:
|
|
|
92
92
|
f"Removed file {self._conf_file}."
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
+
def exists(self):
|
|
96
|
+
"""
|
|
97
|
+
Checks if a file exists. Returns boolean.
|
|
98
|
+
"""
|
|
99
|
+
return self._conf_file.exists()
|
|
100
|
+
|
|
95
101
|
def set(self, key: str, value: Union[int, str, bool], section: str = None,
|
|
96
102
|
separator: str = "="):
|
|
97
103
|
"""
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
import os
|
|
3
|
-
from os.path import join
|
|
4
2
|
from time import sleep, time
|
|
3
|
+
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import cv2
|
|
7
6
|
import keyboard
|
|
@@ -24,7 +23,14 @@ class Screen:
|
|
|
24
23
|
"""
|
|
25
24
|
self.directory = directory
|
|
26
25
|
self.html_file = html_file
|
|
26
|
+
|
|
27
|
+
taken_images = [str(image).split('/')[-1]
|
|
28
|
+
for image in Path(directory).iterdir()]
|
|
29
|
+
taken_images.sort(reverse=True)
|
|
30
|
+
|
|
27
31
|
self.screenshot_num = 1
|
|
32
|
+
if len(taken_images) > 0:
|
|
33
|
+
self.screenshot_num = int(taken_images[0].split('.')[0]) + 1
|
|
28
34
|
|
|
29
35
|
def screenshot(self, timeout: float = 30):
|
|
30
36
|
"""Runs ffmpeg to take a screenshot.
|
|
@@ -237,7 +243,8 @@ def log_decorator(func):
|
|
|
237
243
|
class GUI:
|
|
238
244
|
"""Represents the GUI and allows controlling the system under test."""
|
|
239
245
|
|
|
240
|
-
def __init__(self, wait_time: float = 5, res_dir_name: str = None
|
|
246
|
+
def __init__(self, wait_time: float = 5, res_dir_name: str = None,
|
|
247
|
+
from_cli: bool = False):
|
|
241
248
|
"""Initializes the GUI of system under test.
|
|
242
249
|
|
|
243
250
|
:param wait_time: Time to wait after each action
|
|
@@ -247,30 +254,52 @@ class GUI:
|
|
|
247
254
|
|
|
248
255
|
self.wait_time = wait_time
|
|
249
256
|
self.gdm_init_time = 10
|
|
257
|
+
self.from_cli = from_cli
|
|
250
258
|
# Create the directory for screenshots
|
|
259
|
+
self.html_directory = Path("/tmp/SC-tests")
|
|
260
|
+
if not self.html_directory.exists():
|
|
261
|
+
self.html_directory.mkdir()
|
|
251
262
|
if res_dir_name:
|
|
252
|
-
self.html_directory =
|
|
263
|
+
self.html_directory = self.html_directory.joinpath(res_dir_name)
|
|
264
|
+
elif from_cli:
|
|
265
|
+
run_dirs = [str(run_dir).split('/')[-1]
|
|
266
|
+
for run_dir in self.html_directory.iterdir()
|
|
267
|
+
if "cli_gui" in str(run_dir)]
|
|
268
|
+
run_dirs.sort(reverse=True)
|
|
269
|
+
|
|
270
|
+
last_run_dir = Path(run_dirs[0]) if len(run_dirs) > 0 else None
|
|
271
|
+
if last_run_dir and not last_run_dir.joinpath('done').exists():
|
|
272
|
+
# Use the old run directory
|
|
273
|
+
logger.debug("Using HTML logging file from last time.")
|
|
274
|
+
self.html_directory = self.html_directory.joinpath(
|
|
275
|
+
last_run_dir)
|
|
276
|
+
else:
|
|
277
|
+
# Create new run directory
|
|
278
|
+
logger.debug("Creating new HTML logging file.")
|
|
279
|
+
self.html_directory = self.html_directory.joinpath(
|
|
280
|
+
str(int(time())) + '_cli_gui')
|
|
253
281
|
else:
|
|
254
282
|
calling_func = inspect.stack()[1][3]
|
|
255
|
-
self.html_directory =
|
|
256
|
-
|
|
283
|
+
self.html_directory = self.html_directory.joinpath(
|
|
284
|
+
str(int(time())) + '_' + calling_func)
|
|
257
285
|
|
|
258
|
-
self.screenshot_directory = self.html_directory
|
|
286
|
+
self.screenshot_directory = self.html_directory.joinpath("screenshots")
|
|
259
287
|
# will create both dirs
|
|
260
|
-
|
|
288
|
+
self.screenshot_directory.mkdir(parents=True, exist_ok=True)
|
|
261
289
|
|
|
262
|
-
self.html_file =
|
|
263
|
-
|
|
264
|
-
fp
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
290
|
+
self.html_file = self.html_directory.joinpath("index.html")
|
|
291
|
+
if not self.html_file.exists():
|
|
292
|
+
with open(self.html_file, 'w') as fp:
|
|
293
|
+
fp.write(
|
|
294
|
+
"<html lang=\"en\">\n"
|
|
295
|
+
"<head>\n"
|
|
296
|
+
"<meta charset=\"UTF-8\">\n"
|
|
297
|
+
"<meta name=\"viewport\" content=\"width=device-width, "
|
|
298
|
+
"initial-scale=1.0\">\n"
|
|
299
|
+
"<title>Test Results</title>\n"
|
|
300
|
+
"</head>\n"
|
|
301
|
+
"<body style=\"background-color:#000;\">\n"
|
|
302
|
+
)
|
|
274
303
|
|
|
275
304
|
fmt = "<span style=\"color:limegreen;\">"
|
|
276
305
|
fmt += "%(asctime)s</span> "
|
|
@@ -284,25 +313,36 @@ class GUI:
|
|
|
284
313
|
logging.Formatter("<p>" + fmt + "</p>")
|
|
285
314
|
)
|
|
286
315
|
|
|
316
|
+
if self.from_cli:
|
|
317
|
+
logger.addHandler(self.fileHandler)
|
|
318
|
+
|
|
287
319
|
self.mouse = Mouse()
|
|
288
320
|
|
|
289
321
|
# workaround for keyboard library
|
|
290
322
|
# otherwise the first character is not sent
|
|
291
323
|
keyboard.send('enter')
|
|
292
324
|
|
|
293
|
-
|
|
325
|
+
# create screen object to use from calls
|
|
294
326
|
self.screen = Screen(self.screenshot_directory, self.html_file)
|
|
327
|
+
|
|
328
|
+
def __enter__(self):
|
|
295
329
|
# By restarting gdm, the system gets into defined state
|
|
296
330
|
run(['systemctl', 'restart', 'gdm'], check=True)
|
|
297
331
|
# Cannot screenshot before gdm starts displaying
|
|
298
332
|
# This would break the display
|
|
299
333
|
sleep(self.gdm_init_time)
|
|
300
334
|
|
|
301
|
-
|
|
335
|
+
if not self.from_cli:
|
|
336
|
+
logger.addHandler(self.fileHandler)
|
|
302
337
|
|
|
303
338
|
return self
|
|
304
339
|
|
|
305
340
|
def __exit__(self, type, value, traceback):
|
|
341
|
+
done_file = self.html_directory.joinpath('done')
|
|
342
|
+
print(done_file)
|
|
343
|
+
if done_file.exists():
|
|
344
|
+
return
|
|
345
|
+
|
|
306
346
|
run(['systemctl', 'stop', 'gdm'], check=True)
|
|
307
347
|
|
|
308
348
|
with open(self.html_file, 'a') as fp:
|
|
@@ -311,7 +351,13 @@ class GUI:
|
|
|
311
351
|
"</html>\n"
|
|
312
352
|
)
|
|
313
353
|
|
|
314
|
-
|
|
354
|
+
print(done_file)
|
|
355
|
+
with open(done_file, 'w') as fp:
|
|
356
|
+
fp.write("done")
|
|
357
|
+
|
|
358
|
+
if not self.from_cli:
|
|
359
|
+
logger.removeHandler(self.fileHandler)
|
|
360
|
+
|
|
315
361
|
logger.info(f"HTML file with results created in {self.html_directory}.")
|
|
316
362
|
|
|
317
363
|
@action_decorator
|
|
@@ -377,7 +423,18 @@ class GUI:
|
|
|
377
423
|
def kb_write(self, *args, **kwargs):
|
|
378
424
|
# delay is a workaround needed for keyboard library
|
|
379
425
|
kwargs.setdefault('delay', 0.1)
|
|
380
|
-
|
|
426
|
+
|
|
427
|
+
word = args[0]
|
|
428
|
+
last = ""
|
|
429
|
+
for char in word:
|
|
430
|
+
if char.isupper():
|
|
431
|
+
if last != "":
|
|
432
|
+
keyboard.write(*[last], **kwargs)
|
|
433
|
+
last = ""
|
|
434
|
+
keyboard.send(f"shift+{char.lower()}")
|
|
435
|
+
else:
|
|
436
|
+
last = f"{last}{char}"
|
|
437
|
+
keyboard.write(*[last], **kwargs)
|
|
381
438
|
|
|
382
439
|
@action_decorator
|
|
383
440
|
@log_decorator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: SCAutolib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: Python library for automation tests of smart cards using virtualization.
|
|
5
5
|
Home-page: https://github.com/redhat-qe-security/SCAutolib
|
|
6
6
|
Author: Pavel Yadlouski
|
|
@@ -32,6 +32,16 @@ Requires-Dist: pandas; extra == "graphical"
|
|
|
32
32
|
Requires-Dist: numpy; extra == "graphical"
|
|
33
33
|
Requires-Dist: pytesseract; extra == "graphical"
|
|
34
34
|
Requires-Dist: keyboard; extra == "graphical"
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: author-email
|
|
37
|
+
Dynamic: classifier
|
|
38
|
+
Dynamic: description
|
|
39
|
+
Dynamic: description-content-type
|
|
40
|
+
Dynamic: home-page
|
|
41
|
+
Dynamic: provides-extra
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
Dynamic: summary
|
|
35
45
|
|
|
36
46
|
# Smart Card Automation library (SCAutolib)
|
|
37
47
|
Test automation library for Smart Cards.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|