From 45a54e0987a8ad622403eacfa2eebe5982ea6627 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 9 Apr 2024 15:12:05 +0200 Subject: [PATCH] Remove schema facility and most of formats The usage of the securesystemslib schema/formats facility has been little by little phased out and is now nowhere used anymore. Several reasons to not use it are listed in #183. This patch removes schema.py along with all securesystemslib schema definitions in formats.py. Additionally, it removes unrelated and unused helper functions in formats.py. Signed-off-by: Lukas Puehringer --- securesystemslib/formats.py | 586 +-------------------- securesystemslib/schema.py | 990 ------------------------------------ tests/test_formats.py | 333 ------------ tests/test_schema.py | 509 ------------------ 4 files changed, 1 insertion(+), 2417 deletions(-) delete mode 100755 securesystemslib/schema.py delete mode 100755 tests/test_schema.py diff --git a/securesystemslib/formats.py b/securesystemslib/formats.py index 0d131b2f..ca14a04a 100755 --- a/securesystemslib/formats.py +++ b/securesystemslib/formats.py @@ -16,595 +16,11 @@ See LICENSE for licensing information. - A central location for all format-related checking of securesystemslib - objects. Note: 'formats.py' depends heavily on 'schema.py', so the - 'schema.py' module should be read and understood before tackling this module. + Implements canonical json (OLPC) encoder. - 'formats.py' can be broken down into three sections. (1) Schemas and object - matching. (2) Classes that represent Role Metadata and help produce - correctly formatted files. (3) Functions that help produce or verify - securesystemslib objects. - - The first section deals with schemas and object matching based on format. - There are two ways of checking the format of objects. The first method - raises a 'securesystemslib.exceptions.FormatError' exception if the match - fails and the other returns a Boolean result. - - securesystemslib.formats..check_match(object) - securesystemslib.formats..matches(object) - - Example: - - rsa_key = {'keytype': 'rsa' - 'keyid': 34892fc465ac76bc3232fab - 'keyval': {'public': 'public_key', - 'private': 'private_key'} - - securesystemslib.formats.RSAKEY_SCHEMA.check_match(rsa_key) - securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) - - In this example, if a dict key or dict value is missing or incorrect, - the match fails. There are numerous variations of object checking - provided by 'formats.py' and 'schema.py'. - - The second section deals with the role metadata classes. There are - multiple top-level roles, each with differing metadata formats. - Example: - - root_object = securesystemslib.formats.RootFile.from_metadata(root_metadata_file) - targets_metadata = securesystemslib.formats.TargetsFile.make_metadata(...) - - The input and output of these classes are checked against their respective - schema to ensure correctly formatted metadata. - - The last section contains miscellaneous functions related to the format of - securesystemslib objects. - Example: - - signable_object = make_signable(unsigned_object) """ -import binascii -import calendar -import datetime -import time - from securesystemslib import exceptions -from securesystemslib import schema as SCHEMA - -# Note that in the schema definitions below, the 'SCHEMA.Object' types allow -# additional keys which are not defined. Thus, any additions to them will be -# easily backwards compatible with clients that are already deployed. - -ANY_STRING_SCHEMA = SCHEMA.AnyString() -LIST_OF_ANY_STRING_SCHEMA = SCHEMA.ListOf(ANY_STRING_SCHEMA) - -# A datetime in 'YYYY-MM-DDTHH:MM:SSZ' ISO 8601 format. The "Z" zone designator -# for the zero UTC offset is always used (i.e., a numerical offset is not -# supported.) Example: '2015-10-21T13:20:00Z'. Note: This is a simple format -# check, and an ISO8601 string should be fully verified when it is parsed. -ISO8601_DATETIME_SCHEMA = SCHEMA.RegularExpression( - r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z" -) - -# A Unix/POSIX time format. An integer representing the number of seconds -# since the epoch (January 1, 1970.) Metadata uses this format for the -# 'expires' field. Set 'hi' to the upper timestamp limit (year 2038), the max -# value of an int. -UNIX_TIMESTAMP_SCHEMA = SCHEMA.Integer(lo=0, hi=2147483647) - -# A hexadecimal value in '23432df87ab..' format. -HEX_SCHEMA = SCHEMA.RegularExpression(r"[a-fA-F0-9]+") - -HASH_SCHEMA = HEX_SCHEMA - -# A dict in {'sha256': '23432df87ab..', 'sha512': '34324abc34df..', ...} format. -HASHDICT_SCHEMA = SCHEMA.DictOf( - key_schema=SCHEMA.AnyString(), value_schema=HASH_SCHEMA -) - -# Uniform Resource Locator identifier (e.g., 'https://www.updateframework.com/'). -# TODO: Some level of restriction here would be good.... Note that I pulled -# this from securesystemslib, since it's neither sophisticated nor used -# by anyone else. -URL_SCHEMA = SCHEMA.AnyString() - -# A key identifier (e.g., a hexadecimal value identifying an RSA key). -KEYID_SCHEMA = HASH_SCHEMA - -# A list of KEYID_SCHEMA. -KEYIDS_SCHEMA = SCHEMA.ListOf(KEYID_SCHEMA) - -# The signing scheme used by a key to generate a signature (e.g., -# 'rsassa-pss-sha256' is one of the signing schemes for key type 'rsa'). -SCHEME_SCHEMA = SCHEMA.AnyString() - -# A path string, whether relative or absolute, e.g. 'metadata/root/' -PATH_SCHEMA = SCHEMA.AnyNonemptyString() -PATHS_SCHEMA = SCHEMA.ListOf(PATH_SCHEMA) - -# An integer representing logger levels, such as logging.CRITICAL (=50). -# Must be between 0 and 50. -LOGLEVEL_SCHEMA = SCHEMA.Integer(lo=0, hi=50) - -# A string representing a named object. -NAME_SCHEMA = SCHEMA.AnyString() -NAMES_SCHEMA = SCHEMA.ListOf(NAME_SCHEMA) - -# A byte string representing data. -DATA_SCHEMA = SCHEMA.AnyBytes() - -# A text string. For instance, a string entered by the user. -TEXT_SCHEMA = SCHEMA.AnyString() - -# Supported hash algorithms. -HASHALGORITHMS_SCHEMA = SCHEMA.ListOf( - SCHEMA.OneOf( - [ - SCHEMA.String("sha224"), - SCHEMA.String("sha256"), - SCHEMA.String("sha384"), - SCHEMA.String("sha512"), - SCHEMA.String("blake2s"), - SCHEMA.String("blake2b"), - SCHEMA.String("blake2b-256"), - ] - ) -) - -# The contents of an encrypted key. Encrypted keys are saved to files -# in this format. -ENCRYPTEDKEY_SCHEMA = SCHEMA.AnyString() - -# A value that is either True or False, on or off, etc. -BOOLEAN_SCHEMA = SCHEMA.Boolean() - -# The minimum number of bits for an RSA key. Must be 2048 bits, or greater -# (recommended by TUF). Recommended RSA key sizes: -# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 -RSAKEYBITS_SCHEMA = SCHEMA.Integer(lo=2048) - -# The supported ECDSA signature schemes -ECDSA_SCHEME_SCHEMA = SCHEMA.RegularExpression(r"ecdsa-sha2-nistp(256|384)") - -# A pyca-cryptography signature. -PYCACRYPTOSIGNATURE_SCHEMA = SCHEMA.AnyBytes() - -# An RSA key in PEM format. -PEMRSA_SCHEMA = SCHEMA.AnyString() - -# An ECDSA key in PEM format. -PEMECDSA_SCHEMA = SCHEMA.AnyString() - -# A string representing a password. -PASSWORD_SCHEMA = SCHEMA.AnyString() - -# A list of passwords. -PASSWORDS_SCHEMA = SCHEMA.ListOf(PASSWORD_SCHEMA) - -# The actual values of a key, as opposed to meta data such as a key type and -# key identifier ('rsa', 233df889cb). For RSA keys, the key value is a pair of -# public and private keys in PEM Format stored as strings. -KEYVAL_SCHEMA = SCHEMA.Object( - object_name="KEYVAL_SCHEMA", - public=SCHEMA.AnyString(), - private=SCHEMA.Optional(SCHEMA.AnyString()), -) - -# Public keys CAN have a private portion (for backwards compatibility) which -# MUST be an empty string -PUBLIC_KEYVAL_SCHEMA = SCHEMA.Object( - object_name="KEYVAL_SCHEMA", - public=SCHEMA.AnyString(), - private=SCHEMA.Optional(SCHEMA.String("")), -) - -# Supported securesystemslib key types. -KEYTYPE_SCHEMA = SCHEMA.OneOf( - [ - SCHEMA.String("rsa"), - SCHEMA.String("ed25519"), - SCHEMA.String("ecdsa"), - SCHEMA.RegularExpression(r"ecdsa-sha2-nistp(256|384)"), - ] -) - -# A generic securesystemslib key. All securesystemslib keys should be saved to -# metadata files in this format. -KEY_SCHEMA = SCHEMA.Object( - object_name="KEY_SCHEMA", - keytype=SCHEMA.AnyString(), - scheme=SCHEME_SCHEMA, - keyval=KEYVAL_SCHEMA, - expires=SCHEMA.Optional(ISO8601_DATETIME_SCHEMA), -) - -# Like KEY_SCHEMA, but requires keyval's private portion to be unset or empty, -# and optionally includes the supported keyid hash algorithms used to generate -# the key's keyid. -PUBLIC_KEY_SCHEMA = SCHEMA.Object( - object_name="PUBLIC_KEY_SCHEMA", - keytype=SCHEMA.AnyString(), - keyid_hash_algorithms=SCHEMA.Optional(HASHALGORITHMS_SCHEMA), - keyval=PUBLIC_KEYVAL_SCHEMA, - expires=SCHEMA.Optional(ISO8601_DATETIME_SCHEMA), -) - -# A securesystemslib key object. This schema simplifies validation of keys -# that may be one of the supported key types. Supported key types: 'rsa', -# 'ed25519'. -ANYKEY_SCHEMA = SCHEMA.Object( - object_name="ANYKEY_SCHEMA", - keytype=KEYTYPE_SCHEMA, - scheme=SCHEME_SCHEMA, - keyid=KEYID_SCHEMA, - keyid_hash_algorithms=SCHEMA.Optional(HASHALGORITHMS_SCHEMA), - keyval=KEYVAL_SCHEMA, - expires=SCHEMA.Optional(ISO8601_DATETIME_SCHEMA), -) - -# A list of securesystemslib key objects. -ANYKEYLIST_SCHEMA = SCHEMA.ListOf(ANYKEY_SCHEMA) - -# RSA signature schemes. -RSA_SCHEME_SCHEMA = SCHEMA.OneOf( - [ - SCHEMA.RegularExpression(r"rsassa-pss-(sha224|sha256|sha384|sha512)"), - SCHEMA.RegularExpression(r"rsa-pkcs1v15-(sha224|sha256|sha384|sha512)"), - ] -) - -# An RSA securesystemslib key. -RSAKEY_SCHEMA = SCHEMA.Object( - object_name="RSAKEY_SCHEMA", - keytype=SCHEMA.String("rsa"), - scheme=RSA_SCHEME_SCHEMA, - keyid=KEYID_SCHEMA, - keyid_hash_algorithms=SCHEMA.Optional(HASHALGORITHMS_SCHEMA), - keyval=KEYVAL_SCHEMA, -) - -# An ECDSA securesystemslib key. -ECDSAKEY_SCHEMA = SCHEMA.Object( - object_name="ECDSAKEY_SCHEMA", - keytype=SCHEMA.OneOf( - [ - SCHEMA.String("ecdsa"), - SCHEMA.RegularExpression(r"ecdsa-sha2-nistp(256|384)"), - ] - ), - scheme=ECDSA_SCHEME_SCHEMA, - keyid=KEYID_SCHEMA, - keyid_hash_algorithms=SCHEMA.Optional(HASHALGORITHMS_SCHEMA), - keyval=KEYVAL_SCHEMA, -) - -# An ED25519 raw public key, which must be 32 bytes. -ED25519PUBLIC_SCHEMA = SCHEMA.LengthBytes(32) - -# An ED25519 raw seed key, which must be 32 bytes. -ED25519SEED_SCHEMA = SCHEMA.LengthBytes(32) - -# An ED25519 raw signature, which must be 64 bytes. -ED25519SIGNATURE_SCHEMA = SCHEMA.LengthBytes(64) -# An ECDSA signature. -ECDSASIGNATURE_SCHEMA = SCHEMA.AnyBytes() - -# Ed25519 signature schemes. The vanilla Ed25519 signature scheme is currently -# supported. -ED25519_SIG_SCHEMA = SCHEMA.OneOf([SCHEMA.String("ed25519")]) - -# An ed25519 key. -ED25519KEY_SCHEMA = SCHEMA.Object( - object_name="ED25519KEY_SCHEMA", - keytype=SCHEMA.String("ed25519"), - scheme=ED25519_SIG_SCHEMA, - keyid=KEYID_SCHEMA, - keyid_hash_algorithms=SCHEMA.Optional(HASHALGORITHMS_SCHEMA), - keyval=KEYVAL_SCHEMA, -) - -# GPG key scheme definitions -GPG_HASH_ALGORITHM_STRING = "pgp+SHA2" -GPG_RSA_PUBKEY_METHOD_STRING = "pgp+rsa-pkcsv1.5" -GPG_DSA_PUBKEY_METHOD_STRING = "pgp+dsa-fips-180-2" -GPG_ED25519_PUBKEY_METHOD_STRING = "pgp+eddsa-ed25519" - - -def _create_gpg_pubkey_with_subkey_schema(pubkey_schema): - """Helper method to extend the passed public key schema with an optional - dictionary of sub public keys "subkeys" with the same schema.""" - schema = pubkey_schema - subkey_schema_tuple = ( - "subkeys", - SCHEMA.Optional( - SCHEMA.DictOf(key_schema=KEYID_SCHEMA, value_schema=pubkey_schema) - ), - ) - # Any subclass of `securesystemslib.schema.Object` stores the schemas that - # define the attributes of the object in its `_required` property, even if - # such a schema is of type `Optional`. - # TODO: Find a way that does not require to access a protected member - schema._required.append( # pylint: disable=protected-access - subkey_schema_tuple - ) # pylint: disable=protected-access - return schema - - -GPG_RSA_PUBKEYVAL_SCHEMA = SCHEMA.Object( - object_name="GPG_RSA_PUBKEYVAL_SCHEMA", e=SCHEMA.AnyString(), n=HEX_SCHEMA -) - -# We have to define GPG_RSA_PUBKEY_SCHEMA in two steps, because it is -# self-referential. Here we define a shallow _GPG_RSA_PUBKEY_SCHEMA, which we -# use below to create the self-referential GPG_RSA_PUBKEY_SCHEMA. -_GPG_RSA_PUBKEY_SCHEMA = SCHEMA.Object( - object_name="GPG_RSA_PUBKEY_SCHEMA", - type=SCHEMA.String("rsa"), - method=SCHEMA.String(GPG_RSA_PUBKEY_METHOD_STRING), - hashes=SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), - creation_time=SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), - validity_period=SCHEMA.Optional(SCHEMA.Integer(lo=0)), - keyid=KEYID_SCHEMA, - keyval=SCHEMA.Object( - public=GPG_RSA_PUBKEYVAL_SCHEMA, private=SCHEMA.String("") - ), -) -GPG_RSA_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( - _GPG_RSA_PUBKEY_SCHEMA -) - -GPG_DSA_PUBKEYVAL_SCHEMA = SCHEMA.Object( - object_name="GPG_DSA_PUBKEYVAL_SCHEMA", - y=HEX_SCHEMA, - p=HEX_SCHEMA, - q=HEX_SCHEMA, - g=HEX_SCHEMA, -) - -# C.f. comment above _GPG_RSA_PUBKEY_SCHEMA definition -_GPG_DSA_PUBKEY_SCHEMA = SCHEMA.Object( - object_name="GPG_DSA_PUBKEY_SCHEMA", - type=SCHEMA.String("dsa"), - method=SCHEMA.String(GPG_DSA_PUBKEY_METHOD_STRING), - hashes=SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), - creation_time=SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), - validity_period=SCHEMA.Optional(SCHEMA.Integer(lo=0)), - keyid=KEYID_SCHEMA, - keyval=SCHEMA.Object( - public=GPG_DSA_PUBKEYVAL_SCHEMA, private=SCHEMA.String("") - ), -) - -GPG_DSA_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( - _GPG_DSA_PUBKEY_SCHEMA -) - -GPG_ED25519_PUBKEYVAL_SCHEMA = SCHEMA.Object( - object_name="GPG_ED25519_PUBKEYVAL_SCHEMA", - q=HEX_SCHEMA, -) - -# C.f. comment above _GPG_RSA_PUBKEY_SCHEMA definition -_GPG_ED25519_PUBKEY_SCHEMA = SCHEMA.Object( - object_name="GPG_ED25519_PUBKEY_SCHEMA", - type=SCHEMA.String("eddsa"), - method=SCHEMA.String(GPG_ED25519_PUBKEY_METHOD_STRING), - hashes=SCHEMA.ListOf(SCHEMA.String(GPG_HASH_ALGORITHM_STRING)), - creation_time=SCHEMA.Optional(UNIX_TIMESTAMP_SCHEMA), - validity_period=SCHEMA.Optional(SCHEMA.Integer(lo=0)), - keyid=KEYID_SCHEMA, - keyval=SCHEMA.Object( - public=GPG_ED25519_PUBKEYVAL_SCHEMA, private=SCHEMA.String("") - ), -) -GPG_ED25519_PUBKEY_SCHEMA = _create_gpg_pubkey_with_subkey_schema( - _GPG_ED25519_PUBKEY_SCHEMA -) - -GPG_PUBKEY_SCHEMA = SCHEMA.OneOf( - [GPG_RSA_PUBKEY_SCHEMA, GPG_DSA_PUBKEY_SCHEMA, GPG_ED25519_PUBKEY_SCHEMA] -) - -GPG_SIGNATURE_SCHEMA = SCHEMA.Object( - object_name="SIGNATURE_SCHEMA", - keyid=KEYID_SCHEMA, - short_keyid=SCHEMA.Optional(KEYID_SCHEMA), - other_headers=HEX_SCHEMA, - signature=HEX_SCHEMA, - info=SCHEMA.Optional(SCHEMA.Any()), -) - -# A single signature of an object. Indicates the signature, and the KEYID of -# the signing key. I debated making the signature schema not contain the key -# ID and instead have the signatures of a file be a dictionary with the key -# being the keyid and the value being the signature schema without the keyid. -# That would be under the argument that a key should only be able to sign a -# file once. -SIGNATURE_SCHEMA = SCHEMA.Object( - object_name="SIGNATURE_SCHEMA", keyid=KEYID_SCHEMA, sig=HEX_SCHEMA -) - -# A dict where the dict keys hold a keyid and the dict values a key object. -KEYDICT_SCHEMA = SCHEMA.DictOf(key_schema=KEYID_SCHEMA, value_schema=KEY_SCHEMA) - -ANY_SIGNATURE_SCHEMA = SCHEMA.OneOf([SIGNATURE_SCHEMA, GPG_SIGNATURE_SCHEMA]) - -# List of ANY_SIGNATURE_SCHEMA. -SIGNATURES_SCHEMA = SCHEMA.ListOf(ANY_SIGNATURE_SCHEMA) - -# A signable object. Holds the signing role and its associated signatures. -SIGNABLE_SCHEMA = SCHEMA.Object( - object_name="SIGNABLE_SCHEMA", - signed=SCHEMA.Any(), - signatures=SIGNATURES_SCHEMA, -) - -# Note: Verification keys can have private portions but in case of GPG we -# only have a PUBKEY_SCHEMA (because we never export private gpg keys from -# the gpg keyring) -ANY_VERIFICATION_KEY_SCHEMA = SCHEMA.OneOf([ANYKEY_SCHEMA, GPG_PUBKEY_SCHEMA]) - -VERIFICATION_KEY_DICT_SCHEMA = SCHEMA.DictOf( - key_schema=KEYID_SCHEMA, value_schema=ANY_VERIFICATION_KEY_SCHEMA -) - -ANY_KEYDICT_SCHEMA = SCHEMA.OneOf( - [KEYDICT_SCHEMA, VERIFICATION_KEY_DICT_SCHEMA] -) - -ANY_PUBKEY_SCHEMA = SCHEMA.OneOf([PUBLIC_KEY_SCHEMA, GPG_PUBKEY_SCHEMA]) - -ANY_PUBKEY_DICT_SCHEMA = SCHEMA.DictOf( - key_schema=KEYID_SCHEMA, value_schema=ANY_PUBKEY_SCHEMA -) - - -def datetime_to_unix_timestamp(datetime_object): - """ - - Convert 'datetime_object' (in datetime.datetime()) format) to a Unix/POSIX - timestamp. For example, Python's time.time() returns a Unix timestamp, and - includes the number of microseconds. 'datetime_object' is converted to UTC. - - >>> datetime_object = datetime.datetime(1985, 10, 26, 1, 22) - >>> timestamp = datetime_to_unix_timestamp(datetime_object) - >>> timestamp - 499137720 - - - datetime_object: - The datetime.datetime() object to convert to a Unix timestamp. - - - securesystemslib.exceptions.FormatError, if 'datetime_object' is not a - datetime.datetime() object. - - - None. - - - A unix (posix) timestamp (e.g., 499137660). - """ - - # Is 'datetime_object' a datetime.datetime() object? - # Raise 'securesystemslib.exceptions.FormatError' if not. - if not isinstance(datetime_object, datetime.datetime): - message = ( - repr(datetime_object) + " is not a datetime.datetime() object." - ) - raise exceptions.FormatError(message) - - unix_timestamp = calendar.timegm(datetime_object.timetuple()) - - return unix_timestamp - - -def unix_timestamp_to_datetime(unix_timestamp): - """ - - Convert 'unix_timestamp' (i.e., POSIX time, in UNIX_TIMESTAMP_SCHEMA format) - to a datetime.datetime() object. 'unix_timestamp' is the number of seconds - since the epoch (January 1, 1970.) - - >>> datetime_object = unix_timestamp_to_datetime(1445455680) - >>> datetime_object - datetime.datetime(2015, 10, 21, 19, 28) - - - unix_timestamp: - An integer representing the time (e.g., 1445455680). Conformant to - 'securesystemslib.formats.UNIX_TIMESTAMP_SCHEMA'. - - - securesystemslib.exceptions.FormatError, if 'unix_timestamp' is improperly - formatted. - - - None. - - - A datetime.datetime() object corresponding to 'unix_timestamp'. - """ - - # Is 'unix_timestamp' properly formatted? - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. - UNIX_TIMESTAMP_SCHEMA.check_match(unix_timestamp) - - # Convert 'unix_timestamp' to a 'time.struct_time', in UTC. The Daylight - # Savings Time (DST) flag is set to zero. datetime.fromtimestamp() is not - # used because it returns a local datetime. - struct_time = time.gmtime(unix_timestamp) - - # Extract the (year, month, day, hour, minutes, seconds) arguments for the - # datetime object to be returned. - datetime_object = datetime.datetime(*struct_time[:6]) - - return datetime_object - - -def format_base64(data): - """ - - Return the base64 encoding of 'data' with whitespace and '=' signs omitted. - - - data: - Binary or buffer of data to convert. - - - securesystemslib.exceptions.FormatError, if the base64 encoding fails or the - argument is invalid. - - - None. - - - A base64-encoded string. - """ - - try: - return binascii.b2a_base64(data).decode("utf-8").rstrip("=\n ") - - except (TypeError, binascii.Error) as e: - raise exceptions.FormatError("Invalid base64" " encoding: " + str(e)) - - -def parse_base64(base64_string): - """ - - Parse a base64 encoding with whitespace and '=' signs omitted. - - - base64_string: - A string holding a base64 value. - - - securesystemslib.exceptions.FormatError, if 'base64_string' cannot be parsed - due to an invalid base64 encoding. - - - None. - - - A byte string representing the parsed based64 encoding of - 'base64_string'. - """ - - if not isinstance(base64_string, str): - message = "Invalid argument: " + repr(base64_string) - raise exceptions.FormatError(message) - - extra = len(base64_string) % 4 - if extra: - padding = "=" * (4 - extra) - base64_string = base64_string + padding - - try: - return binascii.a2b_base64(base64_string.encode("utf-8")) - - except (TypeError, binascii.Error) as e: - raise exceptions.FormatError("Invalid base64" " encoding: " + str(e)) def _canonical_string_encoder(string): diff --git a/securesystemslib/schema.py b/securesystemslib/schema.py deleted file mode 100755 index 520cfa91..00000000 --- a/securesystemslib/schema.py +++ /dev/null @@ -1,990 +0,0 @@ -""" - - schema.py - - - Geremy Condra - Vladimir Diaz - - - Refactored April 30, 2012 (previously named checkjson.py). -Vlad - - - 2008-2011 The Tor Project, Inc - 2012-2016 New York University and the TUF contributors - 2016-2021 Securesystemslib contributors - See LICENSE for licensing information. - - - Provide a variety of classes that compare objects - based on their format and determine if they match. - These classes, or schemas, do not simply check the - type of the objects being compared, but inspect - additional aspects of the objects like names and - the number of items included. - For example: - >>> good = {'first': 'Marty', 'last': 'McFly'} - >>> bad = {'sdfsfd': 'Biff', 'last': 'Tannen'} - >>> bad = {'sdfsfd': 'Biff', 'last': 'Tannen'} - >>> schema = Object(first=AnyString(), last=AnyString()) - >>> schema.matches(good) - True - >>> schema.matches(bad) - False - - In the process of determining if the two objects matched the template, - securesystemslib.schema.Object() inspected the named keys of both - dictionaries. In the case of the 'bad' dict, a 'first' dict key could not be - found. As a result, 'bad' was flagged a mismatch. - - 'schema.py' provides additional schemas for testing objects based on other - criteria. See 'securesystemslib.formats.py' and the rest of this module for - extensive examples. Anything related to the checking of securesystemslib - objects and their formats can be found in 'formats.py'. -""" - -import re -import sys - -from securesystemslib import exceptions - - -class Schema: - """ - - A schema matches a set of possible Python objects, of types - that are encodable in JSON. 'Schema' is the base class for - the other classes defined in this module. All derived classes - should implement check_match(). - """ - - def matches(self, object): # pylint: disable=redefined-builtin - """ - - Return True if 'object' matches this schema, False if it doesn't. - If the caller wishes to signal an error on a failed match, check_match() - should be called, which will raise a 'exceptions.FormatError' exception. - """ - - try: - self.check_match(object) - except exceptions.FormatError: - return False - - return True - - def check_match(self, object): # pylint: disable=redefined-builtin - """ - - Abstract method. Classes that inherit from 'Schema' must - implement check_match(). If 'object' matches the schema, check_match() - should simply return. If 'object' does not match the schema, - 'exceptions.FormatError' should be raised. - """ - - raise NotImplementedError() - - -class Any(Schema): - """ - - Matches any single object. Whereas other schemas explicitly state - the required type of its argument, Any() does not. It simply does a - 'pass' when 'check_match()' is called and at the point where the schema - is instantiated. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): passed - - - - >>> schema = Any() - >>> schema.matches('A String') - True - >>> schema.matches([1, 'list']) - True - """ - - def __init__(self): - pass - - def check_match(self, object): # pylint: disable=redefined-builtin - pass - - -class String(Schema): - """ - - Matches a particular string. The argument object must be a string and be - equal to a specific string value. At instantiation, the string is set and - any future comparisons are checked against this internal string value. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = String('Hi') - >>> schema.matches('Hi') - True - >>> schema.matches('Not hi') - False - """ - - def __init__(self, string): - if not isinstance(string, str): - raise exceptions.FormatError( - "Expected a string but" " got " + repr(string) - ) - - self._string = string - - def check_match(self, object): # pylint: disable=redefined-builtin - if self._string != object: - raise exceptions.FormatError( - "Expected " + repr(self._string) + " got " + repr(object) - ) - - -class AnyString(Schema): - """ - - Matches any string, but not a non-string object. This schema - can be viewed as the Any() schema applied to Strings, but an - additional check is performed to ensure only strings are considered. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = AnyString() - >>> schema.matches('') - True - >>> schema.matches('a string') - True - >>> schema.matches(['a']) - False - >>> schema.matches(3) - False - >>> schema.matches(u'a unicode string') - True - >>> schema.matches({}) - False - """ - - def __init__(self): - pass - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, str): - raise exceptions.FormatError( - "Expected a string" " but got " + repr(object) - ) - - -class AnyNonemptyString(AnyString): - """ - - Matches any string with one or more characters. - This schema can be viewed as the Any() schema applied to Strings, but an - additional check is performed to ensure only strings are considered and - that said strings have at least one character. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = AnyNonemptyString() - >>> schema.matches('') - False - >>> schema.matches('a string') - True - >>> schema.matches(['a']) - False - >>> schema.matches(3) - False - >>> schema.matches(u'a unicode string') - True - >>> schema.matches({}) - False - """ - - def check_match(self, object): # pylint: disable=redefined-builtin - AnyString.check_match(self, object) - - if object == "": - raise exceptions.FormatError( - "Expected a string" - " with at least one character but got " + repr(object) - ) - - -class AnyBytes(Schema): - """ - - Matches any byte string, but not a non-byte object. This schema can be - viewed as the Any() schema applied to byte strings, but an additional check - is performed to ensure only strings are considered. Supported methods - include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = AnyBytes() - >>> schema.matches(b'') - True - >>> schema.matches(b'a string') - True - >>> schema.matches(['a']) - False - >>> schema.matches(3) - False - >>> schema.matches({}) - False - """ - - def __init__(self): - pass - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, bytes): - raise exceptions.FormatError( - "Expected a byte string" " but got " + repr(object) - ) - - -class LengthString(Schema): - """ - - Matches any string of a specified length. The argument object must be a - string. At instantiation, the string length is set and any future - comparisons are checked against this internal string value length. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = LengthString(5) - >>> schema.matches('Hello') - True - >>> schema.matches('Hi') - False - """ - - def __init__(self, length): - if isinstance(length, bool) or not isinstance(length, int): - # We need to check for bool as a special case, since bool - # is for historical reasons a subtype of int. - raise exceptions.FormatError( - "Got " + repr(length) + " instead of an integer." - ) - - self._string_length = length - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, str): - raise exceptions.FormatError( - "Expected a string but" " got " + repr(object) - ) - - if len(object) != self._string_length: - raise exceptions.FormatError( - "Expected a string of" " length " + repr(self._string_length) - ) - - -class LengthBytes(Schema): - """ - - Matches any Bytes of a specified length. The argument object must be either - a str() in Python 2, or bytes() in Python 3. At instantiation, the bytes - length is set and any future comparisons are checked against this internal - bytes value length. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - - >>> schema = LengthBytes(5) - >>> schema.matches(b'Hello') - True - >>> schema.matches(b'Hi') - False - """ - - def __init__(self, length): - if isinstance(length, bool) or not isinstance(length, int): - # We need to check for bool as a special case, since bool - # is for historical reasons a subtype of int. - raise exceptions.FormatError( - "Got " + repr(length) + " instead of an integer." - ) - - self._bytes_length = length - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, bytes): - raise exceptions.FormatError( - "Expected a byte but" " got " + repr(object) - ) - - if len(object) != self._bytes_length: - raise exceptions.FormatError( - "Expected a byte of" " length " + repr(self._bytes_length) - ) - - -class OneOf(Schema): - """ - - Matches an object that matches any one of several schemas. OneOf() returns - a result as soon as one of its recognized sub-schemas is encountered in the - object argument. When OneOf() is instantiated, its supported sub-schemas - are specified by a sequence type (e.g., a list, tuple, etc.). A mismatch - is returned after checking all sub-schemas and not finding a supported - type. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = OneOf([ListOf(Integer()), String('Hello'), String('bye')]) - >>> schema.matches(3) - False - >>> schema.matches('bye') - True - >>> schema.matches([]) - True - >>> schema.matches([1,2]) - True - >>> schema.matches(['Hi']) - False - """ - - def __init__(self, alternatives): - # Ensure each item of the list contains the expected object type. - if not isinstance(alternatives, list): - raise exceptions.FormatError( - "Expected a list but" " got " + repr(alternatives) - ) - - for alternative in alternatives: - if not isinstance(alternative, Schema): - raise exceptions.FormatError( - "List contains an" " invalid item " + repr(alternative) - ) - - self._alternatives = alternatives - - def check_match(self, object): # pylint: disable=redefined-builtin - # Simply return as soon as we find a match. - # Raise 'exceptions.FormatError' if no matches are found. - for alternative in self._alternatives: - if alternative.matches(object): - return - raise exceptions.FormatError( - "Object did not match a" - " recognized alternative." # pylint: disable=implicit-str-concat - ) - - -class AllOf(Schema): - """ - - Matches the intersection of a list of schemas. The object being tested - must match all of the required sub-schemas. Unlike OneOf(), which can - return a result as soon as a match is found in one of its supported - sub-schemas, AllOf() must verify each sub-schema before returning a result. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = AllOf([Any(), AnyString(), String('a')]) - >>> schema.matches('b') - False - >>> schema.matches('a') - True - """ - - def __init__(self, required_schemas): - # Ensure each item of the list contains the expected object type. - if not isinstance(required_schemas, list): - raise exceptions.FormatError( - "Expected a list but" " got" + repr(required_schemas) - ) - - for schema in required_schemas: - if not isinstance(schema, Schema): - raise exceptions.FormatError( - "List contains an" " invalid item " + repr(schema) - ) - - self._required_schemas = required_schemas[:] - - def check_match(self, object): # pylint: disable=redefined-builtin - for required_schema in self._required_schemas: - required_schema.check_match(object) - - -class Boolean(Schema): - """ - - Matches a boolean. The object argument must be one of True or False. All - other types are flagged as mismatches. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = Boolean() - >>> schema.matches(True) and schema.matches(False) - True - >>> schema.matches(11) - False - """ - - def __init__(self): - pass - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, bool): - raise exceptions.FormatError( - "Got " + repr(object) + " instead of a boolean." - ) - - -class ListOf(Schema): - """ - - Matches a homogeneous list of some sub-schema. That is, all the sub-schema - must be of the same type. The object argument must be a sequence type - (e.g., a list, tuple, etc.). When ListOf() is instantiated, a minimum and - maximum count can be specified for the homogeneous sub-schema list. If - min_count is set to 'n', the object argument sequence must contain 'n' - items. See ListOf()'s __init__ method for the expected arguments. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = ListOf(RegularExpression('(?:..)*')) - >>> schema.matches('hi') - False - >>> schema.matches([]) - True - >>> schema.matches({}) - False - >>> schema.matches(['Hi', 'this', 'list', 'is', 'full', 'of', 'even', 'strs']) - True - >>> schema.matches(['This', 'one', 'is not']) - False - >>> schema = ListOf(Integer(), min_count=3, max_count=10) - >>> schema.matches([3]*2) - False - >>> schema.matches([3]*3) - True - >>> schema.matches([3]*10) - True - >>> schema.matches([3]*11) - False - """ - - def __init__( - self, schema, min_count=0, max_count=sys.maxsize, list_name="list" - ): - """ - - Create a new ListOf schema. - - - schema: The pattern to match. - min_count: The minimum number of sub-schema in 'schema'. - max_count: The maximum number of sub-schema in 'schema'. - list_name: A string identifier for the ListOf object. - """ - - if not isinstance(schema, Schema): - message = "Expected Schema type but got " + repr(schema) - raise exceptions.FormatError(message) - - self._schema = schema - self._min_count = min_count - self._max_count = max_count - self._list_name = list_name - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, (list, tuple)): - raise exceptions.FormatError( - "Expected object of type {} but got type {}".format( # pylint: disable=consider-using-f-string - self._list_name, type(object).__name__ - ) - ) - - # Check if all the items in the 'object' list - # match 'schema'. - for item in object: - try: - self._schema.check_match(item) - - except exceptions.FormatError as e: - raise exceptions.FormatError( - str(e) + " in " + repr(self._list_name) - ) - - # Raise exception if the number of items in the list is - # not within the expected range. - if not (self._min_count <= len(object) <= self._max_count): - raise exceptions.FormatError( - "Length of " + repr(self._list_name) + " out of range." - ) - - -class Integer(Schema): - """ - - Matches an integer. A range can be specified. For example, only integers - between 8 and 42 can be set as a requirement. The object argument is also - checked against a Boolean type, since booleans have historically been - considered a sub-type of integer. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = Integer() - >>> schema.matches(99) - True - >>> schema.matches(False) - False - >>> schema.matches('a string') - False - >>> Integer(lo=10, hi=30).matches(25) - True - >>> Integer(lo=10, hi=30).matches(5) - False - """ - - def __init__(self, lo=-2147483648, hi=2147483647): - """ - - Create a new Integer schema. - - - lo: The minimum value the int object argument can be. - hi: The maximum value the int object argument can be. - """ - - self._lo = lo - self._hi = hi - - def check_match(self, object): # pylint: disable=redefined-builtin - if isinstance(object, bool) or not isinstance(object, int): - # We need to check for bool as a special case, since bool - # is for historical reasons a subtype of int. - raise exceptions.FormatError( - "Got " + repr(object) + " instead of an integer." - ) - - if not (self._lo <= object <= self._hi): - int_range = "[" + repr(self._lo) + ", " + repr(self._hi) + "]." - raise exceptions.FormatError( - repr(object) + " not in range " + int_range - ) - - -class DictOf(Schema): - """ - - Matches a mapping from items matching a particular key-schema to items - matching a value-schema (i.e., the object being checked must be a dict). - Note that in JSON, keys must be strings. In the example below, the keys of - the dict must be one of the letters contained in 'aeiou' and the value must - be a structure containing any two strings. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = DictOf(RegularExpression(r'[aeiou]+'), Struct([AnyString(), AnyString()])) - >>> schema.matches('') - False - >>> schema.matches({}) - True - >>> schema.matches({'a': ['x', 'y'], 'e' : ['', '']}) - True - >>> schema.matches({'a': ['x', 3], 'e' : ['', '']}) - False - >>> schema.matches({'a': ['x', 'y'], 'e' : ['', ''], 'd' : ['a', 'b']}) - False - """ - - def __init__(self, key_schema, value_schema): - """ - - Create a new DictOf schema. - - - key_schema: The dictionary's key. - value_schema: The dictionary's value. - """ - - if not isinstance(key_schema, Schema): - raise exceptions.FormatError( - "Expected Schema but" " got " + repr(key_schema) - ) - - if not isinstance(value_schema, Schema): - raise exceptions.FormatError( - "Expected Schema but" " got " + repr(value_schema) - ) - - self._key_schema = key_schema - self._value_schema = value_schema - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, dict): - raise exceptions.FormatError( - "Expected a dict but" " got " + repr(object) - ) - - for key, value in object.items(): - self._key_schema.check_match(key) - self._value_schema.check_match(value) - - -class Optional(Schema): - """ - - Provide a way for the Object() schema to accept optional dictionary keys. - The Object() schema outlines how a dictionary should look, such as the - names for dict keys and the object type of the dict values. Optional()'s - intended use is as a sub-schema to Object(). Object() flags an object as a - mismatch if a required key is not encountered, however, dictionary keys - labeled Optional() are not required to appear in the object's list of - required keys. If an Optional() key IS found, Optional()'s sub-schemas are - then verified. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = Object(k1=String('X'), k2=Optional(String('Y'))) - >>> schema.matches({'k1': 'X', 'k2': 'Y'}) - True - >>> schema.matches({'k1': 'X', 'k2': 'Z'}) - False - >>> schema.matches({'k1': 'X'}) - True - """ - - def __init__(self, schema): - if not isinstance(schema, Schema): - raise exceptions.FormatError( - "Expected Schema, but" " got " + repr(schema) - ) - self._schema = schema - - def check_match(self, object): # pylint: disable=redefined-builtin - self._schema.check_match(object) - - -class Object(Schema): - """ - - Matches a dict from specified keys to key-specific types. Unrecognized - keys are allowed. The Object() schema outlines how a dictionary should - look, such as the names for dict keys and the object type of the dict - values. See schema.Optional() to learn how Object() incorporates optional - sub-schemas. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = Object(a=AnyString(), bc=Struct([Integer(), Integer()])) - >>> schema.matches({'a':'ZYYY', 'bc':[5,9]}) - True - >>> schema.matches({'a':'ZYYY', 'bc':[5,9], 'xx':5}) - True - >>> schema.matches({'a':'ZYYY', 'bc':[5,9,3]}) - False - >>> schema.matches({'a':'ZYYY'}) - False - """ - - def __init__(self, object_name="object", **required): - """ - - Create a new Object schema. - - - object_name: A string identifier for the object argument. - - A variable number of keyword arguments is accepted. - """ - - # Ensure valid arguments. - for schema in required.values(): - if not isinstance(schema, Schema): - raise exceptions.FormatError( - "Expected Schema but" " got " + repr(schema) - ) - - self._object_name = object_name - self._required = list(required.items()) - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, dict): - raise exceptions.FormatError( - "Wanted a " + repr(self._object_name) + "." - ) - - # (key, schema) = (a, AnyString()) = (a=AnyString()) - for key, schema in self._required: - # Check if 'object' has all the required dict keys. If not one of the - # required keys, check if it is an Optional(). - try: - item = object[key] - - except KeyError: - # If not an Optional schema, raise an exception. - if not isinstance(schema, Optional): - raise exceptions.FormatError( # pylint: disable=raise-missing-from - "Missing key " - + repr(key) - + " in " - + repr(self._object_name) - ) - - # Check that 'object's schema matches Object()'s schema for this - # particular 'key'. - else: - try: - schema.check_match(item) - - except exceptions.FormatError as e: - raise exceptions.FormatError( - str(e) + " in " + self._object_name + "." + key - ) - - -class Struct(Schema): - """ - - Matches a non-homogeneous list of items. The sub-schemas are allowed to - vary. The object argument must be a sequence type (e.g., a list, tuple, - etc.). There is also an option to specify that additional schemas not - explicitly defined at instantiation are allowed. See __init__() for the - complete list of arguments accepted. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = Struct([ListOf(AnyString()), AnyString(), String('X')]) - >>> schema.matches(False) - False - >>> schema.matches('Foo') - False - >>> schema.matches([[], 'Q', 'X']) - True - >>> schema.matches([[], 'Q', 'D']) - False - >>> schema.matches([[3], 'Q', 'X']) - False - >>> schema.matches([[], 'Q', 'X', 'Y']) - False - >>> schema = Struct([String('X')], allow_more=True) - >>> schema.matches([]) - False - >>> schema.matches(['X']) - True - >>> schema.matches(['X', 'Y']) - True - >>> schema.matches(['X', ['Y', 'Z']]) - True - >>> schema.matches([['X']]) - False - >>> schema = Struct([String('X'), Integer()], [Integer()]) - >>> schema.matches([]) - False - >>> schema.matches({}) - False - >>> schema.matches(['X']) - False - >>> schema.matches(['X', 3]) - True - >>> schema.matches(['X', 3, 9]) - True - >>> schema.matches(['X', 3, 9, 11]) - False - >>> schema.matches(['X', 3, 'A']) - False - """ - - def __init__( - self, - sub_schemas, - optional_schemas=None, - allow_more=False, - struct_name="list", - ): - """ - - Create a new Struct schema. - - - sub_schemas: The sub-schemas recognized. - optional_schemas: Optional list. If none is given, it will be "[]". - allow_more: Specifies that an optional list of types is allowed. - struct_name: A string identifier for the Struct object. - """ - - if optional_schemas is None: - optional_schemas = [] - - # Ensure each item of the list contains the expected object type. - if not isinstance(sub_schemas, (list, tuple)): - raise exceptions.FormatError( - "Expected Schema but got " + repr(sub_schemas) - ) - - for schema in sub_schemas: - if not isinstance(schema, Schema): - raise exceptions.FormatError( - "Expected Schema but" " got " + repr(schema) - ) - - self._sub_schemas = sub_schemas + optional_schemas - self._min = len(sub_schemas) - self._allow_more = allow_more - self._struct_name = struct_name - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, (list, tuple)): - raise exceptions.FormatError( - "Expected " - + repr(self._struct_name) - + "; but got " - + repr(object) - ) - - if len(object) < self._min: - raise exceptions.FormatError( - "Too few fields in " + self._struct_name - ) - - if len(object) > len(self._sub_schemas) and not self._allow_more: - raise exceptions.FormatError( - "Too many fields in " + self._struct_name - ) - - # Iterate through the items of 'object', checking against each schema in - # the list of schemas allowed (i.e., the sub-schemas and also any optional - # schemas. The lenth of 'object' must be less than the length of the - # required schemas + the optional schemas. However, 'object' is allowed to - # be only as large as the length of the required schemas. In the while - # loop below, we check against these two cases. - index = 0 - while index < len(object) and index < len(self._sub_schemas): - item = object[index] - schema = self._sub_schemas[index] - schema.check_match(item) - index = index + 1 - - -class RegularExpression(Schema): - """ - - Matches any string that matches a given regular expression. The RE pattern - set when RegularExpression is instantiated must not be None. See - __init__() for a complete list of accepted arguments. - - Supported methods include: - matches(): returns a Boolean result. - check_match(): raises 'exceptions.FormatError' on a mismatch. - - - >>> schema = RegularExpression('h.*d') - >>> schema.matches('hello world') - True - >>> schema.matches('Hello World') - False - >>> schema.matches('hello world!') - False - >>> schema.matches([33, 'Hello']) - False - """ - - def __init__(self, pattern=None, modifiers=0, re_object=None, re_name=None): - """ - - Create a new regular expression schema. - - - pattern: The pattern to match, or None if re_object is provided. - modifiers: Flags to use when compiling the pattern. - re_object: A compiled regular expression object. - re_name: Identifier for the regular expression object. - """ - - if not isinstance(pattern, str): - if pattern is not None: - raise exceptions.FormatError( - repr(pattern) + " is not a string." - ) - - if re_object is None: - if pattern is None: - raise exceptions.FormatError( - "Cannot compare against an unset regular expression" - ) - - if not pattern.endswith("$"): - pattern += "$" - re_object = re.compile(pattern, modifiers) - self._re_object = re_object - - if re_name is None: - if pattern is not None: - re_name = "pattern /" + pattern + "/" - - else: - re_name = "pattern" - self._re_name = re_name - - def check_match(self, object): # pylint: disable=redefined-builtin - if not isinstance(object, str) or not self._re_object.match(object): - raise exceptions.FormatError( - repr(object) + " did not match " + repr(self._re_name) - ) - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can - # be tested by running schema.py as a standalone module. - # python -B schema.py. - import doctest - - doctest.testmod() diff --git a/tests/test_formats.py b/tests/test_formats.py index f9b2d9a5..bbbea91f 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -15,345 +15,12 @@ Unit test for 'formats.py' """ -import datetime import unittest import securesystemslib.formats -import securesystemslib.schema class TestFormats(unittest.TestCase): # pylint: disable=missing-class-docstring - def setUp(self): - pass - - def tearDown(self): - pass - - def test_schemas(self): - # Test conditions for valid schemas. - valid_schemas = { - "ISO8601_DATETIME_SCHEMA": ( - securesystemslib.formats.ISO8601_DATETIME_SCHEMA, - "1985-10-21T13:20:00Z", - ), - "UNIX_TIMESTAMP_SCHEMA": ( - securesystemslib.formats.UNIX_TIMESTAMP_SCHEMA, - 499137720, - ), - "HASH_SCHEMA": ( - securesystemslib.formats.HASH_SCHEMA, - "A4582BCF323BCEF", - ), - "HASHDICT_SCHEMA": ( - securesystemslib.formats.HASHDICT_SCHEMA, - {"sha256": "A4582BCF323BCEF"}, - ), - "HEX_SCHEMA": ( - securesystemslib.formats.HEX_SCHEMA, - "A4582BCF323BCEF", - ), - "KEYID_SCHEMA": ( - securesystemslib.formats.KEYID_SCHEMA, - "123456789abcdef", - ), - "KEYIDS_SCHEMA": ( - securesystemslib.formats.KEYIDS_SCHEMA, - ["123456789abcdef", "123456789abcdef"], - ), - "SCHEME_SCHEMA": ( - securesystemslib.formats.SCHEME_SCHEMA, - "ecdsa-sha2-nistp256", - ), - "PATH_SCHEMA": ( - securesystemslib.formats.PATH_SCHEMA, - "/home/someuser/", - ), - "PATHS_SCHEMA": ( - securesystemslib.formats.PATHS_SCHEMA, - ["/home/McFly/", "/home/Tannen/"], - ), - "URL_SCHEMA": ( - securesystemslib.formats.URL_SCHEMA, - "https://www.updateframework.com/", - ), - "NAME_SCHEMA": ( - securesystemslib.formats.NAME_SCHEMA, - "Marty McFly", - ), - "TEXT_SCHEMA": (securesystemslib.formats.TEXT_SCHEMA, "Password: "), - "BOOLEAN_SCHEMA": (securesystemslib.formats.BOOLEAN_SCHEMA, True), - "RSAKEYBITS_SCHEMA": ( - securesystemslib.formats.RSAKEYBITS_SCHEMA, - 4096, - ), - "PASSWORD_SCHEMA": ( - securesystemslib.formats.PASSWORD_SCHEMA, - "secret", - ), - "PASSWORDS_SCHEMA": ( - securesystemslib.formats.PASSWORDS_SCHEMA, - ["pass1", "pass2"], - ), - "KEYVAL_SCHEMA": ( - securesystemslib.formats.KEYVAL_SCHEMA, - {"public": "pubkey", "private": "privkey"}, - ), - "PUBLIC_KEYVAL_SCHEMA": ( - securesystemslib.formats.PUBLIC_KEYVAL_SCHEMA, - {"public": "pubkey"}, - ), - "PUBLIC_KEYVAL_SCHEMA2": ( - securesystemslib.formats.PUBLIC_KEYVAL_SCHEMA, - {"public": "pubkey", "private": ""}, - ), - "RSA_SCHEME_SCHEMA_RSASSA_PSS": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsassa-pss-sha224", - ), - "RSA_SCHEME_SCHEMA_RSASSA_PSS_2": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsassa-pss-sha256", - ), - "RSA_SCHEME_SCHEMA_RSASSA_PSS_3": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsassa-pss-sha384", - ), - "RSA_SCHEME_SCHEMA_RSASSA_PSS_4": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsassa-pss-sha512", - ), - "RSA_SCHEME_SCHEMA_PKCS1v15": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsa-pkcs1v15-sha224", - ), - "RSA_SCHEME_SCHEMA_PKCS1v15_2": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsa-pkcs1v15-sha256", - ), - "RSA_SCHEME_SCHEMA_PKCS1v15_3": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsa-pkcs1v15-sha384", - ), - "RSA_SCHEME_SCHEMA_PKCS1v15_4": ( - securesystemslib.formats.RSA_SCHEME_SCHEMA, - "rsa-pkcs1v15-sha512", - ), - "KEY_SCHEMA": ( - securesystemslib.formats.KEY_SCHEMA, - { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": {"public": "pubkey", "private": "privkey"}, - }, - ), - "PUBLIC_KEY_SCHEMA": ( - securesystemslib.formats.KEY_SCHEMA, - { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": {"public": "pubkey"}, - }, - ), - "PUBLIC_KEY_SCHEMA2": ( - securesystemslib.formats.KEY_SCHEMA, - { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": {"public": "pubkey", "private": ""}, - }, - ), - "RSAKEY_SCHEMA": ( - securesystemslib.formats.RSAKEY_SCHEMA, - { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyid": "123456789abcdef", - "keyval": {"public": "pubkey", "private": "privkey"}, - }, - ), - "SIGNATURE_SCHEMA": ( - securesystemslib.formats.SIGNATURE_SCHEMA, - {"keyid": "123abc", "method": "evp", "sig": "A4582BCF323BCEF"}, - ), - "SIGNABLE_SCHEMA": ( - securesystemslib.formats.SIGNABLE_SCHEMA, - { - "signed": "signer", - "signatures": [ - { - "keyid": "123abc", - "method": "evp", - "sig": "A4582BCF323BCEF", - } - ], - }, - ), - "ANY_KEYDICT_SCHEMA": ( - securesystemslib.formats.ANY_KEYDICT_SCHEMA, - { - "123abc": { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": {"public": "pubkey", "private": "privkey"}, - } - }, - ), - } - - # Iterate 'valid_schemas', ensuring each 'valid_schema' correctly matches - # its respective 'schema_type'. - for ( # pylint: disable=unbalanced-dict-unpacking - schema_type, - valid_schema, - ) in valid_schemas.values(): - if not schema_type.matches( # pylint: disable=no-member - valid_schema - ): - print("bad schema: " + repr(valid_schema)) - - self.assertEqual( - True, - schema_type.matches(valid_schema), # pylint: disable=no-member - ) - - # Test conditions for invalid schemas. - # Set the 'valid_schema' of 'valid_schemas' to an invalid - # value and test that it does not match 'schema_type'. - for ( # pylint: disable=unbalanced-dict-unpacking - schema_type, - valid_schema, - ) in valid_schemas.values(): - invalid_schema = 0xBAD - - if isinstance(schema_type, securesystemslib.schema.Integer): - invalid_schema = "BAD" - self.assertEqual( - False, - schema_type.matches( # pylint: disable=no-member - invalid_schema - ), - ) - - def test_unix_timestamp_to_datetime(self): - # Test conditions for valid arguments. - self.assertTrue( - datetime.datetime, - securesystemslib.formats.unix_timestamp_to_datetime(499137720), - ) - datetime_object = datetime.datetime(1985, 10, 26, 1, 22) - self.assertEqual( - datetime_object, - securesystemslib.formats.unix_timestamp_to_datetime(499137720), - ) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.unix_timestamp_to_datetime, - "bad", - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.unix_timestamp_to_datetime, - 1000000000000000000000, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.unix_timestamp_to_datetime, - -1, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.unix_timestamp_to_datetime, - ["5"], - ) - - def test_datetime_to_unix_timestamp(self): - # Test conditions for valid arguments. - datetime_object = datetime.datetime(2015, 10, 21, 19, 28) - self.assertEqual( - 1445455680, - securesystemslib.formats.datetime_to_unix_timestamp( - datetime_object - ), - ) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.datetime_to_unix_timestamp, - "bad", - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.datetime_to_unix_timestamp, - 1000000000000000000000, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.datetime_to_unix_timestamp, - ["1"], - ) - - def test_format_base64(self): - # Test conditions for valid arguments. - data = "updateframework".encode("utf-8") - self.assertEqual( - "dXBkYXRlZnJhbWV3b3Jr", securesystemslib.formats.format_base64(data) - ) - self.assertTrue( - isinstance(securesystemslib.formats.format_base64(data), str) - ) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.format_base64, - 123, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.format_base64, - True, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.format_base64, - ["123"], - ) - - def test_parse_base64(self): - # Test conditions for valid arguments. - base64 = "dXBkYXRlZnJhbWV3b3Jr" - self.assertEqual( - b"updateframework", securesystemslib.formats.parse_base64(base64) - ) - self.assertTrue( - isinstance(securesystemslib.formats.parse_base64(base64), bytes) - ) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.parse_base64, - 123, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.parse_base64, - True, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.parse_base64, - ["123"], - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.formats.parse_base64, - "/", - ) - def test_encode_canonical(self): # Test conditions for valid arguments. encode = securesystemslib.formats.encode_canonical diff --git a/tests/test_schema.py b/tests/test_schema.py deleted file mode 100755 index b91f79b3..00000000 --- a/tests/test_schema.py +++ /dev/null @@ -1,509 +0,0 @@ -""" - - test_schema.py - - - Vladimir Diaz - - - October 2012. - - - See LICENSE for licensing information. - - - Unit test for 'schema.py' -""" - -import re -import unittest - -import securesystemslib.exceptions -import securesystemslib.schema as SCHEMA - - -class TestSchema(unittest.TestCase): # pylint: disable=missing-class-docstring - def setUp(self): - pass - - def tearDown(self): - pass - - def test_Schema(self): - # Test conditions for the instantation of classes that inherit - # from class Schema(). - class NewSchema(SCHEMA.Schema): # pylint: disable=abstract-method - def __init__(self): - pass - - new_schema = NewSchema() - self.assertRaises(NotImplementedError, new_schema.matches, "test") - - # Define a new schema. - class NewSchema2(SCHEMA.Schema): - def __init__(self, string): - self._string = string - - def check_match(self, object): # pylint: disable=redefined-builtin - if self._string != object: - message = "Expected: " + repr(self._string) - raise securesystemslib.exceptions.FormatError(message) - - new_schema2 = NewSchema2("test") - self.assertRaises( - securesystemslib.exceptions.FormatError, - new_schema2.check_match, - "bad", - ) - self.assertFalse(new_schema2.matches("bad")) - self.assertTrue(new_schema2.matches("test")) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - new_schema2.check_match, - True, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - new_schema2.check_match, - NewSchema2, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - new_schema2.check_match, - 123, - ) - - self.assertFalse(new_schema2.matches(True)) - self.assertFalse(new_schema2.matches(NewSchema2)) - self.assertFalse(new_schema2.matches(123)) - - def test_Any(self): - # Test conditions for valid arguments. - any_schema = SCHEMA.Any() - - self.assertTrue(any_schema.matches("test")) - self.assertTrue(any_schema.matches(123)) - self.assertTrue(any_schema.matches(["test"])) - self.assertTrue(any_schema.matches({"word": "definition"})) - self.assertTrue(any_schema.matches(True)) - - def test_String(self): - # Test conditions for valid arguments. - string_schema = SCHEMA.String("test") - - self.assertTrue(string_schema.matches("test")) - - # Test conditions for invalid arguments. - self.assertFalse(string_schema.matches(True)) - self.assertFalse(string_schema.matches(["test"])) - self.assertFalse(string_schema.matches(SCHEMA.Schema)) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.String, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.String, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.String, {"a": 1} - ) - - def test_AnyString(self): - # Test conditions for valid arguments. - anystring_schema = SCHEMA.AnyString() - - self.assertTrue(anystring_schema.matches("")) - self.assertTrue(anystring_schema.matches("a string")) - - # Test conditions for invalid arguments. - self.assertFalse(anystring_schema.matches(["a"])) - self.assertFalse(anystring_schema.matches(3)) - self.assertFalse(anystring_schema.matches({"a": "string"})) - - def test_AnyNonemptyString(self): - anynonemptystring_schema = SCHEMA.AnyNonemptyString() - - self.assertTrue(anynonemptystring_schema.matches("foo")) - - # Test conditions for invalid arguments. - self.assertFalse(anynonemptystring_schema.matches("")) - self.assertFalse(anynonemptystring_schema.matches(["a"])) - self.assertFalse(anynonemptystring_schema.matches(3)) - self.assertFalse(anynonemptystring_schema.matches({"a": "string"})) - - def test_OneOf(self): - # Test conditions for valid arguments. - oneof_schema = SCHEMA.OneOf( - [ - SCHEMA.ListOf(SCHEMA.Integer()), - SCHEMA.String("Hello"), - SCHEMA.String("bye"), - ] - ) - - self.assertTrue(oneof_schema.matches([])) - self.assertTrue(oneof_schema.matches("bye")) - self.assertTrue(oneof_schema.matches([1, 2])) - - # Test conditions for invalid arguments. - self.assertFalse(oneof_schema.matches(3)) - self.assertFalse(oneof_schema.matches(["Hi"])) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.OneOf, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.OneOf, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.OneOf, {"a": 1} - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.OneOf, - [SCHEMA.AnyString(), 1], - ) - - def test_AllOf(self): - # Test conditions for valid arguments. - allof_schema = SCHEMA.AllOf( - [SCHEMA.Any(), SCHEMA.AnyString(), SCHEMA.String("a")] - ) - - self.assertTrue(allof_schema.matches("a")) - - # Test conditions for invalid arguments. - self.assertFalse(allof_schema.matches("b")) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.AllOf, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.AllOf, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.AllOf, {"a": 1} - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.AllOf, - [SCHEMA.AnyString(), 1], - ) - - def test_Boolean(self): - # Test conditions for valid arguments. - boolean_schema = SCHEMA.Boolean() - - self.assertTrue( - boolean_schema.matches(True) and boolean_schema.matches(False) - ) - - # Test conditions for invalid arguments. - self.assertFalse(boolean_schema.matches(11)) - - def test_ListOf(self): - # Test conditions for valid arguments. - listof_schema = SCHEMA.ListOf(SCHEMA.RegularExpression("(?:..)*")) - listof2_schema = SCHEMA.ListOf( - SCHEMA.Integer(), min_count=3, max_count=10 - ) - - self.assertTrue(listof_schema.matches([])) - self.assertTrue( - listof_schema.matches( - ["Hi", "this", "list", "is", "full", "of", "even", "strs"] - ) - ) - - self.assertTrue(listof2_schema.matches([3] * 3)) - self.assertTrue(listof2_schema.matches([3] * 10)) - - # Test conditions for invalid arguments. - self.assertFalse(listof_schema.matches("hi")) - self.assertFalse(listof_schema.matches({})) - self.assertFalse(listof_schema.matches(["This", "one", "is not"])) - - self.assertFalse(listof2_schema.matches([3] * 2)) - self.assertFalse(listof2_schema.matches(([3] * 11))) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.ListOf, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.ListOf, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.ListOf, {"a": 1} - ) - - def test_Integer(self): - # Test conditions for valid arguments. - integer_schema = SCHEMA.Integer() - - self.assertTrue(integer_schema.matches(99)) - self.assertTrue(SCHEMA.Integer(lo=10, hi=30).matches(25)) - - # Test conditions for invalid arguments. - self.assertFalse(integer_schema.matches(False)) - self.assertFalse(integer_schema.matches("a string")) - self.assertFalse(SCHEMA.Integer(lo=10, hi=30).matches(5)) - - def test_DictOf(self): - # Test conditions for valid arguments. - dictof_schema = SCHEMA.DictOf( - SCHEMA.RegularExpression(r"[aeiou]+"), - SCHEMA.Struct([SCHEMA.AnyString(), SCHEMA.AnyString()]), - ) - - self.assertTrue(dictof_schema.matches({})) - self.assertTrue(dictof_schema.matches({"a": ["x", "y"], "e": ["", ""]})) - - # Test conditions for invalid arguments. - self.assertFalse(dictof_schema.matches("")) - self.assertFalse(dictof_schema.matches({"a": ["x", 3], "e": ["", ""]})) - self.assertFalse( - dictof_schema.matches( - {"a": ["x", "y"], "e": ["", ""], "d": ["a", "b"]} - ) - ) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.DictOf, 1, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.DictOf, [1], [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.DictOf, {"a": 1}, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.DictOf, - SCHEMA.AnyString(), - 1, - ) - - def test_Optional(self): - # Test conditions for valid arguments. - optional_schema = SCHEMA.Object( - k1=SCHEMA.String("X"), k2=SCHEMA.Optional(SCHEMA.String("Y")) - ) - - self.assertTrue(optional_schema.matches({"k1": "X", "k2": "Y"})) - self.assertTrue(optional_schema.matches({"k1": "X"})) - - # Test conditions for invalid arguments. - self.assertFalse(optional_schema.matches({"k1": "X", "k2": "Z"})) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Optional, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Optional, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Optional, {"a": 1} - ) - - def test_Object(self): - # Test conditions for valid arguments. - object_schema = SCHEMA.Object( - a=SCHEMA.AnyString(), - bc=SCHEMA.Struct([SCHEMA.Integer(), SCHEMA.Integer()]), - ) - - self.assertTrue(object_schema.matches({"a": "ZYYY", "bc": [5, 9]})) - self.assertTrue( - object_schema.matches({"a": "ZYYY", "bc": [5, 9], "xx": 5}) - ) - - # Test conditions for invalid arguments. - self.assertFalse(object_schema.matches({"a": "ZYYY", "bc": [5, 9, 3]})) - self.assertFalse(object_schema.matches({"a": "ZYYY"})) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Object, a="a" - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Object, a=[1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.Object, - a=SCHEMA.AnyString(), - b=1, - ) - - # Test condition for invalid non-dict arguments. - self.assertFalse(object_schema.matches([{"a": "XYZ"}])) - self.assertFalse(object_schema.matches(8)) - - def test_Struct(self): - # Test conditions for valid arguments. - struct_schema = SCHEMA.Struct( - [ - SCHEMA.ListOf(SCHEMA.AnyString()), - SCHEMA.AnyString(), - SCHEMA.String("X"), - ] - ) - struct2_schema = SCHEMA.Struct([SCHEMA.String("X")], allow_more=True) - struct3_schema = SCHEMA.Struct( - [SCHEMA.String("X"), SCHEMA.Integer()], [SCHEMA.Integer()] - ) - - self.assertTrue(struct_schema.matches([[], "Q", "X"])) - - self.assertTrue(struct2_schema.matches(["X"])) - self.assertTrue(struct2_schema.matches(["X", "Y"])) - self.assertTrue(struct2_schema.matches(["X", ["Y", "Z"]])) - - self.assertTrue(struct3_schema.matches(["X", 3])) - self.assertTrue(struct3_schema.matches(["X", 3, 9])) - - # Test conditions for invalid arguments. - self.assertFalse(struct_schema.matches(False)) - self.assertFalse(struct_schema.matches("Foo")) - self.assertFalse(struct_schema.matches([[], "Q", "D"])) - self.assertFalse(struct_schema.matches([[3], "Q", "X"])) - self.assertFalse(struct_schema.matches([[], "Q", "X", "Y"])) - - self.assertFalse(struct2_schema.matches([])) - self.assertFalse(struct2_schema.matches([["X"]])) - - self.assertFalse(struct3_schema.matches([])) - self.assertFalse(struct3_schema.matches({})) - self.assertFalse(struct3_schema.matches(["X"])) - self.assertFalse(struct3_schema.matches(["X", 3, 9, 11])) - self.assertFalse(struct3_schema.matches(["X", 3, "A"])) - - # Test conditions for invalid arguments in a schema definition. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Struct, 1 - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Struct, [1] - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.Struct, {"a": 1} - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.Struct, - [SCHEMA.AnyString(), 1], - ) - - def test_RegularExpression(self): - # Test conditions for valid arguments. - # RegularExpression(pattern, modifiers, re_object, re_name). - re_schema = SCHEMA.RegularExpression("h.*d") - - self.assertTrue(re_schema.matches("hello world")) - - # Provide a pattern that contains the trailing '$' - re_schema_2 = SCHEMA.RegularExpression( - pattern="abc$", modifiers=0, re_object=None, re_name="my_re" - ) - - self.assertTrue(re_schema_2.matches("abc")) - - # Test for valid optional arguments. - compiled_re = re.compile("^[a-z].*") - re_schema_optional = SCHEMA.RegularExpression( - pattern="abc", modifiers=0, re_object=compiled_re, re_name="my_re" - ) - self.assertTrue(re_schema_optional.matches("abc")) - - # Valid arguments, but the 'pattern' argument is unset (required if the - # 're_object' is 'None'.) - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.RegularExpression, - None, - 0, - None, - None, - ) - - # Valid arguments, 're_name' is unset, and 'pattern' is None. An exception - # is not raised, but 're_name' is set to 'pattern'. - re_schema_optional = SCHEMA.RegularExpression( - pattern=None, modifiers=0, re_object=compiled_re, re_name=None - ) - - self.assertTrue(re_schema_optional.matches("abc")) - self.assertTrue( - re_schema_optional._re_name # pylint: disable=protected-access - == "pattern" - ) - - # Test conditions for invalid arguments. - self.assertFalse(re_schema.matches("Hello World")) - self.assertFalse(re_schema.matches("hello world!")) - self.assertFalse(re_schema.matches([33, "Hello"])) - - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.RegularExpression, 8 - ) - - def test_LengthString(self): - # Test conditions for valid arguments. - length_string = SCHEMA.LengthString(11) - - self.assertTrue(length_string.matches("Hello World")) - self.assertTrue(length_string.matches("Hello Marty")) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - SCHEMA.LengthString, - "hello", - ) - - self.assertFalse(length_string.matches("hello")) - self.assertFalse(length_string.matches(8)) - - def test_LengthBytes(self): - # Test conditions for valid arguments. - length_bytes = SCHEMA.LengthBytes(11) - - self.assertTrue(length_bytes.matches(b"Hello World")) - self.assertTrue(length_bytes.matches(b"Hello Marty")) - - # Test conditions for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.LengthBytes, "hello" - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, SCHEMA.LengthBytes, True - ) - - self.assertFalse(length_bytes.matches(b"hello")) - self.assertFalse(length_bytes.matches(8)) - - def test_AnyBytes(self): - # Test conditions for valid arguments. - anybytes_schema = SCHEMA.AnyBytes() - - self.assertTrue(anybytes_schema.matches(b"")) - self.assertTrue(anybytes_schema.matches(b"a string")) - - # Test conditions for invalid arguments. - self.assertFalse(anybytes_schema.matches("a string")) - self.assertFalse(anybytes_schema.matches(["a"])) - self.assertFalse(anybytes_schema.matches(3)) - self.assertFalse(anybytes_schema.matches({"a": "string"})) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main()