attestationcheck 0.1.0__py3-none-any.whl
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.
- attestationcheck/__init__.py +5 -0
- attestationcheck/__main__.py +7 -0
- attestationcheck/io/__init__.py +0 -0
- attestationcheck/io/cli.py +175 -0
- attestationcheck/io/fmt.py +272 -0
- attestationcheck/io/html.template +69 -0
- attestationcheck/models/__init__.py +0 -0
- attestationcheck/models/attestation.py +73 -0
- attestationcheck/models/config.py +21 -0
- attestationcheck/models/defaultonnone.py +22 -0
- attestationcheck/models/packageinfo.py +82 -0
- attestationcheck/models/pypijson.py +93 -0
- attestationcheck/packageinforesolver.py +190 -0
- attestationcheck/session.py +4 -0
- attestationcheck/verify_attestation.py +90 -0
- attestationcheck-0.1.0.dist-info/METADATA +378 -0
- attestationcheck-0.1.0.dist-info/RECORD +20 -0
- attestationcheck-0.1.0.dist-info/WHEEL +4 -0
- attestationcheck-0.1.0.dist-info/entry_points.txt +2 -0
- attestationcheck-0.1.0.dist-info/licenses/LICENSE.md +20 -0
|
File without changes
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Output the attestations used by dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from dataclasses import fields
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from sys import exit as sysexit
|
|
9
|
+
from sys import stdin, stdout
|
|
10
|
+
|
|
11
|
+
from configurator import Config
|
|
12
|
+
from configurator.node import ConfigNode
|
|
13
|
+
|
|
14
|
+
from attestationcheck import packageinforesolver # ,checker
|
|
15
|
+
from attestationcheck.io import fmt
|
|
16
|
+
from attestationcheck.models.config import LC_Config
|
|
17
|
+
from attestationcheck.models.packageinfo import PackageInfo
|
|
18
|
+
|
|
19
|
+
stdout.reconfigure(encoding="utf-8") # type: ignore[general-type-issues]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cli() -> None: # pragma: no cover
|
|
23
|
+
"""Cli entry point."""
|
|
24
|
+
parser = argparse.ArgumentParser(description=__doc__, argument_default=argparse.SUPPRESS)
|
|
25
|
+
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--format",
|
|
28
|
+
"-f",
|
|
29
|
+
help=f"Output format. one of: {', '.join(set(fmt.formatMap))}. default=simple",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--requirements-paths",
|
|
33
|
+
"-r",
|
|
34
|
+
help="Filenames to read from (omit for stdin if piping, else pyproject.toml)",
|
|
35
|
+
nargs="+",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--groups",
|
|
39
|
+
"-g",
|
|
40
|
+
help="Select groups from supported files",
|
|
41
|
+
nargs="+",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--extras",
|
|
45
|
+
"-e",
|
|
46
|
+
help="Select extras from supported files",
|
|
47
|
+
nargs="+",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--file",
|
|
51
|
+
"-o",
|
|
52
|
+
help="Filename to write output to (omit this for stdout)",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--skip-dependencies",
|
|
56
|
+
help="set of packages/dependencies to skip",
|
|
57
|
+
nargs="+",
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--hide-output-parameters",
|
|
61
|
+
help="set of parameters to hide from the produced output",
|
|
62
|
+
nargs="+",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--show-only-failing",
|
|
66
|
+
help="Only output a set of failing packages from this lib",
|
|
67
|
+
action="store_true",
|
|
68
|
+
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--pypi-api",
|
|
71
|
+
help="Specify a custom pypi api endpoint, for example if using a custom pypi server, "
|
|
72
|
+
"note this must implement the 'pypi' and 'integrity' endpoints",
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--zero",
|
|
76
|
+
"-0",
|
|
77
|
+
help="Return non zero exit code if a package with an unverified attestation is found, ideal"
|
|
78
|
+
" for CI/CD",
|
|
79
|
+
action="store_true",
|
|
80
|
+
)
|
|
81
|
+
args = vars(parser.parse_args())
|
|
82
|
+
|
|
83
|
+
stdin_path = Path("__stdin__")
|
|
84
|
+
if not args.get("requirements_paths"):
|
|
85
|
+
if stdin.isatty():
|
|
86
|
+
args["requirements_paths"] = ["pyproject.toml"]
|
|
87
|
+
else:
|
|
88
|
+
stdin_path.write_text("\n".join(stdin.readlines()), encoding="utf-8")
|
|
89
|
+
|
|
90
|
+
config: ConfigNode = Config()
|
|
91
|
+
|
|
92
|
+
# (Parses in the following order: `pyproject.toml`,
|
|
93
|
+
# `setup.cfg`, `attestationcheck.toml`, `attestationcheck.json`,
|
|
94
|
+
# `attestationcheck.ini`, `~/attestationcheck.toml`, `~/attestationcheck.json`, `~/attestationcheck.ini`)
|
|
95
|
+
config_files = [
|
|
96
|
+
"~/attestationcheck.json",
|
|
97
|
+
"~/attestationcheck.toml",
|
|
98
|
+
"attestationcheck.json",
|
|
99
|
+
"attestationcheck.toml",
|
|
100
|
+
"setup.cfg",
|
|
101
|
+
"pyproject.toml",
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
for file in config_files:
|
|
105
|
+
config += Config.from_path(file, optional=True)
|
|
106
|
+
|
|
107
|
+
scopedData: ConfigNode = config.get("tool", {}).get("attestationcheck", ConfigNode())
|
|
108
|
+
attestationcheckConf: LC_Config = LC_Config.model_validate({**scopedData.data, **args})
|
|
109
|
+
|
|
110
|
+
ec = main(attestationcheckConf)
|
|
111
|
+
stdin_path.unlink(missing_ok=True)
|
|
112
|
+
|
|
113
|
+
sysexit(ec)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main(attestationcheckConf: LC_Config) -> int:
|
|
117
|
+
"""Test entry point."""
|
|
118
|
+
exitCode = 0
|
|
119
|
+
|
|
120
|
+
# File
|
|
121
|
+
requirements_paths = attestationcheckConf.requirements_paths or {"__stdin__"}
|
|
122
|
+
output_file = (
|
|
123
|
+
stdout
|
|
124
|
+
if attestationcheckConf.file in [None, ""]
|
|
125
|
+
else Path(attestationcheckConf.file or "").open("w", encoding="utf-8")
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
package_info_manager = packageinforesolver.PackageInfoManager(
|
|
129
|
+
attestationcheckConf.pypi_api or "https://pypi.org"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
package_info_manager.resolve_requirements(
|
|
133
|
+
requirements_paths=requirements_paths,
|
|
134
|
+
groups=attestationcheckConf.groups,
|
|
135
|
+
extras=attestationcheckConf.extras,
|
|
136
|
+
skip_dependencies=attestationcheckConf.skip_dependencies,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
all_packages: set[PackageInfo] = package_info_manager.getPackages()
|
|
140
|
+
|
|
141
|
+
incompatible = any(not x.is_attestation_verified for x in all_packages)
|
|
142
|
+
|
|
143
|
+
# Format the results
|
|
144
|
+
hide_output_parameters = attestationcheckConf.hide_output_parameters
|
|
145
|
+
|
|
146
|
+
available_params = [param.name.upper() for param in fields(PackageInfo)]
|
|
147
|
+
if not all(hop in available_params for hop in hide_output_parameters):
|
|
148
|
+
msg = (
|
|
149
|
+
f"Invalid parameter(s) in `hide_output_parameters`. "
|
|
150
|
+
f"Valid parameters are: {', '.join(available_params)}"
|
|
151
|
+
)
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
|
|
154
|
+
format_ = attestationcheckConf.format or "simple"
|
|
155
|
+
if attestationcheckConf.format in fmt.formatMap:
|
|
156
|
+
print(
|
|
157
|
+
fmt.fmt(
|
|
158
|
+
format_,
|
|
159
|
+
sorted(all_packages),
|
|
160
|
+
hide_output_parameters,
|
|
161
|
+
show_only_failing=attestationcheckConf.show_only_failing,
|
|
162
|
+
),
|
|
163
|
+
file=output_file,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
exitCode = 2
|
|
167
|
+
|
|
168
|
+
# Exit code of 1 if args.zero
|
|
169
|
+
if attestationcheckConf.zero and incompatible:
|
|
170
|
+
exitCode = 1
|
|
171
|
+
|
|
172
|
+
# Cleanup + exit
|
|
173
|
+
if attestationcheckConf.file not in [None, ""]:
|
|
174
|
+
output_file.close()
|
|
175
|
+
return exitCode
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The formatter is responsible for outputting the list of PackageInfo[s].
|
|
3
|
+
|
|
4
|
+
The available output formats are defined as follows
|
|
5
|
+
|
|
6
|
+
- ansi: Plain text output with ANSI color codes for terminal display.
|
|
7
|
+
used for simple, color-coded output on the command line.
|
|
8
|
+
- plain: A basic, no-frills plain text format, used when a clean and simple
|
|
9
|
+
textual representation is needed without any additional markup or styling.
|
|
10
|
+
- markdown: A lightweight markup language with plain-text formatting syntax. Ideal
|
|
11
|
+
for creating formatted documents that can be easily converted into HTML for web display.
|
|
12
|
+
- html: A format suitable for rendering in web browsers. (can be styled with CSS
|
|
13
|
+
for more complex presentation.)
|
|
14
|
+
- json: A structured data format. This format representing the list of PackageInfo[s]
|
|
15
|
+
as a JSON object
|
|
16
|
+
- csv: A simple, comma-separated values format. widely used in spreadsheets and
|
|
17
|
+
databases for easy import/export of data.
|
|
18
|
+
|
|
19
|
+
Note that these support the get_filtered_dict method, which allows users
|
|
20
|
+
to hide some of the output via the `--hide-output-parameters` cli flag. In addition
|
|
21
|
+
these support the show_only_failing method, which allows users
|
|
22
|
+
to show only packages that are not Verified with out license via the
|
|
23
|
+
`--show-only-failing` cli flag
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import csv
|
|
30
|
+
import json
|
|
31
|
+
import re
|
|
32
|
+
from collections import OrderedDict
|
|
33
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
34
|
+
from io import StringIO
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
import markdown as markdownlib
|
|
39
|
+
from rich.console import Console
|
|
40
|
+
from rich.table import Table
|
|
41
|
+
|
|
42
|
+
from attestationcheck.models.packageinfo import AttestationInfo, PackageInfo
|
|
43
|
+
|
|
44
|
+
THISDIR = Path(__file__).resolve().parent
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
VERSION = version("attestationcheck")
|
|
48
|
+
except PackageNotFoundError:
|
|
49
|
+
VERSION = "dev"
|
|
50
|
+
INFO = {"program": "attestationcheck", "version": VERSION, "license": "MIT LICENSE"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def stripAnsi(string: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Strip ansi codes from a given string.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
----
|
|
59
|
+
string (str): string to strip codes from
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
-------
|
|
63
|
+
str: plaintext, utf-8 string (safe for writing to files)
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
return re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").sub("", string)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ansi(
|
|
70
|
+
packages: list[dict[str, Any]],
|
|
71
|
+
) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Format to ansi.
|
|
74
|
+
|
|
75
|
+
:param License myLice: project license
|
|
76
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
77
|
+
:return str: string to send to specified output in ansi format
|
|
78
|
+
"""
|
|
79
|
+
string = StringIO()
|
|
80
|
+
|
|
81
|
+
console = Console(file=string, color_system="truecolor", safe_box=False)
|
|
82
|
+
|
|
83
|
+
table = Table(title="\nInfo")
|
|
84
|
+
table.add_column("Item", style="cyan")
|
|
85
|
+
table.add_column("Value", style="magenta")
|
|
86
|
+
_ = [table.add_row(k, v) for k, v in INFO.items()]
|
|
87
|
+
|
|
88
|
+
console.print(table)
|
|
89
|
+
|
|
90
|
+
if len(packages) == 0:
|
|
91
|
+
return f"{string.getvalue()}\nNo packages"
|
|
92
|
+
|
|
93
|
+
errors = [x for x in packages if x.get("httpErrorCode", 0) > 0]
|
|
94
|
+
if len(errors) > 0:
|
|
95
|
+
table = Table(title="\nList Of Errors")
|
|
96
|
+
table.add_column("Package", style="magenta")
|
|
97
|
+
_ = [table.add_row(x.get("name", "?")) for x in errors]
|
|
98
|
+
console.print(table)
|
|
99
|
+
|
|
100
|
+
table = Table(title="\nList Of Packages")
|
|
101
|
+
if name_bool := "name" in packages[0]:
|
|
102
|
+
table.add_column("Package", header_style="magenta")
|
|
103
|
+
if attestation_info := "attestation_info" in packages[0]:
|
|
104
|
+
table.add_column("Attestation Info", header_style="magenta")
|
|
105
|
+
|
|
106
|
+
attestation_info_lookup = {
|
|
107
|
+
AttestationInfo.NONE: "[red]Unsupported[/]",
|
|
108
|
+
AttestationInfo.SUPPORTED: "[yellow]Supported[/]",
|
|
109
|
+
AttestationInfo.PRESENT: "[cyan]Present[/]",
|
|
110
|
+
AttestationInfo.VALID: "[green]Valid[/]",
|
|
111
|
+
AttestationInfo.VERIFIED: "[green]Verified[/]",
|
|
112
|
+
}
|
|
113
|
+
_ = [
|
|
114
|
+
table.add_row(
|
|
115
|
+
*(
|
|
116
|
+
([x.get("name")] if name_bool else [])
|
|
117
|
+
+ (
|
|
118
|
+
[attestation_info_lookup[x.get("attestation_info", AttestationInfo.NONE)]]
|
|
119
|
+
if attestation_info
|
|
120
|
+
else []
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
for x in packages
|
|
125
|
+
]
|
|
126
|
+
console.print(table)
|
|
127
|
+
return string.getvalue()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def plainText(
|
|
131
|
+
packages: list[dict[str, Any]],
|
|
132
|
+
) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Format to plain text.
|
|
135
|
+
|
|
136
|
+
:param License myLice: project license
|
|
137
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
138
|
+
:return str: string to send to specified output in plain text format
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
return stripAnsi(ansi(packages))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def markdown(
|
|
145
|
+
packages: list[dict[str, Any]],
|
|
146
|
+
) -> str:
|
|
147
|
+
"""
|
|
148
|
+
Format to markdown.
|
|
149
|
+
|
|
150
|
+
:param License myLice: project license
|
|
151
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
152
|
+
:return str: string to send to specified output in markdown format
|
|
153
|
+
"""
|
|
154
|
+
info = "\n".join(f"- {k}: {v}" for k, v in INFO.items())
|
|
155
|
+
strBuf = [f"## Info\n\n{info}\n\n"]
|
|
156
|
+
|
|
157
|
+
if len(packages) == 0:
|
|
158
|
+
return f"{strBuf[0]}\nNo packages"
|
|
159
|
+
|
|
160
|
+
strBuf.append("## Packages\n\nFind a list of packages below\n")
|
|
161
|
+
packages = sorted(packages, key=lambda i: i.get("name", "?"))
|
|
162
|
+
|
|
163
|
+
# Summary Table
|
|
164
|
+
strBuf.append("|Package|Attestation Info|\n|:--|:--|")
|
|
165
|
+
strBuf.extend([f"|{pkg.get('name')}||{pkg.get('attestation_info')}" for pkg in packages])
|
|
166
|
+
|
|
167
|
+
# Details
|
|
168
|
+
params_use_in_markdown = {
|
|
169
|
+
"author": "Author",
|
|
170
|
+
"is_attestation_verified": "Attestation Verified",
|
|
171
|
+
"is_attestation_valid": "Attestation Valid",
|
|
172
|
+
"is_attestation_present": "Attestation Present",
|
|
173
|
+
"is_supported_publisher": "Attestation Supported",
|
|
174
|
+
"size": "Size",
|
|
175
|
+
}
|
|
176
|
+
for pkg in packages:
|
|
177
|
+
pkg_ordered_dict = OrderedDict(
|
|
178
|
+
(param, pkg[param]) for param in params_use_in_markdown if param in pkg
|
|
179
|
+
)
|
|
180
|
+
strBuf.extend(
|
|
181
|
+
[
|
|
182
|
+
f"\n### {pkg.get('namever')}\n",
|
|
183
|
+
*(f"- {params_use_in_markdown[k]}: {v}" for k, v in pkg_ordered_dict.items()),
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
return "\n".join(strBuf) + "\n"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def html(
|
|
190
|
+
packages: list[dict[str, Any]],
|
|
191
|
+
) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Format to html.
|
|
194
|
+
|
|
195
|
+
:param License myLice: project license
|
|
196
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
197
|
+
:return str: string to send to specified output in html format
|
|
198
|
+
"""
|
|
199
|
+
html = markdownlib.markdown(
|
|
200
|
+
markdown(packages=packages),
|
|
201
|
+
extensions=["tables"],
|
|
202
|
+
)
|
|
203
|
+
return (THISDIR / "html.template").read_text("utf-8").replace("{html}", html)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def raw(packages: list[dict[str, Any]]) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Format to json.
|
|
209
|
+
|
|
210
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
211
|
+
:return str: string to send to specified output in json format
|
|
212
|
+
"""
|
|
213
|
+
return json.dumps(
|
|
214
|
+
{
|
|
215
|
+
"info": INFO,
|
|
216
|
+
"packages": packages,
|
|
217
|
+
},
|
|
218
|
+
indent="\t",
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def rawCsv(
|
|
223
|
+
packages: list[dict[str, Any]],
|
|
224
|
+
) -> str:
|
|
225
|
+
"""
|
|
226
|
+
Format to csv.
|
|
227
|
+
|
|
228
|
+
:param list[dict[str, Any]] packages: list of PackageInfo, representes as a dict to format.
|
|
229
|
+
:return str: string to send to specified output in csv format
|
|
230
|
+
"""
|
|
231
|
+
if len(packages) == 0:
|
|
232
|
+
return ""
|
|
233
|
+
|
|
234
|
+
string = StringIO()
|
|
235
|
+
writer = csv.DictWriter(string, fieldnames=list(packages[0]), lineterminator="\n")
|
|
236
|
+
writer.writeheader()
|
|
237
|
+
writer.writerows(packages)
|
|
238
|
+
return string.getvalue()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def fmt(
|
|
242
|
+
format_: str,
|
|
243
|
+
packages: list[PackageInfo],
|
|
244
|
+
hide_parameters: set[str] | None = None,
|
|
245
|
+
*,
|
|
246
|
+
show_only_failing: bool = False,
|
|
247
|
+
) -> str:
|
|
248
|
+
"""
|
|
249
|
+
Format to a given format by `format_`.
|
|
250
|
+
|
|
251
|
+
:param list[PackageInfo] packages: list of packages to format.
|
|
252
|
+
:param set[str] hide_parameters: set of parameters to ignore in the output.
|
|
253
|
+
:param bool show_only_failing: output only failing packages, defaults to False.
|
|
254
|
+
:return str: string to send to specified output in ansi format
|
|
255
|
+
"""
|
|
256
|
+
hide_parameters = hide_parameters or set()
|
|
257
|
+
if show_only_failing:
|
|
258
|
+
packages = [x for x in packages if not x.is_attestation_verified]
|
|
259
|
+
|
|
260
|
+
pkgs: list[dict[str, Any]] = [x.get_filtered_dict(hide_parameters) for x in packages]
|
|
261
|
+
|
|
262
|
+
return formatMap.get(format_, plainText)(pkgs)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
formatMap = {
|
|
266
|
+
"json": raw,
|
|
267
|
+
"markdown": markdown,
|
|
268
|
+
"html": html,
|
|
269
|
+
"csv": rawCsv,
|
|
270
|
+
"ansi": ansi,
|
|
271
|
+
"simple": plainText,
|
|
272
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<style>
|
|
5
|
+
body {
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
7
|
+
color: #222;
|
|
8
|
+
}
|
|
9
|
+
h2 {
|
|
10
|
+
color: #333;
|
|
11
|
+
margin-bottom: 10px;
|
|
12
|
+
}
|
|
13
|
+
ul {
|
|
14
|
+
list-style-type: none;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
17
|
+
li {
|
|
18
|
+
margin-bottom: 5px;
|
|
19
|
+
}
|
|
20
|
+
table {
|
|
21
|
+
border-collapse: collapse;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
th,
|
|
25
|
+
td {
|
|
26
|
+
border: 1px solid #ddd;
|
|
27
|
+
padding: 8px;
|
|
28
|
+
text-align: left;
|
|
29
|
+
}
|
|
30
|
+
th {
|
|
31
|
+
background-color: #f2f2f2;
|
|
32
|
+
}
|
|
33
|
+
tr:nth-child(even) {
|
|
34
|
+
background-color: #f2f2f2;
|
|
35
|
+
}
|
|
36
|
+
tr:hover {
|
|
37
|
+
background-color: #ddd;
|
|
38
|
+
}
|
|
39
|
+
h3 {
|
|
40
|
+
color: #444;
|
|
41
|
+
}
|
|
42
|
+
@media (prefers-color-scheme: dark) {
|
|
43
|
+
body {
|
|
44
|
+
background-color: #222;
|
|
45
|
+
color: #eee;
|
|
46
|
+
}
|
|
47
|
+
h2 {
|
|
48
|
+
color: #ccc;
|
|
49
|
+
}
|
|
50
|
+
h3 {
|
|
51
|
+
color: #eee;
|
|
52
|
+
}
|
|
53
|
+
th {
|
|
54
|
+
background-color: #333;
|
|
55
|
+
color: #eee;
|
|
56
|
+
}
|
|
57
|
+
tr:nth-child(even) {
|
|
58
|
+
background-color: #333;
|
|
59
|
+
}
|
|
60
|
+
tr:hover {
|
|
61
|
+
background-color: #444;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
{html}
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from attestationcheck.models.defaultonnone import DefaultOnNoneModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Envelope(DefaultOnNoneModel):
|
|
7
|
+
signature: str = ""
|
|
8
|
+
statement: str = ""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class KindVersion(DefaultOnNoneModel):
|
|
12
|
+
kind: str = ""
|
|
13
|
+
version: str = ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LogId(DefaultOnNoneModel):
|
|
17
|
+
key_id: str = ""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InclusionPromise(DefaultOnNoneModel):
|
|
21
|
+
signed_entry_timestamp: str = ""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Checkpoint(DefaultOnNoneModel):
|
|
25
|
+
envelope: str = ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InclusionProof(DefaultOnNoneModel):
|
|
29
|
+
checkpoint: Checkpoint = Checkpoint()
|
|
30
|
+
hashes: list[str] = Field(default_factory=list)
|
|
31
|
+
log_index: str = ""
|
|
32
|
+
root_hash: str = ""
|
|
33
|
+
tree_size: str = ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TransparencyEntry(DefaultOnNoneModel):
|
|
37
|
+
canonicalizedBody: str = ""
|
|
38
|
+
inclusionPromise: InclusionPromise = InclusionPromise()
|
|
39
|
+
inclusionProof: InclusionProof = InclusionProof()
|
|
40
|
+
|
|
41
|
+
integratedTime: int = 0
|
|
42
|
+
kindVersion: KindVersion = KindVersion()
|
|
43
|
+
logId: LogId = LogId()
|
|
44
|
+
logIndex: str = ""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class VerificationMaterial(DefaultOnNoneModel):
|
|
48
|
+
certificate: str = ""
|
|
49
|
+
transparency_entries: list[TransparencyEntry] = Field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Attestation(DefaultOnNoneModel):
|
|
53
|
+
envelope: Envelope = Envelope()
|
|
54
|
+
verification_material: VerificationMaterial = VerificationMaterial()
|
|
55
|
+
version: int = 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Publisher(DefaultOnNoneModel):
|
|
59
|
+
claims: dict = Field(default_factory=dict)
|
|
60
|
+
environment: str = ""
|
|
61
|
+
kind: str = ""
|
|
62
|
+
repository: str = ""
|
|
63
|
+
workflow: str = ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AttestationBundle(DefaultOnNoneModel):
|
|
67
|
+
attestations: list[Attestation] = Field(default_factory=list)
|
|
68
|
+
publisher: Publisher = Publisher()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Provenance(DefaultOnNoneModel):
|
|
72
|
+
attestation_bundles: list[AttestationBundle]
|
|
73
|
+
version: int = 1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import field
|
|
4
|
+
|
|
5
|
+
from attestationcheck.models.defaultonnone import DefaultOnNoneModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LC_Config(DefaultOnNoneModel):
|
|
9
|
+
"""LC_Config type."""
|
|
10
|
+
|
|
11
|
+
file: str | None
|
|
12
|
+
format: str | None
|
|
13
|
+
pypi_api: str | None
|
|
14
|
+
show_only_failing: bool
|
|
15
|
+
zero: bool
|
|
16
|
+
|
|
17
|
+
requirements_paths: set[str] = field(default_factory=set)
|
|
18
|
+
groups: set[str] = field(default_factory=set)
|
|
19
|
+
extras: set[str] = field(default_factory=set)
|
|
20
|
+
skip_dependencies: set[str] = field(default_factory=set)
|
|
21
|
+
hide_output_parameters: set[str] = field(default_factory=set)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, model_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DefaultOnNoneModel(BaseModel):
|
|
7
|
+
@model_validator(mode="before")
|
|
8
|
+
@classmethod
|
|
9
|
+
def default_on_none(cls, values: Any) -> Any | dict[Any, Any]:
|
|
10
|
+
if not isinstance(values, dict):
|
|
11
|
+
return values
|
|
12
|
+
|
|
13
|
+
result = dict(values)
|
|
14
|
+
|
|
15
|
+
for name, field in cls.model_fields.items():
|
|
16
|
+
if name in result and result[name] is None:
|
|
17
|
+
if field.default_factory is not None:
|
|
18
|
+
result[name] = field.default_factory()
|
|
19
|
+
elif field.default is not None:
|
|
20
|
+
result[name] = field.default
|
|
21
|
+
|
|
22
|
+
return result
|