openbadgeslib 3.5.0__tar.gz → 3.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/Changelog.txt +41 -0
- {openbadgeslib-3.5.0/openbadgeslib.egg-info → openbadgeslib-3.5.1}/PKG-INFO +1 -1
- openbadgeslib-3.5.1/openbadgeslib/logs.py +41 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob2/models.py +22 -3
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/credential.py +16 -1
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/did.py +10 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/verifier.py +9 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/openbadges_keygenerator.py +16 -11
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/openbadges_publish.py +13 -2
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/openbadges_signer.py +18 -15
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/util.py +1 -1
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1/openbadgeslib.egg-info}/PKG-INFO +1 -1
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob2_models.py +13 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_credential.py +16 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_did.py +6 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_signer_cli_ldp.py +4 -3
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_verifier.py +23 -0
- openbadgeslib-3.5.0/openbadgeslib/logs.py +0 -85
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/LICENSE.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/MANIFEST.in +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/README.md +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/docs/README.md +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/_jws/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/_jws/exceptions.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/_jws/utils.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/badge.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/baking.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/config.ini.example +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/confparser.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/errors.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/keys.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/mail.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob1/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob1/badge.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob1/signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob1/verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob2/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob2/signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob2/verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/__init__.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/eudi.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/ldp.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/status.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/status_list.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/status_registry.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/openbadges_init.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/openbadges_verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/py.typed +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib.egg-info/SOURCES.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib.egg-info/dependency_links.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib.egg-info/entry_points.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib.egg-info/requires.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib.egg-info/top_level.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/pyproject.toml +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/setup.cfg +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/config1.ini +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conformance/Dockerfile.ob3 +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conformance/README.md +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conformance/conftest.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conformance/docker-compose.yml +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conformance/test_official_validators.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/conftest.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/ob_schemas/README.md +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/ob_schemas/refresh.sh +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/README.md +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/images/sample1.png +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/images/sample1.svg +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/images/userimage01.svg +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/ldp_helpers.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/logo Python Espan/314/203a.svg" +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/logo Python Espa/303/261a.svg" +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/runtests.sh +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_cli_json.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_cli_smoke.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_confparser.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_docs.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_eddsa.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_jws.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_key_operation.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob1_badge_io.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob1_signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob1_verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob2_cli.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob2_signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob2_verifier.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_conformance_schema.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_contexts.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_did_doc.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_eudi_sd_jwt.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_ldp.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_ldp_cli.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_ldp_sign.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_publish_cli.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_signer.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_status.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_status_list.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_ob3_status_registry.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_sign_ecc.pem +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_sign_rsa.pem +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_util.py +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_verify_ecc.pem +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/test_verify_rsa.pem +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/withoutxmlheader.svg +0 -0
- {openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/withxmlheader.svg +0 -0
|
@@ -4,6 +4,47 @@ OpenBadgesLib - Changelog
|
|
|
4
4
|
Newest first. Dates are ISO 8601 (YYYY-MM-DD).
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
* v3.5.1 - 2026-07-07
|
|
8
|
+
|
|
9
|
+
- fix(ob3): a naive datetime passed as the issuance/expiration date is now
|
|
10
|
+
assumed UTC (matching status_registry._iso_z), not local time — so
|
|
11
|
+
validFrom/validUntil no longer silently shift by the host's offset and
|
|
12
|
+
stay consistent with the nbf/exp JWT claims.
|
|
13
|
+
|
|
14
|
+
- fix(ob3): the JWT verifier now cross-checks the `jti` claim against the
|
|
15
|
+
credential `id` (OB3 §8.2 binds them). A validly signed token whose jti
|
|
16
|
+
disagrees with — or omits — the credential id is rejected, alongside the
|
|
17
|
+
existing iss/nbf/sub checks.
|
|
18
|
+
|
|
19
|
+
- fix(ob3): `_b58btc_decode` bounds its input to 128 characters before the
|
|
20
|
+
O(n^2) base58 decode, so a hostile did:key / publicKeyMultibase in a
|
|
21
|
+
fetched did:web or Data Integrity document (up to 5 MiB / 256 KiB) can no
|
|
22
|
+
longer burn CPU. No real multikey exceeds ~70 characters.
|
|
23
|
+
|
|
24
|
+
- fix(ob2): an `evidence` array (which OB 2.0 permits) is preserved when
|
|
25
|
+
parsing an Assertion instead of being silently dropped — `_iri_or_none`
|
|
26
|
+
returned None for a list. A single value still collapses to its IRI.
|
|
27
|
+
|
|
28
|
+
- fix(cli): `openbadges-signer -M/--mail-badge` now prints a clear notice
|
|
29
|
+
on the default OB3 path (it was a silent no-op there); the stale
|
|
30
|
+
`openbadges-keygenerator -t ED25519` hint on the LDP path is corrected to
|
|
31
|
+
the real `key_type = ED25519` config key; and the OB1 mail path reads its
|
|
32
|
+
`[smtp]` config inside the try, so a missing `[smtp]` section is reported
|
|
33
|
+
cleanly after signing instead of raising a traceback.
|
|
34
|
+
|
|
35
|
+
- fix(publish): `openbadges-publish -V 3` no longer aborts mid-loop when a
|
|
36
|
+
badge has an unreadable key or corrupt registry — it skips that badge
|
|
37
|
+
with a notice (like the did.json path already did), regenerates the
|
|
38
|
+
others, and exits non-zero with a summary. An urgent revocation is no
|
|
39
|
+
longer masked by an unrelated half-configured badge.
|
|
40
|
+
|
|
41
|
+
- refactor(keygen): `openbadges-keygenerator` uses `enable_debug_logging`
|
|
42
|
+
instead of the legacy `Logger` class (now removed from logs.py), so key
|
|
43
|
+
generation no longer requires the `[paths]`/`[logs]` config sections just
|
|
44
|
+
to emit console lines (they raised a raw KeyError/FileNotFoundError), and
|
|
45
|
+
a missing `[issuer] name` degrades gracefully.
|
|
46
|
+
|
|
47
|
+
|
|
7
48
|
* v3.5.0 - 2026-07-07
|
|
8
49
|
|
|
9
50
|
- fix(status): serialise status-registry writes with an exclusive
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 3.5.
|
|
3
|
+
Version: 3.5.1
|
|
4
4
|
Summary: Production-ready Python library & CLI for Open Badges 3.0 (W3C Verifiable Credentials): issue, verify, and revoke JWT-VC credentials with Bitstring Status Lists and did:web — plus strict OB 2.0 and legacy OB 1.0.
|
|
5
5
|
Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
|
|
6
6
|
License: LGPLv3
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
OpenBadges Library
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2014-2026, Luis González Fernández, luisgf@luisgf.es
|
|
6
|
+
Copyright (c) 2014-2026, Jesús Cea Avión, jcea@jcea.es
|
|
7
|
+
|
|
8
|
+
All rights reserved.
|
|
9
|
+
|
|
10
|
+
This library is free software; you can redistribute it and/or
|
|
11
|
+
modify it under the terms of the GNU Lesser General Public
|
|
12
|
+
License as published by the Free Software Foundation; either
|
|
13
|
+
version 3.0 of the License, or (at your option) any later version.
|
|
14
|
+
|
|
15
|
+
This library is distributed in the hope that it will be useful,
|
|
16
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
18
|
+
Lesser General Public License for more details.
|
|
19
|
+
|
|
20
|
+
You should have received a copy of the GNU Lesser General Public
|
|
21
|
+
License along with this library.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def enable_debug_logging(debug: bool = False) -> None:
|
|
29
|
+
"""Route library log messages to the console for the CLI tools.
|
|
30
|
+
|
|
31
|
+
With ``debug=True`` DEBUG-level messages are shown; otherwise only INFO and
|
|
32
|
+
above. Idempotent — safe to call once per CLI invocation (it will not stack
|
|
33
|
+
duplicate console handlers).
|
|
34
|
+
"""
|
|
35
|
+
root = logging.getLogger()
|
|
36
|
+
if not any(getattr(h, '_obl_console', False) for h in root.handlers):
|
|
37
|
+
handler: Any = logging.StreamHandler()
|
|
38
|
+
handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
|
|
39
|
+
handler._obl_console = True
|
|
40
|
+
root.addHandler(handler)
|
|
41
|
+
root.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
import uuid
|
|
33
33
|
from dataclasses import dataclass, field
|
|
34
34
|
from datetime import datetime, timezone
|
|
35
|
-
from typing import Any, List, Optional
|
|
35
|
+
from typing import Any, List, Optional, Union
|
|
36
36
|
|
|
37
37
|
from ..util import hash_email
|
|
38
38
|
|
|
@@ -122,6 +122,25 @@ def _iri_or_none(value: Any) -> Optional[str]:
|
|
|
122
122
|
return None
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _iri_or_iris(value: Any) -> Optional[Union[str, List[str]]]:
|
|
126
|
+
"""Like :func:`_iri_or_none`, but keep an array instead of dropping it.
|
|
127
|
+
|
|
128
|
+
OB 2.0 lets ``evidence`` be a bare IRI, an object with an ``id``, or an
|
|
129
|
+
array of either. A single value collapses to its IRI string (unchanged
|
|
130
|
+
behaviour); an array yields the list of IRIs it carries. None when nothing
|
|
131
|
+
usable is present — so a conformant multi-evidence assertion is no longer
|
|
132
|
+
silently discarded on parse.
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(value, list):
|
|
135
|
+
iris = []
|
|
136
|
+
for item in value:
|
|
137
|
+
iri = _iri_or_none(item)
|
|
138
|
+
if iri is not None:
|
|
139
|
+
iris.append(iri)
|
|
140
|
+
return iris or None
|
|
141
|
+
return _iri_or_none(value)
|
|
142
|
+
|
|
143
|
+
|
|
125
144
|
def hash_identity(email: str, salt: Optional[str]) -> str:
|
|
126
145
|
"""Return the ``sha256$<hex>`` IdentityHash for an email plus optional salt."""
|
|
127
146
|
return "sha256$" + hash_email(email, salt if salt is not None else "").decode("ascii")
|
|
@@ -209,7 +228,7 @@ class Assertion:
|
|
|
209
228
|
issued_on: Optional[datetime] = None # defaults to now (UTC)
|
|
210
229
|
expires: Optional[datetime] = None
|
|
211
230
|
image: Optional[str] = None
|
|
212
|
-
evidence: Optional[str] = None
|
|
231
|
+
evidence: Optional[Union[str, List[str]]] = None
|
|
213
232
|
narrative: Optional[str] = None
|
|
214
233
|
|
|
215
234
|
def __post_init__(self) -> None:
|
|
@@ -265,7 +284,7 @@ class Assertion:
|
|
|
265
284
|
issued_on=issued_on,
|
|
266
285
|
expires=expires,
|
|
267
286
|
image=_iri_or_none(d.get("image")),
|
|
268
|
-
evidence=
|
|
287
|
+
evidence=_iri_or_iris(d.get("evidence")),
|
|
269
288
|
narrative=d.get("narrative") if isinstance(d.get("narrative"), str) else None,
|
|
270
289
|
)
|
|
271
290
|
|
|
@@ -56,7 +56,14 @@ def _validate_context(ctx: Any) -> None:
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def _iso(dt: datetime) -> str:
|
|
59
|
-
"""Return a datetime as an ISO 8601 string with Z suffix.
|
|
59
|
+
"""Return a datetime as an ISO 8601 string with Z suffix.
|
|
60
|
+
|
|
61
|
+
A naive datetime is assumed to be UTC (matching status_registry._iso_z),
|
|
62
|
+
not local time — so validFrom/validUntil never silently shift by the host's
|
|
63
|
+
offset and stay consistent with the nbf/exp JWT claims.
|
|
64
|
+
"""
|
|
65
|
+
if dt.tzinfo is None:
|
|
66
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
60
67
|
return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
61
68
|
|
|
62
69
|
|
|
@@ -131,6 +138,14 @@ class OpenBadgeCredential:
|
|
|
131
138
|
self.id = f"urn:uuid:{uuid.uuid4()}"
|
|
132
139
|
if self.issuance_date is None:
|
|
133
140
|
self.issuance_date = datetime.now(timezone.utc)
|
|
141
|
+
elif self.issuance_date.tzinfo is None:
|
|
142
|
+
# A naive datetime is assumed UTC, so validFrom (string) and nbf
|
|
143
|
+
# (epoch int) agree instead of diverging by the host's offset.
|
|
144
|
+
self.issuance_date = self.issuance_date.replace(tzinfo=timezone.utc)
|
|
145
|
+
if self.expiration_date is not None \
|
|
146
|
+
and self.expiration_date.tzinfo is None:
|
|
147
|
+
self.expiration_date = self.expiration_date.replace(
|
|
148
|
+
tzinfo=timezone.utc)
|
|
134
149
|
if self.name is None:
|
|
135
150
|
self.name = self.achievement.name
|
|
136
151
|
|
|
@@ -95,7 +95,17 @@ def _multicodec_pubkey_to_pem(data: bytes) -> bytes:
|
|
|
95
95
|
raise OB3VerificationError("unsupported did:key multicodec 0x%x" % code)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
#: Bound on base58btc input length. Decoding is O(n^2) in the length (big-int
|
|
99
|
+
#: accumulation), and a hostile did:key / publicKeyMultibase in a fetched
|
|
100
|
+
#: document could be hundreds of KB; no real multikey exceeds ~70 characters.
|
|
101
|
+
_MAX_B58_LEN = 128
|
|
102
|
+
|
|
103
|
+
|
|
98
104
|
def _b58btc_decode(value: str) -> bytes:
|
|
105
|
+
if len(value) > _MAX_B58_LEN:
|
|
106
|
+
raise OB3VerificationError(
|
|
107
|
+
"base58btc value too long: %d characters (max %d)"
|
|
108
|
+
% (len(value), _MAX_B58_LEN))
|
|
99
109
|
num = 0
|
|
100
110
|
for ch in value:
|
|
101
111
|
idx = _B58_INDEX.get(ch)
|
|
@@ -311,6 +311,15 @@ class OB3Verifier:
|
|
|
311
311
|
raise OB3VerificationError("JWT payload is missing the required 'sub' claim")
|
|
312
312
|
if sub is not None and sub != subject_id:
|
|
313
313
|
raise OB3VerificationError("JWT 'sub' does not match the credentialSubject id")
|
|
314
|
+
# jti is the registered claim bound to the credential id (OB3 §8.2); the
|
|
315
|
+
# signer always emits it. Require it whenever the body carries an id, and
|
|
316
|
+
# reject any mismatch, so a token cannot claim one id and carry another.
|
|
317
|
+
jti = payload.get("jti")
|
|
318
|
+
vc_id = vc.get("id")
|
|
319
|
+
if vc_id is not None and jti is None:
|
|
320
|
+
raise OB3VerificationError("JWT payload is missing the required 'jti' claim")
|
|
321
|
+
if jti is not None and jti != vc_id:
|
|
322
|
+
raise OB3VerificationError("JWT 'jti' does not match the credential id")
|
|
314
323
|
|
|
315
324
|
return credential
|
|
316
325
|
|
|
@@ -30,14 +30,17 @@
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
import argparse
|
|
33
|
+
import logging
|
|
33
34
|
import os
|
|
34
35
|
import sys
|
|
35
36
|
|
|
36
|
-
from .logs import
|
|
37
|
+
from .logs import enable_debug_logging
|
|
37
38
|
from .keys import KeyFactory, KeyType
|
|
38
39
|
from .confparser import read_config_or_exit, resolve_badge_section
|
|
39
40
|
from .util import __version__
|
|
40
41
|
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
41
44
|
# Entry Point
|
|
42
45
|
|
|
43
46
|
|
|
@@ -100,15 +103,17 @@ def main() -> None:
|
|
|
100
103
|
print('[!] Key file is present at %s' % i)
|
|
101
104
|
sys.exit(1)
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
# Key generation must not require [paths]/[logs]/[issuer]: the legacy Logger
|
|
107
|
+
# opened general.log/signer.log (KeyError/FileNotFoundError on an incomplete
|
|
108
|
+
# config) just to emit these console lines. The module logger needs none.
|
|
109
|
+
enable_debug_logging(args.debug)
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
logger.debug("key_type=%s private=%s public=%s",
|
|
112
|
+
key_type.name, private_key, public_key)
|
|
113
|
+
issuer_name = (conf['issuer'].get('name', '?')
|
|
114
|
+
if conf.has_section('issuer') else '?')
|
|
115
|
+
logger.info("Generating OpenBadges %s %s key pair for issuer '%s'",
|
|
116
|
+
args.ob_version, key_type.name, issuer_name)
|
|
112
117
|
|
|
113
118
|
kf = KeyFactory(key_type)
|
|
114
119
|
priv_key_pem, pub_key_pem = kf.generate_keypair()
|
|
@@ -116,8 +121,8 @@ def main() -> None:
|
|
|
116
121
|
_write_pem_file(private_key, priv_key_pem, 0o600)
|
|
117
122
|
_write_pem_file(public_key, pub_key_pem, 0o644)
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
logger.info('Private key saved at: %s', private_key)
|
|
125
|
+
logger.info('Public key saved at: %s', public_key)
|
|
121
126
|
|
|
122
127
|
|
|
123
128
|
if __name__ == '__main__':
|
|
@@ -231,6 +231,7 @@ def _publish_ob3(args: argparse.Namespace, parser: argparse.ArgumentParser) -> N
|
|
|
231
231
|
print('[!] Cannot derive a did:web identifier: %s' % exc)
|
|
232
232
|
sys.exit(-1)
|
|
233
233
|
|
|
234
|
+
failures = [] # badges skipped (unreadable key / registry)
|
|
234
235
|
umask = os.umask(0o077) # rwx------
|
|
235
236
|
try:
|
|
236
237
|
os.makedirs(args.output, exist_ok=True)
|
|
@@ -264,8 +265,13 @@ def _publish_ob3(args: argparse.Namespace, parser: argparse.ArgumentParser) -> N
|
|
|
264
265
|
with open(conf[name]['private_key'], 'rb') as key:
|
|
265
266
|
priv_pem = key.read()
|
|
266
267
|
except (StatusError, OSError) as exc:
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
# Isolate a per-badge failure (unreadable key, corrupt registry)
|
|
269
|
+
# like the did.json skip above: don't abort the whole publish —
|
|
270
|
+
# a badge_2 mid-configuration must not make an urgent badge_1
|
|
271
|
+
# revocation appear to fail after its list was already written.
|
|
272
|
+
print('[!] Skipping [%s] status lists — %s' % (name, exc))
|
|
273
|
+
failures.append(name)
|
|
274
|
+
continue
|
|
269
275
|
algorithm = alg_for_key_type(detect_key_type(priv_pem))
|
|
270
276
|
|
|
271
277
|
badge_dir = os.path.join(args.output, name)
|
|
@@ -285,8 +291,13 @@ def _publish_ob3(args: argparse.Namespace, parser: argparse.ArgumentParser) -> N
|
|
|
285
291
|
finally:
|
|
286
292
|
os.umask(umask)
|
|
287
293
|
|
|
294
|
+
if failures:
|
|
295
|
+
print('[!] %d badge(s) skipped (see above): %s — their status lists '
|
|
296
|
+
'were NOT regenerated' % (len(failures), ', '.join(failures)))
|
|
288
297
|
print('Please configure your Web server to publish the folder %s as %s' %
|
|
289
298
|
(args.output, publish_url))
|
|
299
|
+
if failures:
|
|
300
|
+
sys.exit(1)
|
|
290
301
|
print('[i] Issuer DID: %s' % did)
|
|
291
302
|
if ':' not in did[len('did:web:'):]:
|
|
292
303
|
print('[i] A bare-host did:web resolves at '
|
|
@@ -268,26 +268,25 @@ def _sign_ob1(args: argparse.Namespace, conf: configparser.ConfigParser, badge:
|
|
|
268
268
|
print('[!] Could not write sign log: %s' % err)
|
|
269
269
|
|
|
270
270
|
if bool(args.mail_badge):
|
|
271
|
-
server = conf['smtp']['smtp_server']
|
|
272
|
-
port = int(conf['smtp']['smtp_port'])
|
|
273
|
-
# configparser stores everything as strings; 'False' would be truthy.
|
|
274
|
-
use_ssl = conf['smtp'].getboolean('use_ssl', False)
|
|
275
|
-
mail_from = conf['smtp']['mail_from']
|
|
276
|
-
username = conf['smtp'].get('username')
|
|
277
|
-
password = conf['smtp'].get('password')
|
|
278
|
-
|
|
279
271
|
try:
|
|
272
|
+
server = conf['smtp']['smtp_server']
|
|
273
|
+
port = int(conf['smtp']['smtp_port'])
|
|
274
|
+
# configparser stores everything as strings; 'False' is truthy.
|
|
275
|
+
use_ssl = conf['smtp'].getboolean('use_ssl', False)
|
|
276
|
+
mail_from = conf['smtp']['mail_from']
|
|
277
|
+
username = conf['smtp'].get('username')
|
|
278
|
+
password = conf['smtp'].get('password')
|
|
280
279
|
mail = BadgeMail(server, port, use_ssl, mail_from, username, password)
|
|
281
280
|
subject, body = mail.get_mail_content(conf[badge]['mail'])
|
|
282
281
|
mail.set_subject(subject)
|
|
283
282
|
mail.set_body(body)
|
|
284
283
|
mail.send(badge_signed)
|
|
285
284
|
except (ValueError, OSError, KeyError) as err:
|
|
286
|
-
# e.g. a
|
|
287
|
-
#
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
285
|
+
# e.g. a missing [smtp] section or key (KeyError), a username
|
|
286
|
+
# set without use_ssl=True (ValueError), or a missing/unreadable
|
|
287
|
+
# mail template file (OSError) — the badge is already signed and
|
|
288
|
+
# saved, so report the config error instead of crashing with a
|
|
289
|
+
# traceback. The [smtp] reads are inside the try for that reason.
|
|
291
290
|
print('[!] Could not send mail: %s' % err)
|
|
292
291
|
|
|
293
292
|
print('%s at: %s' % (msg, badge_file_out))
|
|
@@ -401,6 +400,9 @@ def _sign_ob3(args: argparse.Namespace, conf: configparser.ConfigParser, badge:
|
|
|
401
400
|
except OSError as err:
|
|
402
401
|
print('[!] Could not write sign log: %s' % err)
|
|
403
402
|
print('%s at: %s' % (msg, badge_file_out))
|
|
403
|
+
if args.mail_badge:
|
|
404
|
+
print('[i] --mail-badge is not supported for -V 3; the badge was saved '
|
|
405
|
+
'but not emailed.')
|
|
404
406
|
|
|
405
407
|
|
|
406
408
|
def _sign_ob3_ldp(badge: str, badge_obj: Badge,
|
|
@@ -416,8 +418,9 @@ def _sign_ob3_ldp(badge: str, badge_obj: Badge,
|
|
|
416
418
|
|
|
417
419
|
if key_type is not KeyType.ED25519:
|
|
418
420
|
print("[!] --proof-format ldp (eddsa-rdfc-2022) requires an Ed25519 "
|
|
419
|
-
"key; [%s] uses %s.
|
|
420
|
-
"
|
|
421
|
+
"key; [%s] uses %s. Set 'key_type = ED25519' in that badge's "
|
|
422
|
+
"config section and regenerate its key with "
|
|
423
|
+
"openbadges-keygenerator -g" % (badge, key_type.value))
|
|
421
424
|
sys.exit(-1)
|
|
422
425
|
|
|
423
426
|
verification_method = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 3.5.
|
|
3
|
+
Version: 3.5.1
|
|
4
4
|
Summary: Production-ready Python library & CLI for Open Badges 3.0 (W3C Verifiable Credentials): issue, verify, and revoke JWT-VC credentials with Bitstring Status Lists and did:web — plus strict OB 2.0 and legacy OB 1.0.
|
|
5
5
|
Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
|
|
6
6
|
License: LGPLv3
|
|
@@ -130,6 +130,19 @@ class TestAssertion:
|
|
|
130
130
|
d['@context'] = [OB2_CONTEXT, 'https://example.com/extension']
|
|
131
131
|
assert Assertion.from_dict(d).badge == d['badge']
|
|
132
132
|
|
|
133
|
+
def test_from_dict_keeps_an_evidence_array(self):
|
|
134
|
+
# OB 2.0 allows an array of evidence (IRIs or objects); it must not be
|
|
135
|
+
# silently dropped on parse.
|
|
136
|
+
d = _assertion().to_dict()
|
|
137
|
+
d['evidence'] = ['https://e/ev1', {'id': 'https://e/ev2'}]
|
|
138
|
+
assert Assertion.from_dict(d).evidence == ['https://e/ev1',
|
|
139
|
+
'https://e/ev2']
|
|
140
|
+
|
|
141
|
+
def test_from_dict_keeps_single_evidence_as_string(self):
|
|
142
|
+
d = _assertion().to_dict()
|
|
143
|
+
d['evidence'] = 'https://e/only'
|
|
144
|
+
assert Assertion.from_dict(d).evidence == 'https://e/only'
|
|
145
|
+
|
|
133
146
|
def test_from_dict_rejects_wrong_type(self):
|
|
134
147
|
d = _assertion().to_dict()
|
|
135
148
|
d['type'] = 'BadgeClass'
|
|
@@ -306,3 +306,19 @@ class TestHelpers:
|
|
|
306
306
|
# calls .lower()); a non-string must be rejected here.
|
|
307
307
|
with pytest.raises(ValueError, match="must be a string"):
|
|
308
308
|
_require({'id': bad_value}, 'id', 'vc.credentialSubject')
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TestNaiveDatetimeAssumedUtc:
|
|
312
|
+
def test_naive_issuance_date_coerced_to_utc(self):
|
|
313
|
+
# A naive datetime is assumed UTC, so validFrom (string) and nbf (epoch)
|
|
314
|
+
# agree instead of diverging by the host's local offset.
|
|
315
|
+
cred = _make_credential(issuance_date=datetime(2026, 1, 1, 12, 0, 0))
|
|
316
|
+
assert cred.issuance_date.tzinfo is not None
|
|
317
|
+
assert cred.to_vc()['validFrom'] == '2026-01-01T12:00:00Z'
|
|
318
|
+
aware = datetime(2026, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
|
|
319
|
+
assert cred.to_jwt_payload()['nbf'] == int(aware.timestamp())
|
|
320
|
+
|
|
321
|
+
def test_naive_expiration_date_coerced_to_utc(self):
|
|
322
|
+
cred = _make_credential(expiration_date=datetime(2030, 6, 1, 0, 0, 0))
|
|
323
|
+
assert cred.expiration_date.tzinfo is not None
|
|
324
|
+
assert cred.to_vc()['validUntil'] == '2030-06-01T00:00:00Z'
|
|
@@ -69,6 +69,12 @@ class TestDidKey:
|
|
|
69
69
|
with pytest.raises(OB3VerificationError, match='base58'):
|
|
70
70
|
resolve_did('did:key:z0OIl') # 0, O, I, l are not in the alphabet
|
|
71
71
|
|
|
72
|
+
def test_overlong_base58_rejected(self):
|
|
73
|
+
# A hostile did:key must be length-bounded before the O(n^2) decode; no
|
|
74
|
+
# real multikey exceeds ~70 chars.
|
|
75
|
+
with pytest.raises(OB3VerificationError, match='too long'):
|
|
76
|
+
resolve_did('did:key:z' + 'a' * 200)
|
|
77
|
+
|
|
72
78
|
def test_non_z_multibase_rejected(self):
|
|
73
79
|
with pytest.raises(OB3VerificationError, match='base58btc'):
|
|
74
80
|
resolve_did('did:key:Qabc')
|
|
@@ -190,9 +190,10 @@ class TestSignerLdpCli:
|
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
class TestEd25519CliSupport:
|
|
193
|
-
# Badge.create_from_conf rejected key_type = ED25519 outright, so the
|
|
194
|
-
#
|
|
195
|
-
#
|
|
193
|
+
# Badge.create_from_conf rejected key_type = ED25519 outright, so the CLI
|
|
194
|
+
# could never sign with the keys openbadges-keygenerator produces from a
|
|
195
|
+
# key_type = ED25519 badge section — a prerequisite for LDP issuance,
|
|
196
|
+
# fixed alongside it.
|
|
196
197
|
|
|
197
198
|
def test_vc_jwt_with_ed25519_key_roundtrips(self, tmp_path,
|
|
198
199
|
ed25519_key_files, capsys):
|
|
@@ -611,3 +611,26 @@ class TestEndToEndRoundtrip:
|
|
|
611
611
|
token = OB3Verifier.extract_token_from_svg(signed_svg)
|
|
612
612
|
restored = ob3_rsa_verifier.verify(token)
|
|
613
613
|
assert restored.evidence_url == 'https://example.com/proof/123'
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class TestJtiBinding:
|
|
617
|
+
# The jti claim binds the token to the credential id (OB3 §8.2). A validly
|
|
618
|
+
# signed token whose jti disagrees with (or omits) the credential id must be
|
|
619
|
+
# rejected — sign_payload lets us forge exactly that.
|
|
620
|
+
def test_mismatched_jti_raises(
|
|
621
|
+
self, ob3_rsa_signer, ob3_rsa_verifier, ob3_credential
|
|
622
|
+
):
|
|
623
|
+
payload = ob3_credential.to_jwt_payload()
|
|
624
|
+
payload['jti'] = 'urn:uuid:00000000-0000-0000-0000-000000000000'
|
|
625
|
+
token = ob3_rsa_signer.sign_payload(payload)
|
|
626
|
+
with pytest.raises(OB3VerificationError, match="jti"):
|
|
627
|
+
ob3_rsa_verifier.verify(token)
|
|
628
|
+
|
|
629
|
+
def test_missing_jti_raises(
|
|
630
|
+
self, ob3_rsa_signer, ob3_rsa_verifier, ob3_credential
|
|
631
|
+
):
|
|
632
|
+
payload = ob3_credential.to_jwt_payload()
|
|
633
|
+
del payload['jti']
|
|
634
|
+
token = ob3_rsa_signer.sign_payload(payload)
|
|
635
|
+
with pytest.raises(OB3VerificationError, match="jti"):
|
|
636
|
+
ob3_rsa_verifier.verify(token)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
OpenBadges Library
|
|
4
|
-
|
|
5
|
-
Copyright (c) 2014-2026, Luis González Fernández, luisgf@luisgf.es
|
|
6
|
-
Copyright (c) 2014-2026, Jesús Cea Avión, jcea@jcea.es
|
|
7
|
-
|
|
8
|
-
All rights reserved.
|
|
9
|
-
|
|
10
|
-
This library is free software; you can redistribute it and/or
|
|
11
|
-
modify it under the terms of the GNU Lesser General Public
|
|
12
|
-
License as published by the Free Software Foundation; either
|
|
13
|
-
version 3.0 of the License, or (at your option) any later version.
|
|
14
|
-
|
|
15
|
-
This library is distributed in the hope that it will be useful,
|
|
16
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
18
|
-
Lesser General Public License for more details.
|
|
19
|
-
|
|
20
|
-
You should have received a copy of the GNU Lesser General Public
|
|
21
|
-
License along with this library.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import logging
|
|
25
|
-
import os
|
|
26
|
-
from typing import Any
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def enable_debug_logging(debug: bool = False) -> None:
|
|
30
|
-
"""Route library log messages to the console for the CLI tools.
|
|
31
|
-
|
|
32
|
-
With ``debug=True`` DEBUG-level messages are shown; otherwise only INFO and
|
|
33
|
-
above. Idempotent — safe to call once per CLI invocation (it will not stack
|
|
34
|
-
duplicate console handlers).
|
|
35
|
-
"""
|
|
36
|
-
root = logging.getLogger()
|
|
37
|
-
if not any(getattr(h, '_obl_console', False) for h in root.handlers):
|
|
38
|
-
handler: Any = logging.StreamHandler()
|
|
39
|
-
handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
|
|
40
|
-
handler._obl_console = True
|
|
41
|
-
root.addHandler(handler)
|
|
42
|
-
root.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Logger():
|
|
46
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
47
|
-
self.main = self.init_log(logger='general', base_log=kwargs['base_log'],
|
|
48
|
-
file=kwargs['general'])
|
|
49
|
-
self.signer = self.init_log(logger='signer', base_log=kwargs['base_log'],
|
|
50
|
-
file=kwargs['signer'])
|
|
51
|
-
try:
|
|
52
|
-
self.console = self.init_console(show_debug=kwargs['show_debug'])
|
|
53
|
-
except KeyError:
|
|
54
|
-
self.console = self.init_console()
|
|
55
|
-
|
|
56
|
-
def init_log(self, logger: Any = '', base_log: Any = None,
|
|
57
|
-
log_level: int = logging.INFO,
|
|
58
|
-
file: Any = None) -> logging.Logger:
|
|
59
|
-
logger = logging.getLogger(logger)
|
|
60
|
-
logger.setLevel(logging.DEBUG)
|
|
61
|
-
file_path = os.path.join(base_log, file)
|
|
62
|
-
|
|
63
|
-
""" Create a file handler """
|
|
64
|
-
handler = logging.FileHandler(file_path, "a",
|
|
65
|
-
encoding='utf-8', delay=False)
|
|
66
|
-
handler.setLevel(log_level)
|
|
67
|
-
formatter = logging.Formatter("%(asctime)s %(message)s")
|
|
68
|
-
handler.setFormatter(formatter)
|
|
69
|
-
logger.addHandler(handler)
|
|
70
|
-
|
|
71
|
-
return logger
|
|
72
|
-
|
|
73
|
-
def init_console(self, show_debug: bool = False) -> logging.Logger:
|
|
74
|
-
logger = logging.getLogger()
|
|
75
|
-
logger.setLevel(logging.NOTSET)
|
|
76
|
-
|
|
77
|
-
""" Console a console handler """
|
|
78
|
-
handler = logging.StreamHandler()
|
|
79
|
-
if not show_debug:
|
|
80
|
-
handler.setLevel(logging.INFO)
|
|
81
|
-
formatter = logging.Formatter("%(levelname)s - %(message)s")
|
|
82
|
-
handler.setFormatter(formatter)
|
|
83
|
-
logger.addHandler(handler)
|
|
84
|
-
|
|
85
|
-
return logger
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json
RENAMED
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json
RENAMED
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json
RENAMED
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/openbadgeslib/ob3/contexts/security-multikey-v1.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt
RENAMED
|
File without changes
|
{openbadgeslib-3.5.0 → openbadgeslib-3.5.1}/tests/fixtures/vc_di_eddsa/signed-credential.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|