@macaw-ai/client 0.0.0-stage → 0.9.11
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.
- package/LICENSE.md +339 -0
- package/bin/install.js +422 -0
- package/package.json +37 -4
- package/payload/README.md +56 -0
- package/payload/secureAI/LICENSE +190 -0
- package/payload/secureAI/NOTICE +5 -0
- package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
- package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
- package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
- package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
- package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
- package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
- package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
- package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
- package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
- package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
- package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
- package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
- package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
- package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
- package/payload/secureAI/examples/README.md +244 -0
- package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
- package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
- package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
- package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
- package/payload/secureAI/examples/attestations/README.md +128 -0
- package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
- package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
- package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
- package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
- package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
- package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
- package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
- package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
- package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
- package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
- package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
- package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
- package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
- package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
- package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
- package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
- package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
- package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
- package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
- package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
- package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
- package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
- package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
- package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
- package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
- package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
- package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
- package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
- package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
- package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
- package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
- package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
- package/payload/secureAI/macaw_adapters/__init__.py +55 -0
- package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
- package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
- package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
- package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
- package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
- package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
- package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
- package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
- package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
- package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
- package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
- package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
- package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
- package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
- package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
- package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
- package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
- package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
- package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
- package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
- package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
- package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
- package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
- package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
- package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
- package/payload/secureAI/test_harness_quick.py +296 -0
- package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1a_trade_alice.py - External Attestation Trade Request
|
|
4
|
+
|
|
5
|
+
Alice wants to execute a high-value trade ($15,000).
|
|
6
|
+
Policy requires manager approval via external attestation for amounts > $10,000.
|
|
7
|
+
|
|
8
|
+
This demonstrates:
|
|
9
|
+
- Conditional attestations: only required when amount > 10000
|
|
10
|
+
- Blocking flow: request waits for approval
|
|
11
|
+
- Role-based approval: only users with role:manager can approve
|
|
12
|
+
|
|
13
|
+
NOTE: This example requires interactive approval and is not suitable
|
|
14
|
+
for automated test harnesses.
|
|
15
|
+
|
|
16
|
+
Prerequisites:
|
|
17
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
18
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
19
|
+
- Test users: alice/Alice123!, bob/Bob@123! (bob needs manager role)
|
|
20
|
+
|
|
21
|
+
Run:
|
|
22
|
+
# Terminal 1: Run Alice's trade request (will block waiting)
|
|
23
|
+
python 1a_trade_alice.py
|
|
24
|
+
|
|
25
|
+
# Terminal 2: Run Bob's approval
|
|
26
|
+
python 1a_trade_bob.py
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Trade configuration
|
|
35
|
+
TRADE_SYMBOL = "AAPL"
|
|
36
|
+
TRADE_AMOUNT = 15000 # High-value trade requiring approval
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def execute_trade_handler(params):
|
|
40
|
+
"""Execute trade - the actual tool implementation."""
|
|
41
|
+
print(f"\n [TOOL] execute_trade invoked!")
|
|
42
|
+
print(f" [TOOL] Received parameters:")
|
|
43
|
+
print(f" - symbol: {params.get('symbol')}")
|
|
44
|
+
print(f" - amount: ${params.get('amount'):,}")
|
|
45
|
+
print(f" - action: {params.get('action')}")
|
|
46
|
+
|
|
47
|
+
# Simulate trade execution
|
|
48
|
+
result = {
|
|
49
|
+
"status": "executed",
|
|
50
|
+
"symbol": params.get("symbol"),
|
|
51
|
+
"amount": params.get("amount"),
|
|
52
|
+
"action": params.get("action"),
|
|
53
|
+
"trade_id": "TRD-12345",
|
|
54
|
+
"message": f"Successfully {params.get('action')} ${params.get('amount'):,} of {params.get('symbol')}"
|
|
55
|
+
}
|
|
56
|
+
print(f" [TOOL] Returning: {result}")
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main():
|
|
61
|
+
print("=" * 60)
|
|
62
|
+
print("Example 1a: External Attestation - Trade Request (Alice)")
|
|
63
|
+
print("=" * 60)
|
|
64
|
+
|
|
65
|
+
# Step 1: Create Trading Service (provides execute_trade tool)
|
|
66
|
+
print("\n[Step 1] Creating Trading Service...")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
trading_service = MACAWClient(
|
|
70
|
+
app_name="trading",
|
|
71
|
+
agent_type="service",
|
|
72
|
+
tools={
|
|
73
|
+
"tool:trading/execute_trade": {
|
|
74
|
+
"handler": execute_trade_handler,
|
|
75
|
+
"description": "Execute a stock trade"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if not trading_service.register():
|
|
81
|
+
print(" ERROR: Failed to register Trading Service")
|
|
82
|
+
print("\n Make sure LocalAgent is running:")
|
|
83
|
+
print(" python3 -m macaw_agent.main")
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
print(f" Service ID: {trading_service.agent_id}")
|
|
87
|
+
print(f" Provides: tool:trading/execute_trade")
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f" ERROR: Failed to create Trading Service: {e}")
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
# Step 2: Authenticate as Alice
|
|
94
|
+
print("\n[Step 2] Authenticating as alice...")
|
|
95
|
+
try:
|
|
96
|
+
jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
|
|
97
|
+
print(" Got JWT token")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f" ERROR: Failed to authenticate: {e}")
|
|
100
|
+
print("\n Make sure your identity provider is running and alice user exists")
|
|
101
|
+
trading_service.unregister()
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
# Step 3: Create Alice's user agent with attestation policy
|
|
105
|
+
print("\n[Step 3] Creating Alice's user agent...")
|
|
106
|
+
try:
|
|
107
|
+
# MAPL v3 format for attestations:
|
|
108
|
+
# - "attestations" array: defines WHEN attestation is required
|
|
109
|
+
# - "constraints.attestations": defines HOW attestation works
|
|
110
|
+
alice = MACAWClient(
|
|
111
|
+
user_name="alice",
|
|
112
|
+
iam_token=jwt_token,
|
|
113
|
+
agent_type="user",
|
|
114
|
+
app_name="trading-app",
|
|
115
|
+
intent_policy={
|
|
116
|
+
"resources": ["tool:trading/execute_trade"],
|
|
117
|
+
# Attestation required when amount > 10000
|
|
118
|
+
"attestations": [
|
|
119
|
+
"trade-approved::{params.amount > 10000}"
|
|
120
|
+
],
|
|
121
|
+
# Attestation metadata
|
|
122
|
+
"constraints": {
|
|
123
|
+
"attestations": {
|
|
124
|
+
"trade-approved": {
|
|
125
|
+
"approval_criteria": "role:manager",
|
|
126
|
+
"timeout": 300, # 5 minutes to approve
|
|
127
|
+
"time_to_live": 3600, # Valid for 1 hour after approval
|
|
128
|
+
"one_time": True # Consumed after single use
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if not alice.register():
|
|
136
|
+
print(" ERROR: Failed to register Alice's agent")
|
|
137
|
+
trading_service.unregister()
|
|
138
|
+
return 1
|
|
139
|
+
|
|
140
|
+
print(f" Agent ID: {alice.agent_id}")
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f" ERROR: Failed to create Alice's agent: {e}")
|
|
144
|
+
trading_service.unregister()
|
|
145
|
+
return 1
|
|
146
|
+
|
|
147
|
+
# Step 4: Execute trade (will block waiting for approval)
|
|
148
|
+
print("\n[Step 4] Executing trade...")
|
|
149
|
+
print(f" Symbol: {TRADE_SYMBOL}")
|
|
150
|
+
print(f" Amount: ${TRADE_AMOUNT:,}")
|
|
151
|
+
print(f"\n Policy check: amount ({TRADE_AMOUNT}) > 10000 -> attestation REQUIRED")
|
|
152
|
+
print(f" Attestation: 'trade-approved' requires role:manager")
|
|
153
|
+
print(f"\n Creating PENDING attestation and BLOCKING...")
|
|
154
|
+
print(f" Bob has 5 minutes (timeout=300s) to approve.")
|
|
155
|
+
print(f"\n >>> Run 1a_trade_bob.py in another terminal <<<")
|
|
156
|
+
print("-" * 60)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# This will block internally while waiting for attestation
|
|
160
|
+
result = alice.invoke_tool(
|
|
161
|
+
tool_name="tool:trading/execute_trade",
|
|
162
|
+
target_agent=trading_service.agent_id,
|
|
163
|
+
parameters={
|
|
164
|
+
"symbol": TRADE_SYMBOL,
|
|
165
|
+
"amount": TRADE_AMOUNT,
|
|
166
|
+
"action": "buy"
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
print("\n" + "=" * 60)
|
|
171
|
+
print("TRADE EXECUTED SUCCESSFULLY!")
|
|
172
|
+
print("=" * 60)
|
|
173
|
+
print(f"Result: {json.dumps(result, indent=2)}")
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
error_msg = str(e)
|
|
177
|
+
if "attestation" in error_msg.lower() or "denied" in error_msg.lower():
|
|
178
|
+
print("\n" + "=" * 60)
|
|
179
|
+
print("TRADE BLOCKED - ATTESTATION REQUIRED OR DENIED")
|
|
180
|
+
print("=" * 60)
|
|
181
|
+
print(f"\nError: {error_msg}")
|
|
182
|
+
print(f"\nTo approve, Bob should run:")
|
|
183
|
+
print(f" python 1a_trade_bob.py")
|
|
184
|
+
else:
|
|
185
|
+
print(f"\n ERROR: {e}")
|
|
186
|
+
|
|
187
|
+
finally:
|
|
188
|
+
# Cleanup
|
|
189
|
+
print("\n[Cleanup] Unregistering agents...")
|
|
190
|
+
try:
|
|
191
|
+
alice.unregister()
|
|
192
|
+
trading_service.unregister()
|
|
193
|
+
except:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
import sys
|
|
201
|
+
try:
|
|
202
|
+
sys.exit(main())
|
|
203
|
+
except Exception as e:
|
|
204
|
+
err = str(e)
|
|
205
|
+
print("\n" + "=" * 60)
|
|
206
|
+
if "Local provider does not support" in err:
|
|
207
|
+
print("ERROR: Identity Provider not configured")
|
|
208
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
209
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
210
|
+
print("ERROR: Cannot connect to MACAW")
|
|
211
|
+
print("Fix: Ensure LocalAgent is running")
|
|
212
|
+
else:
|
|
213
|
+
print(f"ERROR: {e}")
|
|
214
|
+
print("=" * 60)
|
|
215
|
+
sys.exit(1)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1a_trade_bob.py - External Attestation Manager Approval
|
|
4
|
+
|
|
5
|
+
Bob reviews and approves/denies pending attestation requests.
|
|
6
|
+
Alice's trade request is BLOCKING, waiting for Bob's approval.
|
|
7
|
+
|
|
8
|
+
This demonstrates:
|
|
9
|
+
- list_attestations(): Find pending requests you can approve
|
|
10
|
+
- approve_attestation(): Grant the request
|
|
11
|
+
- deny_attestation(): Reject the request with reason
|
|
12
|
+
|
|
13
|
+
NOTE: This example requires interactive input and is not suitable
|
|
14
|
+
for automated test harnesses.
|
|
15
|
+
|
|
16
|
+
Prerequisites:
|
|
17
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
18
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
19
|
+
- Test users: alice/Alice123!, bob/Bob@123! (bob needs manager role)
|
|
20
|
+
- Alice has already requested a trade (1a_trade_alice.py running)
|
|
21
|
+
|
|
22
|
+
Run:
|
|
23
|
+
# Terminal 1: Run Alice's trade request first
|
|
24
|
+
python 1a_trade_alice.py
|
|
25
|
+
|
|
26
|
+
# Terminal 2: Run Bob's approval
|
|
27
|
+
python 1a_trade_bob.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
|
|
32
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
print("=" * 60)
|
|
37
|
+
print("Example 1a: External Attestation - Manager Approval (Bob)")
|
|
38
|
+
print("=" * 60)
|
|
39
|
+
|
|
40
|
+
# Step 1: Authenticate as Bob
|
|
41
|
+
print("\n[Step 1] Authenticating as bob...")
|
|
42
|
+
try:
|
|
43
|
+
jwt_token, _ = RemoteIdentityProvider().login("bob", "Bob@123!")
|
|
44
|
+
print(" Got JWT token")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(f" ERROR: Failed to authenticate: {e}")
|
|
47
|
+
print("\n Make sure your identity provider is running and bob user exists")
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
# Step 2: Create Bob's user agent
|
|
51
|
+
print("\n[Step 2] Creating Bob's user agent...")
|
|
52
|
+
try:
|
|
53
|
+
bob = MACAWClient(
|
|
54
|
+
user_name="bob",
|
|
55
|
+
iam_token=jwt_token,
|
|
56
|
+
agent_type="user",
|
|
57
|
+
app_name="attestation-manager",
|
|
58
|
+
intent_policy={
|
|
59
|
+
"resources": ["attestation:*"],
|
|
60
|
+
"constraints": {
|
|
61
|
+
"roles": ["manager"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not bob.register():
|
|
67
|
+
print(" ERROR: Failed to register Bob's agent")
|
|
68
|
+
print("\n Make sure LocalAgent is running:")
|
|
69
|
+
print(" python3 -m macaw_agent.main")
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
print(f" Agent ID: {bob.agent_id}")
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f" ERROR: Failed to create Bob's agent: {e}")
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
# Step 3: List pending attestations
|
|
79
|
+
print("\n[Step 3] Listing pending attestations...")
|
|
80
|
+
try:
|
|
81
|
+
attestations = bob.list_attestations(status="pending")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f" ERROR: Failed to list attestations: {e}")
|
|
84
|
+
bob.unregister()
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
if not attestations:
|
|
88
|
+
print(" No pending attestations found.")
|
|
89
|
+
print("\n Make sure Alice has requested a trade first:")
|
|
90
|
+
print(" python 1a_trade_alice.py")
|
|
91
|
+
bob.unregister()
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
print(f"\n Found {len(attestations)} pending attestation(s):\n")
|
|
95
|
+
|
|
96
|
+
for i, att in enumerate(attestations, 1):
|
|
97
|
+
print(f" [{i}] Key: {att.get('key')}")
|
|
98
|
+
print(f" For Agent: {att.get('for_agent')}")
|
|
99
|
+
print(f" Approval Criteria: {att.get('approval_criteria')}")
|
|
100
|
+
print(f" One-Time: {att.get('one_time', False)}")
|
|
101
|
+
if att.get('value'):
|
|
102
|
+
print(f" Value: {json.dumps(att.get('value', {}))}")
|
|
103
|
+
print()
|
|
104
|
+
|
|
105
|
+
# Step 4: Approve/Deny attestation(s)
|
|
106
|
+
print("[Step 4] Process attestations...")
|
|
107
|
+
print(" Options: [y] Approve [d] Deny [s] Skip")
|
|
108
|
+
|
|
109
|
+
for i, att in enumerate(attestations, 1):
|
|
110
|
+
key = att.get('key')
|
|
111
|
+
for_agent = att.get('for_agent')
|
|
112
|
+
|
|
113
|
+
response = input(f"\n '{key}' for {for_agent}? [y/d/s]: ").strip().lower()
|
|
114
|
+
|
|
115
|
+
if response == 'y':
|
|
116
|
+
print(f" Approving...")
|
|
117
|
+
try:
|
|
118
|
+
if bob.approve_attestation(att, reason="Manager approved"):
|
|
119
|
+
print(f" APPROVED - Alice's request will proceed")
|
|
120
|
+
else:
|
|
121
|
+
print(f" Failed to approve")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f" ERROR: {e}")
|
|
124
|
+
|
|
125
|
+
elif response == 'd':
|
|
126
|
+
print(f" Denying...")
|
|
127
|
+
try:
|
|
128
|
+
if bob.deny_attestation(att, reason="Manager denied"):
|
|
129
|
+
print(f" DENIED - Alice's request will be rejected")
|
|
130
|
+
else:
|
|
131
|
+
print(f" Failed to deny")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f" ERROR: {e}")
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
print(f" Skipped")
|
|
137
|
+
|
|
138
|
+
# Cleanup
|
|
139
|
+
bob.unregister()
|
|
140
|
+
|
|
141
|
+
print("\n" + "=" * 60)
|
|
142
|
+
print("Done. Alice's request should now proceed (or fail if denied).")
|
|
143
|
+
print("=" * 60)
|
|
144
|
+
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
import sys
|
|
150
|
+
try:
|
|
151
|
+
sys.exit(main())
|
|
152
|
+
except Exception as e:
|
|
153
|
+
err = str(e)
|
|
154
|
+
print("\n" + "=" * 60)
|
|
155
|
+
if "Local provider does not support" in err:
|
|
156
|
+
print("ERROR: Identity Provider not configured")
|
|
157
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
158
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
159
|
+
print("ERROR: Cannot connect to MACAW")
|
|
160
|
+
print("Fix: Ensure LocalAgent is running")
|
|
161
|
+
else:
|
|
162
|
+
print(f"ERROR: {e}")
|
|
163
|
+
print("=" * 60)
|
|
164
|
+
sys.exit(1)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1b_grant_admin.py - Reusable Capability Grant (Admin Approval)
|
|
4
|
+
|
|
5
|
+
Admin approves capability grants for researchers.
|
|
6
|
+
Since one_time=False, this approval is REUSABLE.
|
|
7
|
+
|
|
8
|
+
This demonstrates:
|
|
9
|
+
- Identifying reusable vs one-time attestations
|
|
10
|
+
- Granting capabilities that persist
|
|
11
|
+
- Future invocations reuse this approval (no blocking)
|
|
12
|
+
|
|
13
|
+
NOTE: This example requires interactive input and is not suitable
|
|
14
|
+
for automated test harnesses.
|
|
15
|
+
|
|
16
|
+
Prerequisites:
|
|
17
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
18
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
19
|
+
- Test users: alice/Alice123!, bob/Bob@123! (bob needs admin role)
|
|
20
|
+
- Researcher script already waiting (1b_grant_researcher.py running)
|
|
21
|
+
|
|
22
|
+
Run:
|
|
23
|
+
# Terminal 1: Run researcher request first
|
|
24
|
+
python 1b_grant_researcher.py
|
|
25
|
+
|
|
26
|
+
# Terminal 2: Run admin approval
|
|
27
|
+
python 1b_grant_admin.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
|
|
32
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
print("=" * 60)
|
|
37
|
+
print("Example 1b: Reusable Capability Grant - Admin")
|
|
38
|
+
print("=" * 60)
|
|
39
|
+
|
|
40
|
+
# Step 1: Authenticate as admin (using bob with admin role)
|
|
41
|
+
print("\n[Step 1] Authenticating as admin...")
|
|
42
|
+
try:
|
|
43
|
+
jwt_token, _ = RemoteIdentityProvider().login("bob", "Bob@123!")
|
|
44
|
+
print(" Got JWT token")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(f" ERROR: Failed to authenticate: {e}")
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
# Step 2: Create admin agent
|
|
50
|
+
print("\n[Step 2] Creating admin agent...")
|
|
51
|
+
try:
|
|
52
|
+
admin = MACAWClient(
|
|
53
|
+
user_name="admin",
|
|
54
|
+
iam_token=jwt_token,
|
|
55
|
+
agent_type="user",
|
|
56
|
+
app_name="admin-console",
|
|
57
|
+
intent_policy={
|
|
58
|
+
"resources": ["attestation:*"],
|
|
59
|
+
"constraints": {
|
|
60
|
+
"roles": ["admin"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not admin.register():
|
|
66
|
+
print(" ERROR: Failed to register admin agent")
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
print(f" Agent ID: {admin.agent_id}")
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f" ERROR: {e}")
|
|
73
|
+
return 1
|
|
74
|
+
|
|
75
|
+
# Step 3: List pending attestations
|
|
76
|
+
print("\n[Step 3] Listing pending attestations...")
|
|
77
|
+
try:
|
|
78
|
+
attestations = admin.list_attestations(status="pending")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f" ERROR: {e}")
|
|
81
|
+
admin.unregister()
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
if not attestations:
|
|
85
|
+
print(" No pending attestations found.")
|
|
86
|
+
print("\n Make sure the researcher script is running and waiting.")
|
|
87
|
+
admin.unregister()
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
print(f"\n Found {len(attestations)} pending attestation(s):\n")
|
|
91
|
+
|
|
92
|
+
for i, att in enumerate(attestations, 1):
|
|
93
|
+
one_time = att.get('one_time', True)
|
|
94
|
+
print(f" [{i}] Key: {att.get('key')}")
|
|
95
|
+
print(f" For Agent: {att.get('for_agent')}")
|
|
96
|
+
print(f" Approval Criteria: {att.get('approval_criteria')}")
|
|
97
|
+
print(f" One-Time: {one_time}")
|
|
98
|
+
if not one_time:
|
|
99
|
+
print(f" *** REUSABLE CAPABILITY - approval persists! ***")
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
# Step 4: Approve attestation(s)
|
|
103
|
+
print("[Step 4] Process attestations...")
|
|
104
|
+
print(" Options: [y] Approve [d] Deny [s] Skip")
|
|
105
|
+
|
|
106
|
+
for i, att in enumerate(attestations, 1):
|
|
107
|
+
key = att.get('key')
|
|
108
|
+
for_agent = att.get('for_agent')
|
|
109
|
+
one_time = att.get('one_time', True)
|
|
110
|
+
|
|
111
|
+
prompt = f"\n '{key}' for {for_agent}"
|
|
112
|
+
if not one_time:
|
|
113
|
+
prompt += " (REUSABLE)"
|
|
114
|
+
prompt += "? [y/d/s]: "
|
|
115
|
+
|
|
116
|
+
response = input(prompt).strip().lower()
|
|
117
|
+
|
|
118
|
+
if response == 'y':
|
|
119
|
+
print(f" Approving...")
|
|
120
|
+
try:
|
|
121
|
+
if admin.approve_attestation(att, reason="Admin granted capability"):
|
|
122
|
+
print(f" APPROVED")
|
|
123
|
+
if not one_time:
|
|
124
|
+
print(f" *** This approval will be REUSED for future invocations ***")
|
|
125
|
+
else:
|
|
126
|
+
print(f" Failed to approve")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f" ERROR: {e}")
|
|
129
|
+
|
|
130
|
+
elif response == 'd':
|
|
131
|
+
print(f" Denying...")
|
|
132
|
+
try:
|
|
133
|
+
if admin.deny_attestation(att, reason="Admin denied capability"):
|
|
134
|
+
print(f" DENIED")
|
|
135
|
+
else:
|
|
136
|
+
print(f" Failed to deny")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f" ERROR: {e}")
|
|
139
|
+
|
|
140
|
+
else:
|
|
141
|
+
print(f" Skipped")
|
|
142
|
+
|
|
143
|
+
# Cleanup
|
|
144
|
+
admin.unregister()
|
|
145
|
+
|
|
146
|
+
print("\n" + "=" * 60)
|
|
147
|
+
print("Done. Researcher should now proceed.")
|
|
148
|
+
print("Future invocations will reuse this approval (no blocking).")
|
|
149
|
+
print("=" * 60)
|
|
150
|
+
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
import sys
|
|
156
|
+
try:
|
|
157
|
+
sys.exit(main())
|
|
158
|
+
except Exception as e:
|
|
159
|
+
err = str(e)
|
|
160
|
+
print("\n" + "=" * 60)
|
|
161
|
+
if "Local provider does not support" in err:
|
|
162
|
+
print("ERROR: Identity Provider not configured")
|
|
163
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
164
|
+
elif "Connection refused" in err or "connect" in err.lower():
|
|
165
|
+
print("ERROR: Cannot connect to MACAW")
|
|
166
|
+
print("Fix: Ensure LocalAgent is running")
|
|
167
|
+
else:
|
|
168
|
+
print(f"ERROR: {e}")
|
|
169
|
+
print("=" * 60)
|
|
170
|
+
sys.exit(1)
|