arrow-local-uri 0.1.0__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.
Files changed (31) hide show
  1. arrow_local_uri-0.1.0/PKG-INFO +32 -0
  2. arrow_local_uri-0.1.0/README.md +22 -0
  3. arrow_local_uri-0.1.0/arrow_local_uri.egg-info/PKG-INFO +32 -0
  4. arrow_local_uri-0.1.0/arrow_local_uri.egg-info/SOURCES.txt +29 -0
  5. arrow_local_uri-0.1.0/arrow_local_uri.egg-info/dependency_links.txt +1 -0
  6. arrow_local_uri-0.1.0/arrow_local_uri.egg-info/requires.txt +1 -0
  7. arrow_local_uri-0.1.0/arrow_local_uri.egg-info/top_level.txt +1 -0
  8. arrow_local_uri-0.1.0/arrow_uri/__init__.py +133 -0
  9. arrow_local_uri-0.1.0/arrow_uri/audit.py +128 -0
  10. arrow_local_uri-0.1.0/arrow_uri/context.py +87 -0
  11. arrow_local_uri-0.1.0/arrow_uri/delegation.py +141 -0
  12. arrow_local_uri-0.1.0/arrow_uri/elevation.py +299 -0
  13. arrow_local_uri-0.1.0/arrow_uri/elevation_uri.py +547 -0
  14. arrow_local_uri-0.1.0/arrow_uri/envelope.py +103 -0
  15. arrow_local_uri-0.1.0/arrow_uri/errors.py +29 -0
  16. arrow_local_uri-0.1.0/arrow_uri/helper.py +186 -0
  17. arrow_local_uri-0.1.0/arrow_uri/identity.py +78 -0
  18. arrow_local_uri-0.1.0/arrow_uri/json_types.py +8 -0
  19. arrow_local_uri-0.1.0/arrow_uri/keys.py +246 -0
  20. arrow_local_uri-0.1.0/arrow_uri/models.py +184 -0
  21. arrow_local_uri-0.1.0/arrow_uri/oauth.py +299 -0
  22. arrow_local_uri-0.1.0/arrow_uri/protection.py +127 -0
  23. arrow_local_uri-0.1.0/arrow_uri/revocation.py +48 -0
  24. arrow_local_uri-0.1.0/arrow_uri/secrets.py +136 -0
  25. arrow_local_uri-0.1.0/arrow_uri/security.py +457 -0
  26. arrow_local_uri-0.1.0/arrow_uri/trace.py +110 -0
  27. arrow_local_uri-0.1.0/arrow_uri/trust.py +144 -0
  28. arrow_local_uri-0.1.0/arrow_uri/uri.py +322 -0
  29. arrow_local_uri-0.1.0/arrow_uri/validity.py +215 -0
  30. arrow_local_uri-0.1.0/pyproject.toml +16 -0
  31. arrow_local_uri-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: arrow-local-uri
3
+ Version: 0.1.0
4
+ Summary: Local URI trust, authentication, validity, tracing, and elevation framework.
5
+ Author: Joao Lopes
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pydantic>=2.10
10
+
11
+ # Arrow
12
+
13
+ Arrow is an independent local URI trust boundary for Python applications. It
14
+ provides signed and expiring envelopes, authentication strength, provenance,
15
+ capabilities, validity, replay protection, revocation, delegation, tracing,
16
+ redaction, rate limiting, idempotency, OAuth primitives, and native OS
17
+ privilege elevation.
18
+
19
+ Arrow contains no Lake Terminal storage or session-management logic.
20
+
21
+ <!-- exactus:inventory:start -->
22
+ ## Directory contents
23
+
24
+ ### Files
25
+
26
+ - [`pyproject.toml`](pyproject.toml) — Provide pyproject toml content.
27
+
28
+ ### Subdirectories
29
+
30
+ - [`arrow_uri/`](arrow_uri/README.md) — Arrow public interface.
31
+
32
+ <!-- exactus:inventory:end -->
@@ -0,0 +1,22 @@
1
+ # Arrow
2
+
3
+ Arrow is an independent local URI trust boundary for Python applications. It
4
+ provides signed and expiring envelopes, authentication strength, provenance,
5
+ capabilities, validity, replay protection, revocation, delegation, tracing,
6
+ redaction, rate limiting, idempotency, OAuth primitives, and native OS
7
+ privilege elevation.
8
+
9
+ Arrow contains no Lake Terminal storage or session-management logic.
10
+
11
+ <!-- exactus:inventory:start -->
12
+ ## Directory contents
13
+
14
+ ### Files
15
+
16
+ - [`pyproject.toml`](pyproject.toml) — Provide pyproject toml content.
17
+
18
+ ### Subdirectories
19
+
20
+ - [`arrow_uri/`](arrow_uri/README.md) — Arrow public interface.
21
+
22
+ <!-- exactus:inventory:end -->
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: arrow-local-uri
3
+ Version: 0.1.0
4
+ Summary: Local URI trust, authentication, validity, tracing, and elevation framework.
5
+ Author: Joao Lopes
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: pydantic>=2.10
10
+
11
+ # Arrow
12
+
13
+ Arrow is an independent local URI trust boundary for Python applications. It
14
+ provides signed and expiring envelopes, authentication strength, provenance,
15
+ capabilities, validity, replay protection, revocation, delegation, tracing,
16
+ redaction, rate limiting, idempotency, OAuth primitives, and native OS
17
+ privilege elevation.
18
+
19
+ Arrow contains no Lake Terminal storage or session-management logic.
20
+
21
+ <!-- exactus:inventory:start -->
22
+ ## Directory contents
23
+
24
+ ### Files
25
+
26
+ - [`pyproject.toml`](pyproject.toml) — Provide pyproject toml content.
27
+
28
+ ### Subdirectories
29
+
30
+ - [`arrow_uri/`](arrow_uri/README.md) — Arrow public interface.
31
+
32
+ <!-- exactus:inventory:end -->
@@ -0,0 +1,29 @@
1
+ README.md
2
+ pyproject.toml
3
+ arrow_local_uri.egg-info/PKG-INFO
4
+ arrow_local_uri.egg-info/SOURCES.txt
5
+ arrow_local_uri.egg-info/dependency_links.txt
6
+ arrow_local_uri.egg-info/requires.txt
7
+ arrow_local_uri.egg-info/top_level.txt
8
+ arrow_uri/__init__.py
9
+ arrow_uri/audit.py
10
+ arrow_uri/context.py
11
+ arrow_uri/delegation.py
12
+ arrow_uri/elevation.py
13
+ arrow_uri/elevation_uri.py
14
+ arrow_uri/envelope.py
15
+ arrow_uri/errors.py
16
+ arrow_uri/helper.py
17
+ arrow_uri/identity.py
18
+ arrow_uri/json_types.py
19
+ arrow_uri/keys.py
20
+ arrow_uri/models.py
21
+ arrow_uri/oauth.py
22
+ arrow_uri/protection.py
23
+ arrow_uri/revocation.py
24
+ arrow_uri/secrets.py
25
+ arrow_uri/security.py
26
+ arrow_uri/trace.py
27
+ arrow_uri/trust.py
28
+ arrow_uri/uri.py
29
+ arrow_uri/validity.py
@@ -0,0 +1 @@
1
+ pydantic>=2.10
@@ -0,0 +1,133 @@
1
+ """Arrow public interface."""
2
+
3
+ from .audit import AuditEvent, AuditRecord, HashChainAuditLog
4
+ from .context import Deadline, RequestContext, RequestIdentity
5
+ from .delegation import Delegation, DelegationManager, DelegationRequest
6
+ from .elevation import ElevationGrant, PrivilegeBroker
7
+ from .elevation_uri import (
8
+ ElevationEnvelope,
9
+ ElevationURIManager,
10
+ PrivilegedOperation,
11
+ PrivilegedRegistry,
12
+ )
13
+ from .envelope import EnvelopeSigner, SignedEnvelope
14
+ from .helper import HelperGrant, HelperGrantSigner
15
+ from .identity import ExecutablePolicy, PeerIdentityProvider
16
+ from .keys import KeyRing, KeyStatus, SigningKey
17
+ from .models import (
18
+ CallerInfo,
19
+ SecurityContext,
20
+ TraceContext,
21
+ URIRequest,
22
+ URIResponse,
23
+ URIStatus,
24
+ )
25
+ from .oauth import (
26
+ AuthorizationRequest,
27
+ DeviceAuthorization,
28
+ OAuthClient,
29
+ OAuthTransport,
30
+ PKCEPair,
31
+ TokenSet,
32
+ )
33
+ from .protection import IdempotencyStore, RateLimiter, Redactor
34
+ from .revocation import RevocationRegistry
35
+ from .secrets import MemorySecretStore, RefreshTokenVault, SecretStore
36
+ from .security import (
37
+ AuthorizationPolicy,
38
+ NonceStore,
39
+ OAuthToken,
40
+ OAuthTokenValidator,
41
+ SessionClaims,
42
+ SignedSessionManager,
43
+ StaticOAuthValidator,
44
+ )
45
+ from .trace import Span, SpanStatus, Trace
46
+ from .trust import (
47
+ AuthenticationStrength,
48
+ Evidence,
49
+ EvidenceKind,
50
+ Principal,
51
+ TrustLevel,
52
+ )
53
+ from .uri import (
54
+ SignedURIManager,
55
+ SignedURIRequest,
56
+ URIValue,
57
+ URIValues,
58
+ )
59
+ from .validity import (
60
+ ValidityCode,
61
+ ValidityPolicy,
62
+ ValidityResult,
63
+ ValidityWindow,
64
+ )
65
+
66
+ __all__: tuple[str, ...] = (
67
+ "AuditEvent",
68
+ "AuditRecord",
69
+ "AuthenticationStrength",
70
+ "AuthorizationPolicy",
71
+ "AuthorizationRequest",
72
+ "CallerInfo",
73
+ "Deadline",
74
+ "Delegation",
75
+ "DelegationManager",
76
+ "DelegationRequest",
77
+ "DeviceAuthorization",
78
+ "ElevationEnvelope",
79
+ "ElevationGrant",
80
+ "ElevationURIManager",
81
+ "EnvelopeSigner",
82
+ "Evidence",
83
+ "EvidenceKind",
84
+ "ExecutablePolicy",
85
+ "HashChainAuditLog",
86
+ "HelperGrant",
87
+ "HelperGrantSigner",
88
+ "IdempotencyStore",
89
+ "KeyRing",
90
+ "KeyStatus",
91
+ "MemorySecretStore",
92
+ "NonceStore",
93
+ "OAuthClient",
94
+ "OAuthToken",
95
+ "OAuthTokenValidator",
96
+ "OAuthTransport",
97
+ "PKCEPair",
98
+ "PeerIdentityProvider",
99
+ "Principal",
100
+ "PrivilegeBroker",
101
+ "PrivilegedOperation",
102
+ "PrivilegedRegistry",
103
+ "RateLimiter",
104
+ "Redactor",
105
+ "RefreshTokenVault",
106
+ "RequestContext",
107
+ "RequestIdentity",
108
+ "RevocationRegistry",
109
+ "SecretStore",
110
+ "SecurityContext",
111
+ "SessionClaims",
112
+ "SignedEnvelope",
113
+ "SignedSessionManager",
114
+ "SignedURIManager",
115
+ "SignedURIRequest",
116
+ "SigningKey",
117
+ "Span",
118
+ "SpanStatus",
119
+ "StaticOAuthValidator",
120
+ "TokenSet",
121
+ "Trace",
122
+ "TraceContext",
123
+ "TrustLevel",
124
+ "URIRequest",
125
+ "URIResponse",
126
+ "URIStatus",
127
+ "URIValue",
128
+ "URIValues",
129
+ "ValidityCode",
130
+ "ValidityPolicy",
131
+ "ValidityResult",
132
+ "ValidityWindow",
133
+ )
@@ -0,0 +1,128 @@
1
+ """Tamper-evident hash-chained audit records."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import asdict, dataclass
6
+ from hashlib import sha256
7
+ from json import dumps
8
+ from threading import Lock
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from pathlib import Path
13
+
14
+
15
+ @dataclass(frozen=True, slots=True, kw_only=True)
16
+ class AuditEvent:
17
+ """Fields describing one audited event.
18
+
19
+ Attributes:
20
+ timestamp: Event timestamp text.
21
+ request_id: Originating request identifier.
22
+ trace_id: Originating trace identifier.
23
+ principal: Acting principal, when known.
24
+ action: Performed action.
25
+ outcome: Event outcome.
26
+
27
+ """
28
+
29
+ timestamp: str
30
+ request_id: str
31
+ trace_id: str
32
+ principal: str | None
33
+ action: str
34
+ outcome: str
35
+
36
+
37
+ @dataclass(frozen=True, slots=True, kw_only=True)
38
+ class AuditRecord:
39
+ """One chained audit record.
40
+
41
+ Attributes:
42
+ sequence: Monotonic record sequence.
43
+ timestamp: Event timestamp text.
44
+ request_id: Originating request identifier.
45
+ trace_id: Originating trace identifier.
46
+ principal: Acting principal, when known.
47
+ action: Performed action.
48
+ outcome: Event outcome.
49
+ previous_hash: Hash of the preceding record.
50
+ record_hash: Hash of this record.
51
+
52
+ """
53
+
54
+ sequence: int
55
+ timestamp: str
56
+ request_id: str
57
+ trace_id: str
58
+ principal: str | None
59
+ action: str
60
+ outcome: str
61
+ previous_hash: str
62
+ record_hash: str
63
+
64
+
65
+ class HashChainAuditLog:
66
+ """Append-only audit log with SHA-256 chain integrity."""
67
+
68
+ def __init__(self, *, path: Path) -> None:
69
+ """Create a log writing chained records to a JSONL file.
70
+
71
+ Args:
72
+ path: Destination JSONL file path.
73
+
74
+ """
75
+ self._path: Path = path
76
+ self._lock: Lock = Lock()
77
+ self._sequence: int = 0
78
+ self._previous_hash: str = "0" * 64
79
+
80
+ def append(self, *, event: AuditEvent) -> AuditRecord:
81
+ """Append and return a chained record.
82
+
83
+ Args:
84
+ event: Audited event fields.
85
+
86
+ Returns:
87
+ The appended chained record.
88
+
89
+ """
90
+ with self._lock:
91
+ sequence: int = self._sequence + 1
92
+ material: str = dumps(
93
+ obj={
94
+ "sequence": sequence,
95
+ "timestamp": event.timestamp,
96
+ "request_id": event.request_id,
97
+ "trace_id": event.trace_id,
98
+ "principal": event.principal,
99
+ "action": event.action,
100
+ "outcome": event.outcome,
101
+ "previous_hash": self._previous_hash,
102
+ },
103
+ separators=(",", ":"),
104
+ sort_keys=True,
105
+ )
106
+ digest: str = sha256(
107
+ data=material.encode(encoding="utf-8"),
108
+ ).hexdigest()
109
+ record: AuditRecord = AuditRecord(
110
+ sequence=sequence,
111
+ timestamp=event.timestamp,
112
+ request_id=event.request_id,
113
+ trace_id=event.trace_id,
114
+ principal=event.principal,
115
+ action=event.action,
116
+ outcome=event.outcome,
117
+ previous_hash=self._previous_hash,
118
+ record_hash=digest,
119
+ )
120
+ self._path.parent.mkdir(parents=True, exist_ok=True)
121
+ with self._path.open(mode="a", encoding="utf-8") as stream:
122
+ _ = stream.write(
123
+ dumps(obj=asdict(obj=record), separators=(",", ":"))
124
+ + "\n",
125
+ )
126
+ self._sequence = sequence
127
+ self._previous_hash = digest
128
+ return record
@@ -0,0 +1,87 @@
1
+ """Immutable trusted request context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import UTC, datetime
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from .models import CallerInfo, SecurityContext
11
+ from .trace import Trace
12
+ from .trust import Principal
13
+ from .validity import ValidityResult
14
+
15
+
16
+ @dataclass(frozen=True, slots=True, kw_only=True)
17
+ class Deadline:
18
+ """Absolute request deadline.
19
+
20
+ Attributes:
21
+ at: Instant at which the deadline elapses.
22
+
23
+ """
24
+
25
+ at: datetime
26
+
27
+ @property
28
+ def expired(self) -> bool:
29
+ """Return whether the deadline has elapsed.
30
+
31
+ Returns:
32
+ Whether the current time is at or past the deadline.
33
+
34
+ """
35
+ return datetime.now(tz=UTC) >= self.at
36
+
37
+
38
+ @dataclass(frozen=True, slots=True, kw_only=True)
39
+ class RequestIdentity:
40
+ """Stable identity for one invocation.
41
+
42
+ Attributes:
43
+ request_id: Unique request identifier.
44
+ operation: Requested operation name.
45
+ uri: Source request URI.
46
+
47
+ """
48
+
49
+ request_id: str
50
+ operation: str
51
+ uri: str
52
+
53
+
54
+ @dataclass(frozen=True, slots=True, kw_only=True)
55
+ class RequestContext:
56
+ """Trusted context delivered to application code.
57
+
58
+ Attributes:
59
+ request: Stable request identity.
60
+ caller: Transport-established caller provenance.
61
+ principal: Consolidated authenticated principal.
62
+ security: Authenticated identity and authorization state.
63
+ validity: Validity evaluation result.
64
+ trace: Trace collector for the request.
65
+ deadline: Optional absolute request deadline.
66
+
67
+ """
68
+
69
+ request: RequestIdentity
70
+ caller: CallerInfo
71
+ principal: Principal
72
+ security: SecurityContext
73
+ validity: ValidityResult
74
+ trace: Trace
75
+ deadline: Deadline | None = None
76
+
77
+ @property
78
+ def executable(self) -> bool:
79
+ """Return whether validity and deadline permit execution.
80
+
81
+ Returns:
82
+ Whether the request may execute right now.
83
+
84
+ """
85
+ return self.validity.valid and (
86
+ self.deadline is None or not self.deadline.expired
87
+ )
@@ -0,0 +1,141 @@
1
+ """Capability delegation for local agents and applications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import UTC, datetime, timedelta
7
+ from secrets import token_urlsafe
8
+ from typing import TYPE_CHECKING
9
+
10
+ from .errors import AuthorizationError
11
+
12
+ if TYPE_CHECKING:
13
+ from .revocation import RevocationRegistry
14
+
15
+
16
+ @dataclass(frozen=True, slots=True, kw_only=True)
17
+ class Delegation:
18
+ """Temporary subset of a principal's capabilities.
19
+
20
+ Attributes:
21
+ delegation_id: Unique delegation identifier.
22
+ issuer: Issuing principal.
23
+ subject: Delegated subject.
24
+ capabilities: Delegated capability names.
25
+ issued_at: Issue timestamp.
26
+ expires_at: Expiry timestamp.
27
+ depth: Chain depth of this delegation.
28
+ max_depth: Maximum permitted chain depth.
29
+
30
+ """
31
+
32
+ delegation_id: str
33
+ issuer: str
34
+ subject: str
35
+ capabilities: frozenset[str]
36
+ issued_at: datetime
37
+ expires_at: datetime
38
+ depth: int
39
+ max_depth: int
40
+
41
+ @property
42
+ def valid(self) -> bool:
43
+ """Return whether the delegation remains temporally valid.
44
+
45
+ Returns:
46
+ Whether the expiry timestamp is still in the future.
47
+
48
+ """
49
+ return datetime.now(tz=UTC) < self.expires_at
50
+
51
+
52
+ @dataclass(frozen=True, slots=True, kw_only=True)
53
+ class DelegationRequest:
54
+ """Fields describing one requested delegation.
55
+
56
+ Attributes:
57
+ issuer: Issuing principal.
58
+ subject: Delegated subject.
59
+ issuer_capabilities: Capabilities the issuer holds.
60
+ capabilities: Capabilities to delegate.
61
+ ttl: Delegation lifetime.
62
+ depth: Chain depth of this delegation.
63
+ max_depth: Maximum permitted chain depth.
64
+
65
+ """
66
+
67
+ issuer: str
68
+ subject: str
69
+ issuer_capabilities: frozenset[str]
70
+ capabilities: frozenset[str]
71
+ ttl: timedelta
72
+ depth: int = 0
73
+ max_depth: int = 1
74
+
75
+
76
+ @dataclass(slots=True, kw_only=True)
77
+ class DelegationManager:
78
+ """Issue constrained delegation chains.
79
+
80
+ Attributes:
81
+ revocations: Revocation registry consulted during validation.
82
+
83
+ """
84
+
85
+ revocations: RevocationRegistry
86
+
87
+ @staticmethod
88
+ def issue(*, request: DelegationRequest) -> Delegation:
89
+ """Issue a subset-only delegation.
90
+
91
+ Args:
92
+ request: Requested delegation fields.
93
+
94
+ Returns:
95
+ The issued delegation.
96
+
97
+ Raises:
98
+ AuthorizationError: When the request exceeds the issuer's
99
+ capabilities or the permitted chain depth.
100
+
101
+ """
102
+ if not request.capabilities <= request.issuer_capabilities:
103
+ msg: str = (
104
+ "Cannot delegate capabilities the issuer does not possess."
105
+ )
106
+ raise AuthorizationError(
107
+ msg,
108
+ )
109
+ if request.depth > request.max_depth:
110
+ msg = "Delegation depth exceeded."
111
+ raise AuthorizationError(msg)
112
+ now: datetime = datetime.now(tz=UTC)
113
+ return Delegation(
114
+ delegation_id=token_urlsafe(nbytes=18),
115
+ issuer=request.issuer,
116
+ subject=request.subject,
117
+ capabilities=request.capabilities,
118
+ issued_at=now,
119
+ expires_at=now + request.ttl,
120
+ depth=request.depth,
121
+ max_depth=request.max_depth,
122
+ )
123
+
124
+ def validate(self, *, delegation: Delegation) -> None:
125
+ """Validate expiry and revocation.
126
+
127
+ Args:
128
+ delegation: Delegation to validate.
129
+
130
+ Raises:
131
+ AuthorizationError: When the delegation is expired or revoked.
132
+
133
+ """
134
+ if not delegation.valid:
135
+ msg: str = "Delegation expired."
136
+ raise AuthorizationError(msg)
137
+ if self.revocations.is_revoked(
138
+ identifier=delegation.delegation_id,
139
+ ):
140
+ msg = "Delegation revoked."
141
+ raise AuthorizationError(msg)