SCAutolib 3.3.3__tar.gz → 3.4.0__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.
- {scautolib-3.3.3/SCAutolib.egg-info → scautolib-3.4.0}/PKG-INFO +12 -2
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/__init__.py +4 -2
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/cli_commands.py +182 -35
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/controller.py +52 -35
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/file.py +6 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/gui.py +89 -50
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/utils.py +1 -0
- {scautolib-3.3.3 → scautolib-3.4.0/SCAutolib.egg-info}/PKG-INFO +12 -2
- {scautolib-3.3.3 → scautolib-3.4.0}/setup.py +1 -1
- {scautolib-3.3.3 → scautolib-3.4.0}/LICENSE +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/MANIFEST.in +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/README.md +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/enums.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/exceptions.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/isDistro.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/CA.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/__init__.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/authselect.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/card.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/log.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/models/user.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/ca.cnf +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/gnome_disable_welcome +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/softhsm2.conf +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/sssd.conf-10 +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/sssd.conf-8or9 +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/user.cnf +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/virt_cacard.service +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib/templates/virtcacard.cil +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib.egg-info/SOURCES.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib.egg-info/dependency_links.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib.egg-info/entry_points.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib.egg-info/requires.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/SCAutolib.egg-info/top_level.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/requirements.txt +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/setup.cfg +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_ca.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_card.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_cli.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_controller.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_file.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_openssl_conf.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_sssd_conf.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_user.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/test/test_utils.py +0 -0
- {scautolib-3.3.3 → scautolib-3.4.0}/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.0
|
|
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.
|
|
@@ -7,10 +7,12 @@ from schema import Schema, Use, Or, And, Optional
|
|
|
7
7
|
|
|
8
8
|
from SCAutolib.enums import CardType, UserType
|
|
9
9
|
|
|
10
|
-
fmt = "%(name)s:%(module)s.%(funcName)s.%(lineno)d
|
|
10
|
+
fmt = ("%(asctime)s %(name)s:%(module)s.%(funcName)s.%(lineno)d "
|
|
11
|
+
"[%(levelname)s] %(message)s")
|
|
11
12
|
date_fmt = "%H:%M:%S"
|
|
12
13
|
coloredlogs.install(level="DEBUG", fmt=fmt, datefmt=date_fmt,
|
|
13
|
-
field_styles={'levelname': {'bold': True, 'color': 'blue'}
|
|
14
|
+
field_styles={'levelname': {'bold': True, 'color': 'blue'},
|
|
15
|
+
'asctime': {'color': 'green'}})
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
# Disable logs from imported packages
|
|
16
18
|
logging.getLogger("paramiko").setLevel(logging.WARNING)
|
|
@@ -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
|
|
@@ -170,10 +167,6 @@ class Controller:
|
|
|
170
167
|
for u in self.lib_conf["users"]]):
|
|
171
168
|
packages += self._general_steps_for_ipa()
|
|
172
169
|
|
|
173
|
-
# In RHEL-10 we need one extra policy for the pcsc-lite to work
|
|
174
|
-
if isDistro(['rhel', 'centos'], version='10'):
|
|
175
|
-
packages += ["sssd-polkit-rules"]
|
|
176
|
-
|
|
177
170
|
# Check for installed packages
|
|
178
171
|
missing = _check_packages(packages)
|
|
179
172
|
if install_missing and missing:
|
|
@@ -185,21 +178,7 @@ class Controller:
|
|
|
185
178
|
raise exceptions.SCAutolibException(msg)
|
|
186
179
|
|
|
187
180
|
if graphical:
|
|
188
|
-
|
|
189
|
-
run(['dnf', 'groupinstall', 'Server with GUI', '-y',
|
|
190
|
-
'--allowerasing'])
|
|
191
|
-
run(['pip', 'install', 'python-uinput'])
|
|
192
|
-
else:
|
|
193
|
-
# Fedora doesn't have server with GUI group so installed gdm
|
|
194
|
-
# manually and also python3-uinput should be installed from RPM
|
|
195
|
-
run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
|
|
196
|
-
# disable subscription message
|
|
197
|
-
run(['systemctl', '--global', 'mask',
|
|
198
|
-
'org.gnome.SettingsDaemon.Subscription.target'])
|
|
199
|
-
# disable welcome message
|
|
200
|
-
self.dconf_file.create()
|
|
201
|
-
self.dconf_file.save()
|
|
202
|
-
run('dconf update')
|
|
181
|
+
self.setup_graphical(install_missing, gdm)
|
|
203
182
|
|
|
204
183
|
if not isDistro('fedora'):
|
|
205
184
|
run(['dnf', 'groupinstall', "Smart Card Support", '-y',
|
|
@@ -220,6 +199,38 @@ class Controller:
|
|
|
220
199
|
dump_to_json(user.User(username="root",
|
|
221
200
|
password=self.lib_conf["root_passwd"]))
|
|
222
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
|
+
|
|
223
234
|
def setup_local_ca(self, force: bool = False):
|
|
224
235
|
"""
|
|
225
236
|
Setup local CA based on configuration from the configuration file. All
|
|
@@ -453,6 +464,12 @@ class Controller:
|
|
|
453
464
|
cache_file.unlink()
|
|
454
465
|
logger.debug("Removed opensc file cache")
|
|
455
466
|
|
|
467
|
+
sssd_cache_dir = Path(os.path.expanduser('~sssd') + "/.cache/opensc/")
|
|
468
|
+
if sssd_cache_dir.exists():
|
|
469
|
+
for cache_file in sssd_cache_dir.iterdir():
|
|
470
|
+
cache_file.unlink()
|
|
471
|
+
logger.debug("Removed opensc file cache for sssd user")
|
|
472
|
+
|
|
456
473
|
# file only created in graphical mode that is why it is removed.
|
|
457
474
|
self.dconf_file.remove()
|
|
458
475
|
|
|
@@ -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,8 +1,6 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import inspect
|
|
3
|
-
import os
|
|
4
|
-
from os.path import join
|
|
5
2
|
from time import sleep, time
|
|
3
|
+
from pathlib import Path
|
|
6
4
|
|
|
7
5
|
import cv2
|
|
8
6
|
import keyboard
|
|
@@ -15,28 +13,6 @@ from SCAutolib import run, logger
|
|
|
15
13
|
from SCAutolib.isDistro import isDistro
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
class HTMLFileHandler(logging.FileHandler):
|
|
19
|
-
"""Extending FileHandler to work with HTML files."""
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self, filename, mode='a', encoding=None, delay=False, errors=None):
|
|
23
|
-
"""
|
|
24
|
-
A handler class which writes formatted logging records to disk HTML
|
|
25
|
-
files.
|
|
26
|
-
"""
|
|
27
|
-
super(HTMLFileHandler, self).__init__(
|
|
28
|
-
filename, mode, encoding, delay, errors
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
def emit(self, record: logging.LogRecord) -> None:
|
|
32
|
-
"""
|
|
33
|
-
Do whatever it takes to actually log the specified logging record.
|
|
34
|
-
"""
|
|
35
|
-
custom_record = copy.deepcopy(record)
|
|
36
|
-
custom_record.msg = str(record.msg).rstrip().replace("\n", "<br>\n")
|
|
37
|
-
return super().emit(custom_record)
|
|
38
|
-
|
|
39
|
-
|
|
40
16
|
class Screen:
|
|
41
17
|
"""Captures the screenshots."""
|
|
42
18
|
|
|
@@ -47,7 +23,14 @@ class Screen:
|
|
|
47
23
|
"""
|
|
48
24
|
self.directory = directory
|
|
49
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
|
+
|
|
50
31
|
self.screenshot_num = 1
|
|
32
|
+
if len(taken_images) > 0:
|
|
33
|
+
self.screenshot_num = int(taken_images[0].split('.')[0]) + 1
|
|
51
34
|
|
|
52
35
|
def screenshot(self, timeout: float = 30):
|
|
53
36
|
"""Runs ffmpeg to take a screenshot.
|
|
@@ -74,6 +57,9 @@ class Screen:
|
|
|
74
57
|
|
|
75
58
|
if out.returncode == 0:
|
|
76
59
|
captured = True
|
|
60
|
+
else:
|
|
61
|
+
logger.debug(f"ffmpeg failed with {out.returncode}. "
|
|
62
|
+
f"stdout: {out.stdout}, stderr: {out.stderr}")
|
|
77
63
|
|
|
78
64
|
if not captured:
|
|
79
65
|
raise Exception('Could not capture screenshot within timeout.')
|
|
@@ -257,7 +243,8 @@ def log_decorator(func):
|
|
|
257
243
|
class GUI:
|
|
258
244
|
"""Represents the GUI and allows controlling the system under test."""
|
|
259
245
|
|
|
260
|
-
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):
|
|
261
248
|
"""Initializes the GUI of system under test.
|
|
262
249
|
|
|
263
250
|
:param wait_time: Time to wait after each action
|
|
@@ -267,60 +254,95 @@ class GUI:
|
|
|
267
254
|
|
|
268
255
|
self.wait_time = wait_time
|
|
269
256
|
self.gdm_init_time = 10
|
|
257
|
+
self.from_cli = from_cli
|
|
270
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()
|
|
271
262
|
if res_dir_name:
|
|
272
|
-
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')
|
|
273
281
|
else:
|
|
274
282
|
calling_func = inspect.stack()[1][3]
|
|
275
|
-
self.html_directory =
|
|
276
|
-
|
|
283
|
+
self.html_directory = self.html_directory.joinpath(
|
|
284
|
+
str(int(time())) + '_' + calling_func)
|
|
277
285
|
|
|
278
|
-
self.screenshot_directory = self.html_directory
|
|
286
|
+
self.screenshot_directory = self.html_directory.joinpath("screenshots")
|
|
279
287
|
# will create both dirs
|
|
280
|
-
|
|
288
|
+
self.screenshot_directory.mkdir(parents=True, exist_ok=True)
|
|
281
289
|
|
|
282
|
-
self.html_file =
|
|
283
|
-
|
|
284
|
-
fp
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
)
|
|
294
303
|
|
|
295
|
-
fmt = "<span style=\"color:
|
|
304
|
+
fmt = "<span style=\"color:limegreen;\">"
|
|
305
|
+
fmt += "%(asctime)s</span> "
|
|
306
|
+
fmt += "<span style=\"color:white;\">"
|
|
296
307
|
fmt += "%(name)s:%(module)s.%(funcName)s.%(lineno)d </span>"
|
|
297
308
|
fmt += "<span style=\"color:royalblue;\">[%(levelname)s] </span>"
|
|
298
|
-
fmt += "<
|
|
299
|
-
self.fileHandler =
|
|
309
|
+
fmt += "<pre style=\"color:limegreen;\">%(message)s</pre>"
|
|
310
|
+
self.fileHandler = logging.FileHandler(self.html_file)
|
|
300
311
|
self.fileHandler.setLevel(logging.DEBUG)
|
|
301
312
|
self.fileHandler.setFormatter(
|
|
302
313
|
logging.Formatter("<p>" + fmt + "</p>")
|
|
303
314
|
)
|
|
304
315
|
|
|
316
|
+
if self.from_cli:
|
|
317
|
+
logger.addHandler(self.fileHandler)
|
|
318
|
+
|
|
305
319
|
self.mouse = Mouse()
|
|
306
320
|
|
|
307
321
|
# workaround for keyboard library
|
|
308
322
|
# otherwise the first character is not sent
|
|
309
323
|
keyboard.send('enter')
|
|
310
324
|
|
|
311
|
-
|
|
325
|
+
# create screen object to use from calls
|
|
312
326
|
self.screen = Screen(self.screenshot_directory, self.html_file)
|
|
327
|
+
|
|
328
|
+
def __enter__(self):
|
|
313
329
|
# By restarting gdm, the system gets into defined state
|
|
314
330
|
run(['systemctl', 'restart', 'gdm'], check=True)
|
|
315
331
|
# Cannot screenshot before gdm starts displaying
|
|
316
332
|
# This would break the display
|
|
317
333
|
sleep(self.gdm_init_time)
|
|
318
334
|
|
|
319
|
-
|
|
335
|
+
if not self.from_cli:
|
|
336
|
+
logger.addHandler(self.fileHandler)
|
|
320
337
|
|
|
321
338
|
return self
|
|
322
339
|
|
|
323
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
|
+
|
|
324
346
|
run(['systemctl', 'stop', 'gdm'], check=True)
|
|
325
347
|
|
|
326
348
|
with open(self.html_file, 'a') as fp:
|
|
@@ -329,7 +351,13 @@ class GUI:
|
|
|
329
351
|
"</html>\n"
|
|
330
352
|
)
|
|
331
353
|
|
|
332
|
-
|
|
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
|
+
|
|
333
361
|
logger.info(f"HTML file with results created in {self.html_directory}.")
|
|
334
362
|
|
|
335
363
|
@action_decorator
|
|
@@ -395,7 +423,18 @@ class GUI:
|
|
|
395
423
|
def kb_write(self, *args, **kwargs):
|
|
396
424
|
# delay is a workaround needed for keyboard library
|
|
397
425
|
kwargs.setdefault('delay', 0.1)
|
|
398
|
-
|
|
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)
|
|
399
438
|
|
|
400
439
|
@action_decorator
|
|
401
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.0
|
|
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
|