Design of SCAutolib
This section describes design decisions that were made during implementation with their explanations.
Library is designed to be compatible with Red Hat testing environment
Tests in this environment are split to several phases: setup phase, test phase, and cleanup phase. From this perspective, the library provides setup and cleanup procedures. We have a setup, then multiple tests can be run with this setup, and finally we have a cleanup.
SCAutolib uses MVC pattern adopted for CLI and test usage.
SCAutolib is built with a flexible, layered architecture designed to make smart card automation intuitive and robust. At its heart, we’ve adopted an MVC (Model-View-Controller) pattern, adapting it to suit the unique needs of a Python library used for both direct command-line interaction and extensive test automation. This design approach aims to keep the codebase organized, reusable, and easy for new developers to understand and contribute to.
While our architecture is continually evolving to introduce new features and enhance flexibility, the core MVC principles currently guide our design:
Model: The Smart Card’s Brains
Models are all basic classes in the library (CAs, Users, Card, etc.) that provides low-level atomic functionality
View: Your Command Center
View is a Command-Line Interface (CLI) interface for manual interaction with the library
Controller: The Orchestrator
Controller is a heart of the library that handles logic behind both variants of the library usage: via View (aka CLI) and from imported code
All library-related directories and files (cards/users directory, library config files, backups) are stored in
/etc/SCAutolibReason for this is to simplify file management and cleanup phase after all tests are executed.
SCAutolib in Depth
This section describes the library in more detail. In other words, this section contains our approach for adopting MVC pattern.
Models
These are the foundational classes in the library (CAs, Users, Card, etc.) that give you low-level, atomic control over the smart card setup and operations. Think of them as the smart card’s “brains” providing raw functionality.
Note
If some new moving part is added to the library (e.g. removinator), it should be added to the models.
Each model calls only its own methods and does not call methods of any other models, except for model’s properties. In other words, models are not aware of other models, but only of their properties. This is made to clarify the implementation approach of the library: everything that model requires for it’s functionality is provided by the Controller.
For example, if you want to create a new LocalCA, you need to create a new CNF
file for this CA before calling the setup method on LocalCA object
ca_root_dir = "/etc/SCAutolib/ca"
cnf_file = SCAutolib.models.file.OpensslCnf("/tmp/ca.cnf", "CA", ca_root_dir)
cnf_file.create()
cnf_file.save()
local_ca = SCAutolib.models.ca.CA.LocalCA(root_dir=ca_root_dir, cnf=cnf_file)
local_ca.setup()
This implementation would help to avoid situation with circular dependencies and vague call of some method on unexpected place (model).
Some models have overwritten __dict__ property so they can be serialized
to JSON format and stored in the file.
Overwriting __dict__ is required because not every attribute of the model
is JSON serializable (e.g. pathlib.Path object).
After serialization, the model can be restored (loaded) via corresponding
load method.
Example:
class BaseUser:
@staticmethod
def load(json_file, **kwargs):
with json_file.open("r") as f:
cnt = json.load(f)
if cnt["local"]:
user = User(local=cnt["local"],
username=cnt["username"],
password=cnt["password"], ...)
else:
# IPAUser is loaded similarly to LocalUser
...
return user
class User(BaseUser):
...
@property
def __dict__(self):
dict_ = super().__dict__.copy()
for k, v in dict_.items():
if type(v) in (PosixPath, Path):
dict_[k] = str(v)
if self._card:
dict_["_card"] = str(self._card.dump_file)
return dict_
Example of usage:
user = User(...)
with user.dump_file("w") as f:
json.dump(user.__dict__, f)
loaded_user = BaseUser.load(user.dump_file)
dump_file is an attribute of the model that defines path to the file where
the model is serialized.
Controller
The Controller within SCAutolib serves as the essential “glue” connecting the low-level atomic functionalities of the Models with the user’s requests, whether originating from the View (CLI) or programmatic automation. It orchestrates the high-level logic, ensuring methods are called in the correct sequence and managing the creation of necessary files and objects. While the Controller’s methods are designed to be invoked by the CLI or other system setup scripts, they are intentionally not the primary interface for direct test usage. This is to avoid unnecessary complexity for testing, as tests typically require more granular control. Instead, for testing purposes, the Controller facilitates the dumping of relevant Model states to JSON files upon method completion. These JSON representations can then be loaded via test fixtures, allowing test automation to directly interact with pre-configured Model instances, craft specific smart card operations, execute them, and assert against the outcomes with greater precision and efficiency. This flexible Controller role enables both manual CLI control and robust, fine-grained automated testing.
View
The View component in SCAutolib serves as a direct interface for interacting
with the library’s capabilities. Primarily, this is our
Command-Line Interface (CLI), encapsulated within cli_commands.py. Its
core purpose is to provide an accessible API for users to leverage SCAutolib’s
functionalities directly from their command line. This design is particularly
beneficial for those looking to integrate smart card operations into automation
scripts, like Bash, enabling quick execution and validation without needing to
write full Python programs. The CLI is intentionally designed to be lean,
focusing purely on capturing user input and presenting output. It avoids
embedding complex smart card logic, ensuring a clear separation of concerns
where the underlying “brains” of the smart card operations reside within the
Model layer, leaving the View to simply handle user interaction.