tba-storage-core 0.4.0.dev2__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.
- tba_storage_core/__init__.py +25 -0
- tba_storage_core/components.py +35 -0
- tba_storage_core/framework.py +3 -0
- tba_storage_core/migrations/__init__.py +15 -0
- tba_storage_core/migrations/contract.py +74 -0
- tba_storage_core/migrations/env.py +7 -0
- tba_storage_core/migrations/helpers.py +219 -0
- tba_storage_core/migrations/versions/0001_initial_identity_tables.py +17 -0
- tba_storage_core/migrations/versions/0002_client_and_service_tables.py +23 -0
- tba_storage_core/migrations/versions/0003_authorization_runtime_tables.py +17 -0
- tba_storage_core/migrations/versions/0004_device_par_revocation_tables.py +17 -0
- tba_storage_core/migrations/versions/0005_session_logout_tables.py +17 -0
- tba_storage_core/migrations/versions/0006_key_rotation_and_audit_tables.py +17 -0
- tba_storage_core/migrations/versions/0007_browser_session_cookie_and_auth_code_linkage.py +50 -0
- tba_storage_core/migrations/versions/0008_refresh_token_family_state.py +44 -0
- tba_storage_core/migrations/versions/0009_admin_identity_bootstrap_and_password_recovery.py +48 -0
- tba_storage_core/migrations/versions/0010_realm_namespace_tables.py +33 -0
- tba_storage_core/migrations/versions/0011_delegation_grant_lifecycle_tables.py +39 -0
- tba_storage_core/migrations/versions/0012_key_version_and_token_provenance_tables.py +36 -0
- tba_storage_core/migrations/versions/0013_contract_durable_state_tables.py +59 -0
- tba_storage_core/migrations/versions/0014_optional_contract_state_tables.py +57 -0
- tba_storage_core/migrations/versions/0015_principal_identity_credential_tables.py +85 -0
- tba_storage_core/migrations/versions/0016_crypto_key_storage_tables.py +84 -0
- tba_storage_core/migrations/versions/0017_authorization_server_table.py +21 -0
- tba_storage_core/migrations/versions/0018_policy_tables.py +27 -0
- tba_storage_core/migrations/versions/0019_federation_and_invariant_tables.py +35 -0
- tba_storage_core/migrations/versions/0020_backchannel_logout_replay_table.py +21 -0
- tba_storage_core/migrations/versions/0021_authentication_challenge_table.py +21 -0
- tba_storage_core/migrations/versions/0022_authority_and_trust_graph_tables.py +35 -0
- tba_storage_core/migrations/versions/0023_artifact_and_token_profile_discriminators.py +65 -0
- tba_storage_core/migrations/versions/0024_credential_issuer_wallet_and_configuration.py +32 -0
- tba_storage_core/migrations/versions/0025_credential_issuance_and_status.py +34 -0
- tba_storage_core/migrations/versions/0026_presentation_transaction_and_replay.py +28 -0
- tba_storage_core/migrations/versions/0027_attestation_and_reference_material.py +32 -0
- tba_storage_core/migrations/versions/0028_spiffe_svid_and_trust_bundles.py +22 -0
- tba_storage_core/migrations/versions/0029_security_event_delivery.py +28 -0
- tba_storage_core/migrations/versions/0030_optional_did_and_gnap_state.py +37 -0
- tba_storage_core/migrations/versions/0031_certificate_trust_and_status.py +22 -0
- tba_storage_core/migrations/versions/0032_claim_definition_and_provenance.py +30 -0
- tba_storage_core/migrations/versions/0033_replay_reservations.py +18 -0
- tba_storage_core/migrations/versions/0034_dpop_replay_nonce_tables.py +21 -0
- tba_storage_core/migrations/versions/0035_webauthn_rp_ceremony_and_credentials.py +76 -0
- tba_storage_core/migrations/versions/0036_workload_credentials_artifacts_and_proof_replay.py +27 -0
- tba_storage_core/migrations/versions/0037_operator_tables.py +30 -0
- tba_storage_core/migrations/versions/0038_adopt_component_ownership.py +66 -0
- tba_storage_core/migrations/versions/0039_normalized_platform_iam.py +17 -0
- tba_storage_core/py.typed +0 -0
- tba_storage_core/storage.lock.toml +211 -0
- tba_storage_core/storage.toml +34 -0
- tba_storage_core/tables/__init__.py +179 -0
- tba_storage_core/tables/_graph_base.py +53 -0
- tba_storage_core/tables/_registry.py +200 -0
- tba_storage_core/tables/access_review_campaign/__init__.py +9 -0
- tba_storage_core/tables/access_review_campaign/_table.py +25 -0
- tba_storage_core/tables/access_review_decision/__init__.py +9 -0
- tba_storage_core/tables/access_review_decision/_table.py +27 -0
- tba_storage_core/tables/access_review_item/__init__.py +9 -0
- tba_storage_core/tables/access_review_item/_table.py +26 -0
- tba_storage_core/tables/attestation_appraisal_policy/__init__.py +3 -0
- tba_storage_core/tables/attestation_endorsement/__init__.py +3 -0
- tba_storage_core/tables/attestation_evidence/__init__.py +3 -0
- tba_storage_core/tables/attestation_reference_manifest/__init__.py +3 -0
- tba_storage_core/tables/attestation_reference_value/__init__.py +3 -0
- tba_storage_core/tables/attestation_result/__init__.py +3 -0
- tba_storage_core/tables/attestation_state.py +124 -0
- tba_storage_core/tables/attribute_policy/__init__.py +9 -0
- tba_storage_core/tables/attribute_policy/_table.py +40 -0
- tba_storage_core/tables/audit_event/__init__.py +10 -0
- tba_storage_core/tables/audit_event/_table.py +47 -0
- tba_storage_core/tables/auth_code/__init__.py +13 -0
- tba_storage_core/tables/auth_code/_table.py +41 -0
- tba_storage_core/tables/auth_session/__init__.py +13 -0
- tba_storage_core/tables/auth_session/_table.py +45 -0
- tba_storage_core/tables/authentication_challenge/__init__.py +7 -0
- tba_storage_core/tables/authentication_challenge/_table.py +38 -0
- tba_storage_core/tables/authority_derivation_graph/__init__.py +15 -0
- tba_storage_core/tables/authority_derivation_graph/_table.py +94 -0
- tba_storage_core/tables/authorization_invariant/__init__.py +5 -0
- tba_storage_core/tables/authorization_invariant/_table.py +34 -0
- tba_storage_core/tables/authorization_server/__init__.py +6 -0
- tba_storage_core/tables/authorization_server/_table.py +61 -0
- tba_storage_core/tables/authz_verification_report/__init__.py +9 -0
- tba_storage_core/tables/authz_verification_report/_table.py +29 -0
- tba_storage_core/tables/backchannel_logout_replay/__init__.py +5 -0
- tba_storage_core/tables/backchannel_logout_replay/_table.py +37 -0
- tba_storage_core/tables/certificate_record/__init__.py +3 -0
- tba_storage_core/tables/certificate_state.py +82 -0
- tba_storage_core/tables/certificate_status_snapshot/__init__.py +3 -0
- tba_storage_core/tables/claim_state.py +98 -0
- tba_storage_core/tables/client/__init__.py +9 -0
- tba_storage_core/tables/client/_table.py +41 -0
- tba_storage_core/tables/client_registration/__init__.py +10 -0
- tba_storage_core/tables/client_registration/_table.py +47 -0
- tba_storage_core/tables/consent/__init__.py +13 -0
- tba_storage_core/tables/consent/_table.py +57 -0
- tba_storage_core/tables/control_correctness_report/__init__.py +9 -0
- tba_storage_core/tables/control_correctness_report/_table.py +20 -0
- tba_storage_core/tables/credential/__init__.py +9 -0
- tba_storage_core/tables/credential/_table.py +74 -0
- tba_storage_core/tables/credential_api_key/__init__.py +9 -0
- tba_storage_core/tables/credential_api_key/_table.py +50 -0
- tba_storage_core/tables/credential_audit_event/__init__.py +9 -0
- tba_storage_core/tables/credential_audit_event/_table.py +25 -0
- tba_storage_core/tables/credential_client_secret/__init__.py +9 -0
- tba_storage_core/tables/credential_client_secret/_table.py +24 -0
- tba_storage_core/tables/credential_configuration/__init__.py +3 -0
- tba_storage_core/tables/credential_deferred_transaction/__init__.py +3 -0
- tba_storage_core/tables/credential_dpop_key/__init__.py +9 -0
- tba_storage_core/tables/credential_dpop_key/_table.py +22 -0
- tba_storage_core/tables/credential_ecosystem_registry.py +119 -0
- tba_storage_core/tables/credential_issuance_state.py +154 -0
- tba_storage_core/tables/credential_issuance_transaction/__init__.py +3 -0
- tba_storage_core/tables/credential_issuer/__init__.py +3 -0
- tba_storage_core/tables/credential_mfa_factor/__init__.py +9 -0
- tba_storage_core/tables/credential_mfa_factor/_table.py +24 -0
- tba_storage_core/tables/credential_mtls_certificate/__init__.py +9 -0
- tba_storage_core/tables/credential_mtls_certificate/_table.py +26 -0
- tba_storage_core/tables/credential_notification/__init__.py +3 -0
- tba_storage_core/tables/credential_offer/__init__.py +3 -0
- tba_storage_core/tables/credential_password/__init__.py +9 -0
- tba_storage_core/tables/credential_password/_table.py +24 -0
- tba_storage_core/tables/credential_recovery_code/__init__.py +9 -0
- tba_storage_core/tables/credential_recovery_code/_table.py +23 -0
- tba_storage_core/tables/credential_service_key/__init__.py +9 -0
- tba_storage_core/tables/credential_service_key/_table.py +49 -0
- tba_storage_core/tables/credential_status_entry/__init__.py +3 -0
- tba_storage_core/tables/credential_status_list/__init__.py +3 -0
- tba_storage_core/tables/credential_status_publication/__init__.py +3 -0
- tba_storage_core/tables/credential_webauthn_passkey/__init__.py +9 -0
- tba_storage_core/tables/credential_webauthn_passkey/_table.py +42 -0
- tba_storage_core/tables/crypto_key/__init__.py +13 -0
- tba_storage_core/tables/crypto_key/_table.py +52 -0
- tba_storage_core/tables/crypto_key_version/__init__.py +13 -0
- tba_storage_core/tables/crypto_key_version/_table.py +53 -0
- tba_storage_core/tables/delegated_admin_scope/__init__.py +9 -0
- tba_storage_core/tables/delegated_admin_scope/_table.py +34 -0
- tba_storage_core/tables/delegation_grant/__init__.py +13 -0
- tba_storage_core/tables/delegation_grant/_table.py +134 -0
- tba_storage_core/tables/device_code/__init__.py +13 -0
- tba_storage_core/tables/device_code/_table.py +54 -0
- tba_storage_core/tables/did_document/__init__.py +3 -0
- tba_storage_core/tables/did_document_version/__init__.py +3 -0
- tba_storage_core/tables/did_gnap_state.py +169 -0
- tba_storage_core/tables/did_resolution_cache/__init__.py +3 -0
- tba_storage_core/tables/did_service/__init__.py +3 -0
- tba_storage_core/tables/did_verification_method/__init__.py +3 -0
- tba_storage_core/tables/dpop_nonce/__init__.py +5 -0
- tba_storage_core/tables/dpop_nonce/_table.py +77 -0
- tba_storage_core/tables/dpop_replay/__init__.py +5 -0
- tba_storage_core/tables/dpop_replay/_table.py +97 -0
- tba_storage_core/tables/entitlement/__init__.py +9 -0
- tba_storage_core/tables/entitlement/_table.py +22 -0
- tba_storage_core/tables/entitlement_assignment/__init__.py +9 -0
- tba_storage_core/tables/entitlement_assignment/_table.py +26 -0
- tba_storage_core/tables/federated_session/__init__.py +5 -0
- tba_storage_core/tables/federated_session/_table.py +32 -0
- tba_storage_core/tables/federation/__init__.py +5 -0
- tba_storage_core/tables/federation/_table.py +37 -0
- tba_storage_core/tables/gnap_client_instance/__init__.py +3 -0
- tba_storage_core/tables/gnap_continuation/__init__.py +3 -0
- tba_storage_core/tables/gnap_grant/__init__.py +3 -0
- tba_storage_core/tables/gnap_interaction/__init__.py +3 -0
- tba_storage_core/tables/identity_provider/__init__.py +5 -0
- tba_storage_core/tables/identity_provider/_table.py +37 -0
- tba_storage_core/tables/invariant_evaluation/__init__.py +5 -0
- tba_storage_core/tables/invariant_evaluation/_table.py +29 -0
- tba_storage_core/tables/invariant_violation/__init__.py +5 -0
- tba_storage_core/tables/invariant_violation/_table.py +27 -0
- tba_storage_core/tables/key_attestation_evidence/__init__.py +13 -0
- tba_storage_core/tables/key_attestation_evidence/_table.py +99 -0
- tba_storage_core/tables/key_envelope/__init__.py +13 -0
- tba_storage_core/tables/key_envelope/_table.py +54 -0
- tba_storage_core/tables/key_rotation_event/__init__.py +9 -0
- tba_storage_core/tables/key_rotation_event/_table.py +44 -0
- tba_storage_core/tables/key_rotation_policy/__init__.py +9 -0
- tba_storage_core/tables/key_rotation_policy/_table.py +38 -0
- tba_storage_core/tables/logout_state/__init__.py +10 -0
- tba_storage_core/tables/logout_state/_table.py +67 -0
- tba_storage_core/tables/machine_identity/__init__.py +9 -0
- tba_storage_core/tables/machine_identity/_table.py +49 -0
- tba_storage_core/tables/operator_activity/__init__.py +9 -0
- tba_storage_core/tables/operator_activity/_table.py +29 -0
- tba_storage_core/tables/operator_audit_event/__init__.py +9 -0
- tba_storage_core/tables/operator_audit_event/_table.py +29 -0
- tba_storage_core/tables/operator_metadata/__init__.py +9 -0
- tba_storage_core/tables/operator_metadata/_table.py +19 -0
- tba_storage_core/tables/operator_record/__init__.py +9 -0
- tba_storage_core/tables/operator_record/_table.py +44 -0
- tba_storage_core/tables/operator_transaction/__init__.py +9 -0
- tba_storage_core/tables/operator_transaction/_table.py +33 -0
- tba_storage_core/tables/plugin_descriptor/__init__.py +9 -0
- tba_storage_core/tables/plugin_descriptor/_table.py +23 -0
- tba_storage_core/tables/plugin_lifecycle_event/__init__.py +9 -0
- tba_storage_core/tables/plugin_lifecycle_event/_table.py +22 -0
- tba_storage_core/tables/policy/__init__.py +5 -0
- tba_storage_core/tables/policy/_table.py +30 -0
- tba_storage_core/tables/policy_condition/__init__.py +9 -0
- tba_storage_core/tables/policy_condition/_table.py +21 -0
- tba_storage_core/tables/policy_set/__init__.py +5 -0
- tba_storage_core/tables/policy_set/_table.py +33 -0
- tba_storage_core/tables/policy_set_member/__init__.py +5 -0
- tba_storage_core/tables/policy_set_member/_table.py +32 -0
- tba_storage_core/tables/policy_target/__init__.py +5 -0
- tba_storage_core/tables/policy_target/_table.py +36 -0
- tba_storage_core/tables/policy_version/__init__.py +5 -0
- tba_storage_core/tables/policy_version/_table.py +35 -0
- tba_storage_core/tables/presentation_consent/__init__.py +3 -0
- tba_storage_core/tables/presentation_replay/__init__.py +3 -0
- tba_storage_core/tables/presentation_state.py +98 -0
- tba_storage_core/tables/presentation_transaction/__init__.py +3 -0
- tba_storage_core/tables/principal/__init__.py +9 -0
- tba_storage_core/tables/principal/_table.py +49 -0
- tba_storage_core/tables/principal_key_binding/__init__.py +13 -0
- tba_storage_core/tables/principal_key_binding/_table.py +48 -0
- tba_storage_core/tables/provider_artifact/__init__.py +9 -0
- tba_storage_core/tables/provider_artifact/_table.py +22 -0
- tba_storage_core/tables/pushed_authorization_request/__init__.py +13 -0
- tba_storage_core/tables/pushed_authorization_request/_table.py +59 -0
- tba_storage_core/tables/realm/__init__.py +13 -0
- tba_storage_core/tables/realm/_table.py +87 -0
- tba_storage_core/tables/release_attestation_event/__init__.py +9 -0
- tba_storage_core/tables/release_attestation_event/_table.py +21 -0
- tba_storage_core/tables/release_authorization_state/__init__.py +9 -0
- tba_storage_core/tables/release_authorization_state/_table.py +21 -0
- tba_storage_core/tables/release_capability_record/__init__.py +9 -0
- tba_storage_core/tables/release_capability_record/_table.py +21 -0
- tba_storage_core/tables/release_posture/__init__.py +9 -0
- tba_storage_core/tables/release_posture/_table.py +21 -0
- tba_storage_core/tables/release_security_posture/__init__.py +9 -0
- tba_storage_core/tables/release_security_posture/_table.py +22 -0
- tba_storage_core/tables/replay_reservation.py +34 -0
- tba_storage_core/tables/residency_zone/__init__.py +9 -0
- tba_storage_core/tables/residency_zone/_table.py +20 -0
- tba_storage_core/tables/resource_server_contract/__init__.py +9 -0
- tba_storage_core/tables/resource_server_contract/_table.py +21 -0
- tba_storage_core/tables/revoked_token/__init__.py +13 -0
- tba_storage_core/tables/revoked_token/_table.py +39 -0
- tba_storage_core/tables/role/__init__.py +9 -0
- tba_storage_core/tables/role/_table.py +22 -0
- tba_storage_core/tables/runtime_qualification/__init__.py +9 -0
- tba_storage_core/tables/runtime_qualification/_table.py +21 -0
- tba_storage_core/tables/scim_group/__init__.py +9 -0
- tba_storage_core/tables/scim_group/_table.py +21 -0
- tba_storage_core/tables/scim_patch_event/__init__.py +9 -0
- tba_storage_core/tables/scim_patch_event/_table.py +22 -0
- tba_storage_core/tables/scim_schema/__init__.py +9 -0
- tba_storage_core/tables/scim_schema/_table.py +21 -0
- tba_storage_core/tables/scim_user/__init__.py +9 -0
- tba_storage_core/tables/scim_user/_table.py +21 -0
- tba_storage_core/tables/sdk_package/__init__.py +9 -0
- tba_storage_core/tables/sdk_package/_table.py +23 -0
- tba_storage_core/tables/security_event/__init__.py +3 -0
- tba_storage_core/tables/security_event_delivery/__init__.py +3 -0
- tba_storage_core/tables/security_event_replay/__init__.py +3 -0
- tba_storage_core/tables/security_event_state.py +100 -0
- tba_storage_core/tables/security_event_subscription/__init__.py +3 -0
- tba_storage_core/tables/service_identity/__init__.py +9 -0
- tba_storage_core/tables/service_identity/_table.py +47 -0
- tba_storage_core/tables/spiffe_state.py +83 -0
- tba_storage_core/tables/spiffe_trust_bundle/__init__.py +3 -0
- tba_storage_core/tables/subject_alias/__init__.py +9 -0
- tba_storage_core/tables/subject_alias/_table.py +21 -0
- tba_storage_core/tables/svid_record/__init__.py +3 -0
- tba_storage_core/tables/tenant/__init__.py +13 -0
- tba_storage_core/tables/tenant/_table.py +83 -0
- tba_storage_core/tables/tenant_membership/__init__.py +9 -0
- tba_storage_core/tables/tenant_membership/_table.py +28 -0
- tba_storage_core/tables/tenant_residency/__init__.py +9 -0
- tba_storage_core/tables/tenant_residency/_table.py +23 -0
- tba_storage_core/tables/token_record/__init__.py +10 -0
- tba_storage_core/tables/token_record/_table.py +123 -0
- tba_storage_core/tables/trust_anchor/__init__.py +3 -0
- tba_storage_core/tables/trust_domain_federation/__init__.py +3 -0
- tba_storage_core/tables/trust_federation_graph/__init__.py +15 -0
- tba_storage_core/tables/trust_federation_graph/_table.py +94 -0
- tba_storage_core/tables/user/__init__.py +13 -0
- tba_storage_core/tables/user/_table.py +127 -0
- tba_storage_core/tables/verifier_registration/__init__.py +3 -0
- tba_storage_core/tables/wallet_attestation/__init__.py +3 -0
- tba_storage_core/tables/wallet_instance/__init__.py +3 -0
- tba_storage_core/tables/wallet_key_binding/__init__.py +3 -0
- tba_storage_core/tables/wallet_registration/__init__.py +3 -0
- tba_storage_core/tables/webauthn_attestation_record/__init__.py +3 -0
- tba_storage_core/tables/webauthn_attestation_record/_table.py +28 -0
- tba_storage_core/tables/webauthn_ceremony/__init__.py +3 -0
- tba_storage_core/tables/webauthn_ceremony/_table.py +43 -0
- tba_storage_core/tables/webauthn_relying_party/__init__.py +3 -0
- tba_storage_core/tables/webauthn_relying_party/_table.py +24 -0
- tba_storage_core/tables/workload_credential_state.py +57 -0
- tba_storage_core/tables/workload_identity/__init__.py +9 -0
- tba_storage_core/tables/workload_identity/_table.py +89 -0
- tba_storage_core-0.4.0.dev2.dist-info/METADATA +126 -0
- tba_storage_core-0.4.0.dev2.dist-info/RECORD +294 -0
- tba_storage_core-0.4.0.dev2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Storage adapters and Tigrbl/SQLAlchemy tables for the TBA identity package suite."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .migrations.contract import (
|
|
6
|
+
MigrationContract,
|
|
7
|
+
MigrationRevision,
|
|
8
|
+
build_migration_contract,
|
|
9
|
+
collect_migration_revisions,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ensure_identity_storage_importable() -> None:
|
|
14
|
+
"""Compatibility hook for callers that previously used facade path setup."""
|
|
15
|
+
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ensure_identity_storage_importable",
|
|
21
|
+
"MigrationContract",
|
|
22
|
+
"MigrationRevision",
|
|
23
|
+
"build_migration_contract",
|
|
24
|
+
"collect_migration_revisions",
|
|
25
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Explicit full identity-storage composition used by the compatibility facade."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import tomllib
|
|
9
|
+
except ModuleNotFoundError: # pragma: no cover - Python 3.10
|
|
10
|
+
import tomli as tomllib
|
|
11
|
+
|
|
12
|
+
from tigrbl_migrations import (
|
|
13
|
+
StorageComposition,
|
|
14
|
+
discover_components,
|
|
15
|
+
)
|
|
16
|
+
from tigrbl_migrations.version import require_supported_manifest_version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_import_roots() -> tuple[str, ...]:
|
|
20
|
+
profile = tomllib.loads(
|
|
21
|
+
files(__package__).joinpath("storage.toml").read_text(encoding="utf-8")
|
|
22
|
+
)
|
|
23
|
+
require_supported_manifest_version(profile["manifest_version"])
|
|
24
|
+
return tuple(profile["components"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
COMPONENT_IMPORT_ROOTS = _load_import_roots()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_full_composition():
|
|
31
|
+
manifests, migrations = discover_components(COMPONENT_IMPORT_ROOTS)
|
|
32
|
+
return StorageComposition.from_manifests(*manifests), migrations
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ["COMPONENT_IMPORT_ROOTS", "load_full_composition"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Repository-owned migration contracts and durable version helpers."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.contract import (
|
|
4
|
+
MigrationContract,
|
|
5
|
+
MigrationRevision,
|
|
6
|
+
build_migration_contract,
|
|
7
|
+
collect_migration_revisions,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"MigrationContract",
|
|
12
|
+
"MigrationRevision",
|
|
13
|
+
"build_migration_contract",
|
|
14
|
+
"collect_migration_revisions",
|
|
15
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
REVISION_PATTERN = re.compile(r"^revision\s*=\s*[\"']([^\"']+)[\"']", re.MULTILINE)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class MigrationRevision:
|
|
14
|
+
revision: str
|
|
15
|
+
path: str
|
|
16
|
+
ordinal: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class MigrationContract:
|
|
21
|
+
revisions: tuple[MigrationRevision, ...]
|
|
22
|
+
required_collections: tuple[str, ...]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def is_ordered(self) -> bool:
|
|
26
|
+
return tuple(item.ordinal for item in self.revisions) == tuple(sorted(item.ordinal for item in self.revisions))
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def latest_revision(self) -> str | None:
|
|
30
|
+
return self.revisions[-1].revision if self.revisions else None
|
|
31
|
+
|
|
32
|
+
def assert_complete(self) -> None:
|
|
33
|
+
if not self.revisions:
|
|
34
|
+
raise ValueError("migration contract requires at least one revision")
|
|
35
|
+
if not self.is_ordered:
|
|
36
|
+
raise ValueError("migration revisions must be ordered")
|
|
37
|
+
if len({item.revision for item in self.revisions}) != len(self.revisions):
|
|
38
|
+
raise ValueError("migration revisions must be unique")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def collect_migration_revisions(versions_dir: Path) -> tuple[MigrationRevision, ...]:
|
|
42
|
+
revisions: list[MigrationRevision] = []
|
|
43
|
+
for path in sorted(versions_dir.glob("*.py")):
|
|
44
|
+
if path.name == "__init__.py":
|
|
45
|
+
continue
|
|
46
|
+
text = path.read_text(encoding="utf-8")
|
|
47
|
+
match = REVISION_PATTERN.search(text)
|
|
48
|
+
if match is None:
|
|
49
|
+
continue
|
|
50
|
+
ordinal_text = path.name.split("_", 1)[0]
|
|
51
|
+
ordinal = int(ordinal_text) if ordinal_text.isdigit() else len(revisions) + 1
|
|
52
|
+
revisions.append(MigrationRevision(revision=match.group(1), path=path.as_posix(), ordinal=ordinal))
|
|
53
|
+
return tuple(revisions)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_migration_contract(
|
|
57
|
+
*,
|
|
58
|
+
versions_dir: Path,
|
|
59
|
+
required_collections: Iterable[str],
|
|
60
|
+
) -> MigrationContract:
|
|
61
|
+
contract = MigrationContract(
|
|
62
|
+
revisions=collect_migration_revisions(versions_dir),
|
|
63
|
+
required_collections=tuple(sorted(set(required_collections))),
|
|
64
|
+
)
|
|
65
|
+
contract.assert_complete()
|
|
66
|
+
return contract
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"MigrationContract",
|
|
71
|
+
"MigrationRevision",
|
|
72
|
+
"build_migration_contract",
|
|
73
|
+
"collect_migration_revisions",
|
|
74
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""DDL helpers for executable repository-owned migrations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Column, ForeignKey, MetaData, Table, inspect, text
|
|
9
|
+
from sqlalchemy.exc import NoSuchTableError, SQLAlchemyError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SupportsTable(Protocol):
|
|
13
|
+
__table__: object
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
AUTHN_SCHEMA = "authn"
|
|
17
|
+
MIGRATION_TABLE = "schema_migrations"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def ensure_schema(conn) -> None:
|
|
21
|
+
if conn.dialect.name != "sqlite":
|
|
22
|
+
conn.exec_driver_sql(f"CREATE SCHEMA IF NOT EXISTS {AUTHN_SCHEMA}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ensure_migration_table(conn) -> None:
|
|
26
|
+
ensure_schema(conn)
|
|
27
|
+
if conn.dialect.name == "sqlite":
|
|
28
|
+
conn.exec_driver_sql(
|
|
29
|
+
f"CREATE TABLE IF NOT EXISTS {MIGRATION_TABLE} (revision VARCHAR(128) PRIMARY KEY, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
conn.exec_driver_sql(
|
|
33
|
+
f"CREATE TABLE IF NOT EXISTS {AUTHN_SCHEMA}.{MIGRATION_TABLE} (revision VARCHAR(128) PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW())"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def applied_revisions(conn) -> set[str]:
|
|
38
|
+
ensure_migration_table(conn)
|
|
39
|
+
if conn.dialect.name == "sqlite":
|
|
40
|
+
rows = conn.exec_driver_sql(f"SELECT revision FROM {MIGRATION_TABLE}").fetchall()
|
|
41
|
+
else:
|
|
42
|
+
rows = conn.exec_driver_sql(f"SELECT revision FROM {AUTHN_SCHEMA}.{MIGRATION_TABLE}").fetchall()
|
|
43
|
+
return {str(row[0]) for row in rows}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def mark_revision(conn, revision: str) -> None:
|
|
47
|
+
ensure_migration_table(conn)
|
|
48
|
+
table = MIGRATION_TABLE if conn.dialect.name == "sqlite" else f"{AUTHN_SCHEMA}.{MIGRATION_TABLE}"
|
|
49
|
+
conn.execute(text(f"INSERT INTO {table} (revision) VALUES (:revision)"), {"revision": revision})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def unmark_revision(conn, revision: str) -> None:
|
|
53
|
+
ensure_migration_table(conn)
|
|
54
|
+
table = MIGRATION_TABLE if conn.dialect.name == "sqlite" else f"{AUTHN_SCHEMA}.{MIGRATION_TABLE}"
|
|
55
|
+
conn.execute(text(f"DELETE FROM {table} WHERE revision = :revision"), {"revision": revision})
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _ddl_tables(conn, *models: SupportsTable) -> list[Table]:
|
|
59
|
+
return [model.__table__ for model in models]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_tables(conn, *models: SupportsTable) -> None:
|
|
63
|
+
ensure_schema(conn)
|
|
64
|
+
for table in _ddl_tables(conn, *models):
|
|
65
|
+
table.create(bind=conn, checkfirst=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def drop_tables(conn, *models: SupportsTable) -> None:
|
|
69
|
+
for table in reversed(_ddl_tables(conn, *models)):
|
|
70
|
+
table.drop(bind=conn, checkfirst=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def table_names(conn) -> set[str]:
|
|
74
|
+
inspector = inspect(conn)
|
|
75
|
+
if conn.dialect.name == "sqlite":
|
|
76
|
+
return set(inspector.get_table_names(schema=AUTHN_SCHEMA))
|
|
77
|
+
return set(inspector.get_table_names(schema=AUTHN_SCHEMA))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def column_names(conn, table: str) -> set[str]:
|
|
81
|
+
inspector = inspect(conn)
|
|
82
|
+
try:
|
|
83
|
+
cols = inspector.get_columns(
|
|
84
|
+
table,
|
|
85
|
+
schema=AUTHN_SCHEMA if conn.dialect.name == "sqlite" else AUTHN_SCHEMA,
|
|
86
|
+
)
|
|
87
|
+
except NoSuchTableError:
|
|
88
|
+
return set()
|
|
89
|
+
return {str(col["name"]) for col in cols}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _table_name(conn, table: str) -> str:
|
|
93
|
+
return f'"{AUTHN_SCHEMA}"."{table}"'
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _index_name(conn, index: str) -> str:
|
|
97
|
+
if conn.dialect.name == "sqlite":
|
|
98
|
+
return f'"{AUTHN_SCHEMA}"."{index}"'
|
|
99
|
+
return f'"{index}"'
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _copy_sqlalchemy_column(column, *, preserve_foreign_keys: bool = True) -> Column:
|
|
103
|
+
args = [column.type]
|
|
104
|
+
if preserve_foreign_keys:
|
|
105
|
+
for fk in column.foreign_keys:
|
|
106
|
+
target = fk.target_fullname
|
|
107
|
+
if target.startswith(f"{AUTHN_SCHEMA}."):
|
|
108
|
+
target = target[len(AUTHN_SCHEMA) + 1 :]
|
|
109
|
+
args.append(
|
|
110
|
+
ForeignKey(
|
|
111
|
+
target,
|
|
112
|
+
onupdate=fk.onupdate,
|
|
113
|
+
ondelete=fk.ondelete,
|
|
114
|
+
deferrable=fk.deferrable,
|
|
115
|
+
initially=fk.initially,
|
|
116
|
+
name=fk.constraint.name if fk.constraint is not None else None,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
server_default = None
|
|
120
|
+
if column.server_default is not None and getattr(column.server_default, "arg", None) is not None:
|
|
121
|
+
server_default = column.server_default.arg
|
|
122
|
+
return Column(
|
|
123
|
+
column.name,
|
|
124
|
+
*args,
|
|
125
|
+
primary_key=column.primary_key,
|
|
126
|
+
nullable=column.nullable,
|
|
127
|
+
unique=bool(column.unique),
|
|
128
|
+
autoincrement=column.autoincrement,
|
|
129
|
+
server_default=server_default,
|
|
130
|
+
comment=column.comment,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _sqlite_rebuild_without_columns(conn, table: str, columns_to_drop: Iterable[str]) -> None:
|
|
135
|
+
drop_set = {name for name in columns_to_drop if name in column_names(conn, table)}
|
|
136
|
+
if not drop_set:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
old_meta = MetaData()
|
|
140
|
+
old_table = Table(table, old_meta, schema=AUTHN_SCHEMA, autoload_with=conn)
|
|
141
|
+
keep_columns = [column for column in old_table.columns if column.name not in drop_set]
|
|
142
|
+
if not keep_columns:
|
|
143
|
+
raise ValueError(f"cannot rebuild SQLite table '{table}' without any remaining columns")
|
|
144
|
+
|
|
145
|
+
old_indexes = inspect(conn).get_indexes(table, schema=AUTHN_SCHEMA)
|
|
146
|
+
tmp_table_name = f"__tmp__{table}"
|
|
147
|
+
|
|
148
|
+
conn.exec_driver_sql("PRAGMA foreign_keys=OFF")
|
|
149
|
+
try:
|
|
150
|
+
new_meta = MetaData()
|
|
151
|
+
new_table = Table(
|
|
152
|
+
tmp_table_name,
|
|
153
|
+
new_meta,
|
|
154
|
+
schema=AUTHN_SCHEMA,
|
|
155
|
+
*(_copy_sqlalchemy_column(column, preserve_foreign_keys=False) for column in keep_columns),
|
|
156
|
+
)
|
|
157
|
+
new_table.create(bind=conn, checkfirst=False)
|
|
158
|
+
|
|
159
|
+
keep_names = [column.name for column in keep_columns]
|
|
160
|
+
select_columns = ", ".join(f'"{name}"' for name in keep_names)
|
|
161
|
+
conn.exec_driver_sql(
|
|
162
|
+
f'INSERT INTO {_table_name(conn, tmp_table_name)} ({select_columns}) '
|
|
163
|
+
f'SELECT {select_columns} FROM {_table_name(conn, table)}'
|
|
164
|
+
)
|
|
165
|
+
conn.exec_driver_sql(f'DROP TABLE {_table_name(conn, table)}')
|
|
166
|
+
conn.exec_driver_sql(
|
|
167
|
+
f'ALTER TABLE {_table_name(conn, tmp_table_name)} RENAME TO "{table}"'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
for index in old_indexes:
|
|
171
|
+
index_columns = list(index.get("column_names") or [])
|
|
172
|
+
if not index_columns or any(name in drop_set for name in index_columns):
|
|
173
|
+
continue
|
|
174
|
+
unique_sql = "UNIQUE " if bool(index.get("unique", False)) else ""
|
|
175
|
+
rendered = ", ".join(f'"{name}"' for name in index_columns)
|
|
176
|
+
conn.exec_driver_sql(
|
|
177
|
+
f"CREATE {unique_sql}INDEX IF NOT EXISTS {_index_name(conn, index['name'])} "
|
|
178
|
+
f'ON "{table}" ({rendered})'
|
|
179
|
+
)
|
|
180
|
+
finally:
|
|
181
|
+
conn.exec_driver_sql("PRAGMA foreign_keys=ON")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def drop_columns(conn, table: str, columns_to_drop: Iterable[str]) -> None:
|
|
185
|
+
drop_list = [name for name in columns_to_drop if name in column_names(conn, table)]
|
|
186
|
+
if not drop_list:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
if conn.dialect.name == "sqlite":
|
|
190
|
+
remaining = list(drop_list)
|
|
191
|
+
for name in drop_list:
|
|
192
|
+
try:
|
|
193
|
+
conn.exec_driver_sql(f'ALTER TABLE {_table_name(conn, table)} DROP COLUMN "{name}"')
|
|
194
|
+
remaining.remove(name)
|
|
195
|
+
except SQLAlchemyError:
|
|
196
|
+
continue
|
|
197
|
+
if remaining:
|
|
198
|
+
_sqlite_rebuild_without_columns(conn, table, remaining)
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
rendered_table = _table_name(conn, table)
|
|
202
|
+
for name in drop_list:
|
|
203
|
+
conn.exec_driver_sql(f'ALTER TABLE {rendered_table} DROP COLUMN IF EXISTS "{name}"')
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = [
|
|
207
|
+
"AUTHN_SCHEMA",
|
|
208
|
+
"MIGRATION_TABLE",
|
|
209
|
+
"applied_revisions",
|
|
210
|
+
"column_names",
|
|
211
|
+
"create_tables",
|
|
212
|
+
"drop_columns",
|
|
213
|
+
"drop_tables",
|
|
214
|
+
"ensure_migration_table",
|
|
215
|
+
"ensure_schema",
|
|
216
|
+
"mark_revision",
|
|
217
|
+
"table_names",
|
|
218
|
+
"unmark_revision",
|
|
219
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Executable DDL migration for 0001_initial_identity_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import Realm, Tenant, User
|
|
5
|
+
|
|
6
|
+
revision = '0001_initial_identity_tables'
|
|
7
|
+
down_revision = None
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade(conn) -> None:
|
|
13
|
+
create_tables(conn, Realm, Tenant, User)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade(conn) -> None:
|
|
17
|
+
drop_tables(conn, Realm, Tenant, User)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Executable DDL migration for 0002_client_and_service_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import (
|
|
5
|
+
Client,
|
|
6
|
+
ClientRegistration,
|
|
7
|
+
CredentialApiKey,
|
|
8
|
+
CredentialServiceKey,
|
|
9
|
+
ServiceIdentity,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
revision = '0002_client_and_service_tables'
|
|
13
|
+
down_revision = '0001_initial_identity_tables'
|
|
14
|
+
branch_labels = None
|
|
15
|
+
depends_on = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def upgrade(conn) -> None:
|
|
19
|
+
create_tables(conn, Client, ClientRegistration, ServiceIdentity, CredentialServiceKey, CredentialApiKey)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def downgrade(conn) -> None:
|
|
23
|
+
drop_tables(conn, Client, ClientRegistration, ServiceIdentity, CredentialServiceKey, CredentialApiKey)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Executable DDL migration for 0003_authorization_runtime_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import AuthSession, AuthCode, Consent, TokenRecord
|
|
5
|
+
|
|
6
|
+
revision = '0003_authorization_runtime_tables'
|
|
7
|
+
down_revision = '0002_client_and_service_tables'
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade(conn) -> None:
|
|
13
|
+
create_tables(conn, AuthSession, AuthCode, Consent, TokenRecord)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade(conn) -> None:
|
|
17
|
+
drop_tables(conn, AuthSession, AuthCode, Consent, TokenRecord)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Executable DDL migration for 0004_device_par_revocation_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import DeviceCode, PushedAuthorizationRequest, RevokedToken
|
|
5
|
+
|
|
6
|
+
revision = '0004_device_par_revocation_tables'
|
|
7
|
+
down_revision = '0003_authorization_runtime_tables'
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade(conn) -> None:
|
|
13
|
+
create_tables(conn, DeviceCode, PushedAuthorizationRequest, RevokedToken)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade(conn) -> None:
|
|
17
|
+
drop_tables(conn, DeviceCode, PushedAuthorizationRequest, RevokedToken)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Executable DDL migration for 0005_session_logout_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import LogoutState
|
|
5
|
+
|
|
6
|
+
revision = '0005_session_logout_tables'
|
|
7
|
+
down_revision = '0004_device_par_revocation_tables'
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade(conn) -> None:
|
|
13
|
+
create_tables(conn, LogoutState)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade(conn) -> None:
|
|
17
|
+
drop_tables(conn, LogoutState)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Executable DDL migration for 0006_key_rotation_and_audit_tables."""
|
|
2
|
+
|
|
3
|
+
from tba_storage_core.migrations.helpers import create_tables, drop_tables
|
|
4
|
+
from tba_storage_core.tables import KeyRotationEvent, AuditEvent
|
|
5
|
+
|
|
6
|
+
revision = '0006_key_rotation_and_audit_tables'
|
|
7
|
+
down_revision = '0005_session_logout_tables'
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade(conn) -> None:
|
|
13
|
+
create_tables(conn, KeyRotationEvent, AuditEvent)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def downgrade(conn) -> None:
|
|
17
|
+
drop_tables(conn, KeyRotationEvent, AuditEvent)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Executable DDL migration for 0007_browser_session_cookie_and_auth_code_linkage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tba_storage_core.migrations.helpers import column_names, drop_columns, AUTHN_SCHEMA
|
|
6
|
+
|
|
7
|
+
revision = '0007_browser_session_cookie_and_auth_code_linkage'
|
|
8
|
+
down_revision = '0006_key_rotation_and_audit_tables'
|
|
9
|
+
branch_labels = None
|
|
10
|
+
depends_on = None
|
|
11
|
+
|
|
12
|
+
SESSION_COLUMNS = (
|
|
13
|
+
'session_state_salt',
|
|
14
|
+
'cookie_secret_hash',
|
|
15
|
+
'cookie_issued_at',
|
|
16
|
+
'cookie_rotated_at',
|
|
17
|
+
)
|
|
18
|
+
AUTH_CODE_COLUMNS = ('session_id',)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _table_name(conn, name: str) -> str:
|
|
22
|
+
return f"{AUTHN_SCHEMA}.{name}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_column(conn, table: str, ddl: str) -> None:
|
|
26
|
+
conn.exec_driver_sql(f"ALTER TABLE {_table_name(conn, table)} ADD COLUMN {ddl}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade(conn) -> None:
|
|
30
|
+
session_cols = column_names(conn, 'sessions')
|
|
31
|
+
if 'session_state_salt' not in session_cols:
|
|
32
|
+
_add_column(conn, 'sessions', 'session_state_salt VARCHAR(128)')
|
|
33
|
+
if 'cookie_secret_hash' not in session_cols:
|
|
34
|
+
_add_column(conn, 'sessions', 'cookie_secret_hash VARCHAR(128)')
|
|
35
|
+
if 'cookie_issued_at' not in session_cols:
|
|
36
|
+
_add_column(conn, 'sessions', 'cookie_issued_at TIMESTAMP')
|
|
37
|
+
if 'cookie_rotated_at' not in session_cols:
|
|
38
|
+
_add_column(conn, 'sessions', 'cookie_rotated_at TIMESTAMP')
|
|
39
|
+
|
|
40
|
+
auth_code_cols = column_names(conn, 'auth_codes')
|
|
41
|
+
if 'session_id' not in auth_code_cols:
|
|
42
|
+
if conn.dialect.name == 'sqlite':
|
|
43
|
+
_add_column(conn, 'auth_codes', 'session_id CHAR(32)')
|
|
44
|
+
else:
|
|
45
|
+
_add_column(conn, 'auth_codes', 'session_id UUID')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def downgrade(conn) -> None:
|
|
49
|
+
drop_columns(conn, 'auth_codes', AUTH_CODE_COLUMNS)
|
|
50
|
+
drop_columns(conn, 'sessions', SESSION_COLUMNS)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Executable DDL migration for 0008_refresh_token_family_state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, drop_columns
|
|
6
|
+
|
|
7
|
+
revision = "0008_refresh_token_family_state"
|
|
8
|
+
down_revision = "0007_browser_session_cookie_and_auth_code_linkage"
|
|
9
|
+
branch_labels = None
|
|
10
|
+
depends_on = None
|
|
11
|
+
|
|
12
|
+
TOKEN_RECORD_COLUMNS = (
|
|
13
|
+
"refresh_family_id",
|
|
14
|
+
"refresh_parent_hash",
|
|
15
|
+
"refresh_successor_hash",
|
|
16
|
+
"used_at",
|
|
17
|
+
"reuse_detected_at",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _table_name(conn, name: str) -> str:
|
|
22
|
+
return f"{AUTHN_SCHEMA}.{name}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_column(conn, table: str, ddl: str) -> None:
|
|
26
|
+
conn.exec_driver_sql(f"ALTER TABLE {_table_name(conn, table)} ADD COLUMN {ddl}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade(conn) -> None:
|
|
30
|
+
token_cols = column_names(conn, "token_records")
|
|
31
|
+
if "refresh_family_id" not in token_cols:
|
|
32
|
+
_add_column(conn, "token_records", "refresh_family_id VARCHAR(64)")
|
|
33
|
+
if "refresh_parent_hash" not in token_cols:
|
|
34
|
+
_add_column(conn, "token_records", "refresh_parent_hash VARCHAR(128)")
|
|
35
|
+
if "refresh_successor_hash" not in token_cols:
|
|
36
|
+
_add_column(conn, "token_records", "refresh_successor_hash VARCHAR(128)")
|
|
37
|
+
if "used_at" not in token_cols:
|
|
38
|
+
_add_column(conn, "token_records", "used_at TIMESTAMP")
|
|
39
|
+
if "reuse_detected_at" not in token_cols:
|
|
40
|
+
_add_column(conn, "token_records", "reuse_detected_at TIMESTAMP")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def downgrade(conn) -> None:
|
|
44
|
+
drop_columns(conn, "token_records", TOKEN_RECORD_COLUMNS)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Executable DDL migration for 0009_admin_identity_bootstrap_and_password_recovery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, drop_columns
|
|
6
|
+
|
|
7
|
+
revision = "0009_admin_identity_bootstrap_and_password_recovery"
|
|
8
|
+
down_revision = "0008_refresh_token_family_state"
|
|
9
|
+
branch_labels = None
|
|
10
|
+
depends_on = None
|
|
11
|
+
|
|
12
|
+
USER_COLUMNS = (
|
|
13
|
+
"is_admin",
|
|
14
|
+
"is_superuser",
|
|
15
|
+
"must_change_password",
|
|
16
|
+
"password_reset_token_hash",
|
|
17
|
+
"password_reset_expires_at",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _table_name(name: str) -> str:
|
|
22
|
+
return f"{AUTHN_SCHEMA}.{name}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_column(conn, table: str, ddl: str) -> None:
|
|
26
|
+
conn.exec_driver_sql(f"ALTER TABLE {_table_name(table)} ADD COLUMN {ddl}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _bool_default(conn) -> str:
|
|
30
|
+
return "0" if conn.dialect.name == "sqlite" else "FALSE"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def upgrade(conn) -> None:
|
|
34
|
+
user_cols = column_names(conn, "users")
|
|
35
|
+
if "is_admin" not in user_cols:
|
|
36
|
+
_add_column(conn, "users", f"is_admin BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
|
|
37
|
+
if "is_superuser" not in user_cols:
|
|
38
|
+
_add_column(conn, "users", f"is_superuser BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
|
|
39
|
+
if "must_change_password" not in user_cols:
|
|
40
|
+
_add_column(conn, "users", f"must_change_password BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
|
|
41
|
+
if "password_reset_token_hash" not in user_cols:
|
|
42
|
+
_add_column(conn, "users", "password_reset_token_hash VARCHAR(128)")
|
|
43
|
+
if "password_reset_expires_at" not in user_cols:
|
|
44
|
+
_add_column(conn, "users", "password_reset_expires_at TIMESTAMP")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def downgrade(conn) -> None:
|
|
48
|
+
drop_columns(conn, "users", USER_COLUMNS)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Executable DDL migration for 0010_realm_namespace_tables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, create_tables, drop_columns, table_names
|
|
6
|
+
from tba_storage_core.tables import Realm
|
|
7
|
+
|
|
8
|
+
revision = "0010_realm_namespace_tables"
|
|
9
|
+
down_revision = "0009_admin_identity_bootstrap_and_password_recovery"
|
|
10
|
+
branch_labels = None
|
|
11
|
+
depends_on = None
|
|
12
|
+
|
|
13
|
+
DEFAULT_REALM_ID = "FFFFFFFF-1000-0000-0000-000000000000"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _table_name(table: str) -> str:
|
|
17
|
+
return f'"{AUTHN_SCHEMA}"."{table}"'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _add_column(conn, table: str, ddl: str) -> None:
|
|
21
|
+
conn.exec_driver_sql(f"ALTER TABLE {_table_name(table)} ADD COLUMN {ddl}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def upgrade(conn) -> None:
|
|
25
|
+
create_tables(conn, Realm)
|
|
26
|
+
if "tenants" in table_names(conn) and "realm_id" not in column_names(conn, "tenants"):
|
|
27
|
+
_add_column(conn, "tenants", "realm_id UUID")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def downgrade(conn) -> None:
|
|
31
|
+
if "tenants" in table_names(conn):
|
|
32
|
+
drop_columns(conn, "tenants", ("realm_id",))
|
|
33
|
+
Realm.__table__.drop(bind=conn, checkfirst=True)
|