@macaw-ai/client 0.0.0-stage → 0.9.10
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 +278 -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,306 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Authenticated Prompts Demo with MACAW Policies
|
|
4
|
+
|
|
5
|
+
This demo extends the basic demo to show authenticated prompts:
|
|
6
|
+
|
|
7
|
+
1. Single SecureOpenAI service agent (created once)
|
|
8
|
+
2. User agents with JWT delegation (automatic embedded_context)
|
|
9
|
+
3. A2A calls from users to service
|
|
10
|
+
4. Policy enforcement at ToolAgent PEP level
|
|
11
|
+
5. Authenticated prompts auto-created by invoke_tool
|
|
12
|
+
|
|
13
|
+
Tests two paths:
|
|
14
|
+
- PATH 1: Direct invoke_tool (existing pattern)
|
|
15
|
+
- PATH 2: bind_to_user wrapper (new pattern - same underlying invoke_tool)
|
|
16
|
+
|
|
17
|
+
Both paths auto-create authenticated prompts via registry lookup.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
24
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# User-specific test configurations based on their policies
|
|
28
|
+
# Alice: GPT-3.5 only, max 500 tokens
|
|
29
|
+
# Bob: GPT-3.5/4, max 2000 tokens
|
|
30
|
+
USER_TESTS = {
|
|
31
|
+
"alice": {
|
|
32
|
+
"password": "Alice123!",
|
|
33
|
+
"policy_desc": "gpt-3.5-turbo only, max_tokens <= 500",
|
|
34
|
+
"tests": [
|
|
35
|
+
# (model, max_tokens, query, should_succeed)
|
|
36
|
+
("gpt-3.5-turbo", 400, "What is revenue growth?", True), # ALLOWED
|
|
37
|
+
("gpt-4", 400, "What is revenue growth?", False), # BLOCKED - wrong model
|
|
38
|
+
("gpt-3.5-turbo", 600, "What is compound interest?", False), # BLOCKED - exceeds max_tokens
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"bob": {
|
|
42
|
+
"password": "Bob@123!",
|
|
43
|
+
"policy_desc": "gpt-3.5-turbo/gpt-4, max_tokens <= 2000",
|
|
44
|
+
"tests": [
|
|
45
|
+
# (model, max_tokens, query, should_succeed)
|
|
46
|
+
("gpt-3.5-turbo", 400, "What is revenue growth?", True), # ALLOWED
|
|
47
|
+
("gpt-4", 400, "What is market cap?", True), # ALLOWED - Bob CAN use gpt-4
|
|
48
|
+
("gpt-3.5-turbo", 600, "What is compound interest?", True), # ALLOWED - Bob's limit is 2000
|
|
49
|
+
("gpt-4", 2500, "Deep financial analysis", False), # BLOCKED - exceeds max_tokens
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_user_path1(username: str, openai_service: SecureOpenAI):
|
|
56
|
+
"""
|
|
57
|
+
PATH 1: Direct invoke_tool (existing pattern).
|
|
58
|
+
|
|
59
|
+
invoke_tool auto-creates authenticated prompts based on registry lookup.
|
|
60
|
+
"""
|
|
61
|
+
print(f"\n{'='*60}")
|
|
62
|
+
print(f"PATH 1: {username.upper()} via invoke_tool")
|
|
63
|
+
print("="*60)
|
|
64
|
+
|
|
65
|
+
user_config = USER_TESTS[username]
|
|
66
|
+
|
|
67
|
+
# 1. Get JWT
|
|
68
|
+
print(f"1. Authenticating...")
|
|
69
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, user_config["password"])
|
|
70
|
+
print(" OK Got JWT token")
|
|
71
|
+
|
|
72
|
+
# 2. Create user agent with JWT
|
|
73
|
+
print("2. Creating user agent...")
|
|
74
|
+
user = MACAWClient(
|
|
75
|
+
user_name=username,
|
|
76
|
+
iam_token=jwt_token, # Converted to embedded_context automatically!
|
|
77
|
+
agent_type="user",
|
|
78
|
+
app_name="financial-analyzer"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if not user.register():
|
|
82
|
+
print(" FAIL Failed to create user agent")
|
|
83
|
+
return None
|
|
84
|
+
print(f" OK User agent: {user.agent_id}")
|
|
85
|
+
|
|
86
|
+
# 3. Test with invoke_tool (auto-creates authenticated prompts!)
|
|
87
|
+
tests = user_config["tests"]
|
|
88
|
+
|
|
89
|
+
print(f"\n3. Testing via invoke_tool to service {openai_service.server_id}:")
|
|
90
|
+
print(" (invoke_tool will auto-create authenticated prompts for 'messages')")
|
|
91
|
+
print(f" Policy: {username} -> {user_config['policy_desc']}")
|
|
92
|
+
|
|
93
|
+
for model, max_tokens, query, should_succeed in tests:
|
|
94
|
+
expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
|
|
95
|
+
print(f"\n -> {model} with {max_tokens} tokens ({expected})")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# A2A call to single service agent
|
|
99
|
+
# invoke_tool auto-creates authenticated prompts!
|
|
100
|
+
result = user.invoke_tool(
|
|
101
|
+
tool_name=f"tool:{openai_service.app_name}/generate",
|
|
102
|
+
parameters={
|
|
103
|
+
"model": model,
|
|
104
|
+
"max_tokens": max_tokens,
|
|
105
|
+
"messages": [
|
|
106
|
+
{"role": "system", "content": "You are a financial analyst."},
|
|
107
|
+
{"role": "user", "content": query}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
target_agent=openai_service.server_id
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Check if we got a valid response
|
|
114
|
+
if isinstance(result, dict):
|
|
115
|
+
if "error" in result:
|
|
116
|
+
if should_succeed:
|
|
117
|
+
print(f" FAIL UNEXPECTED FAILURE: {result['error']}")
|
|
118
|
+
else:
|
|
119
|
+
print(f" PASS CORRECTLY BLOCKED: {result['error'][:60]}...")
|
|
120
|
+
elif "choices" in result:
|
|
121
|
+
if should_succeed:
|
|
122
|
+
print(f" PASS SUCCESS (as expected)")
|
|
123
|
+
print(f" Model: {result.get('model')}")
|
|
124
|
+
content = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
|
125
|
+
print(f" Response: {content[:80]}...")
|
|
126
|
+
else:
|
|
127
|
+
print(f" FAIL UNEXPECTED SUCCESS - should have been blocked!")
|
|
128
|
+
else:
|
|
129
|
+
print(f" FAIL Unexpected response: {result}")
|
|
130
|
+
else:
|
|
131
|
+
print(f" FAIL Invalid response type: {type(result)}")
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
error = str(e)
|
|
135
|
+
is_policy_block = ("not in allowed" in error or "not permitted" in error or
|
|
136
|
+
"policy" in error.lower() or "max_tokens" in error or
|
|
137
|
+
"model" in error or "blocked" in error.lower())
|
|
138
|
+
if is_policy_block:
|
|
139
|
+
if should_succeed:
|
|
140
|
+
print(f" FAIL UNEXPECTED BLOCK: {error}")
|
|
141
|
+
else:
|
|
142
|
+
print(f" PASS CORRECTLY BLOCKED by policy")
|
|
143
|
+
else:
|
|
144
|
+
print(f" FAIL Error: {error}")
|
|
145
|
+
|
|
146
|
+
return user
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_user_path2(username: str, openai_service: SecureOpenAI):
|
|
150
|
+
"""
|
|
151
|
+
PATH 2: bind_to_user wrapper (new pattern).
|
|
152
|
+
|
|
153
|
+
Same underlying invoke_tool, but wrapped in OpenAI-compatible API.
|
|
154
|
+
"""
|
|
155
|
+
print(f"\n{'='*60}")
|
|
156
|
+
print(f"PATH 2: {username.upper()} via bind_to_user")
|
|
157
|
+
print("="*60)
|
|
158
|
+
|
|
159
|
+
user_config = USER_TESTS[username]
|
|
160
|
+
|
|
161
|
+
# 1. Get JWT
|
|
162
|
+
print(f"1. Authenticating...")
|
|
163
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, user_config["password"])
|
|
164
|
+
print(" OK Got JWT token")
|
|
165
|
+
|
|
166
|
+
# 2. Create user agent with JWT
|
|
167
|
+
print("2. Creating user agent...")
|
|
168
|
+
user = MACAWClient(
|
|
169
|
+
user_name=username,
|
|
170
|
+
iam_token=jwt_token,
|
|
171
|
+
agent_type="user",
|
|
172
|
+
app_name="financial-analyzer"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if not user.register():
|
|
176
|
+
print(" FAIL Failed to create user agent")
|
|
177
|
+
return
|
|
178
|
+
print(f" OK User agent: {user.agent_id}")
|
|
179
|
+
|
|
180
|
+
# 3. Bind user to service
|
|
181
|
+
print("3. Binding user to SecureOpenAI service...")
|
|
182
|
+
user_openai = openai_service.bind_to_user(user)
|
|
183
|
+
print(f" OK Bound to: {openai_service.server_id}")
|
|
184
|
+
|
|
185
|
+
# 4. Test with OpenAI-style API
|
|
186
|
+
tests = user_config["tests"]
|
|
187
|
+
|
|
188
|
+
print(f"\n4. Testing via bind_to_user wrapper:")
|
|
189
|
+
print(" (internally calls invoke_tool which auto-creates auth prompts)")
|
|
190
|
+
print(f" Policy: {username} -> {user_config['policy_desc']}")
|
|
191
|
+
|
|
192
|
+
for model, max_tokens, query, should_succeed in tests:
|
|
193
|
+
expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
|
|
194
|
+
print(f"\n -> {model} with {max_tokens} tokens ({expected})")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
response = user_openai.chat.completions.create(
|
|
198
|
+
model=model,
|
|
199
|
+
max_tokens=max_tokens,
|
|
200
|
+
messages=[
|
|
201
|
+
{"role": "system", "content": "You are a financial analyst."},
|
|
202
|
+
{"role": "user", "content": query}
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if should_succeed:
|
|
207
|
+
print(f" PASS SUCCESS (as expected)")
|
|
208
|
+
print(f" Model: {response.model}")
|
|
209
|
+
print(f" Response: {response.choices[0].message.content[:80]}...")
|
|
210
|
+
else:
|
|
211
|
+
print(f" FAIL UNEXPECTED SUCCESS - should have been blocked!")
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
error = str(e)
|
|
215
|
+
is_policy_block = ("not in allowed" in error or "not permitted" in error or
|
|
216
|
+
"policy" in error.lower() or "max_tokens" in error or
|
|
217
|
+
"model" in error or "blocked" in error.lower())
|
|
218
|
+
if is_policy_block:
|
|
219
|
+
if should_succeed:
|
|
220
|
+
print(f" FAIL UNEXPECTED BLOCK: {error}")
|
|
221
|
+
else:
|
|
222
|
+
print(f" PASS CORRECTLY BLOCKED by policy")
|
|
223
|
+
else:
|
|
224
|
+
print(f" FAIL Error: {error}")
|
|
225
|
+
|
|
226
|
+
# 5. Demonstrate unbind
|
|
227
|
+
print(f"\n5. Testing unbind()...")
|
|
228
|
+
print(f" is_bound before: {user_openai.is_bound}")
|
|
229
|
+
user_openai.unbind()
|
|
230
|
+
print(f" is_bound after: {user_openai.is_bound}")
|
|
231
|
+
try:
|
|
232
|
+
user_openai.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "test"}])
|
|
233
|
+
print(f" FAIL Call should have failed after unbind!")
|
|
234
|
+
except RuntimeError as e:
|
|
235
|
+
print(f" PASS Correctly rejected: {str(e)[:50]}...")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main():
|
|
239
|
+
"""Test authenticated prompts with both paths."""
|
|
240
|
+
print("\n" + "="*70)
|
|
241
|
+
print("Authenticated Prompts + MACAW Policy Demo")
|
|
242
|
+
print("="*70)
|
|
243
|
+
print("\nArchitecture:")
|
|
244
|
+
print(" - Single SecureOpenAI service agent (created once)")
|
|
245
|
+
print(" - User agents with JWT -> embedded_context (automatic)")
|
|
246
|
+
print(" - A2A calls: User -> SecureOpenAI service")
|
|
247
|
+
print(" - Policy enforcement at ToolAgent PEP level")
|
|
248
|
+
print(" - Authenticated prompts auto-created by invoke_tool")
|
|
249
|
+
|
|
250
|
+
print("\nTwo paths tested:")
|
|
251
|
+
print(" PATH 1: Direct invoke_tool (existing pattern)")
|
|
252
|
+
print(" PATH 2: bind_to_user wrapper (new pattern)")
|
|
253
|
+
|
|
254
|
+
print("\nExpected policies (enforced by ToolAgent PEP):")
|
|
255
|
+
print(" - Alice: GPT-3.5 only, max 500 tokens")
|
|
256
|
+
print(" - Bob: GPT-3.5/4, max 2000 tokens")
|
|
257
|
+
|
|
258
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
259
|
+
print("\nWARNING: No OPENAI_API_KEY - demo will fail")
|
|
260
|
+
print(" Set with: export OPENAI_API_KEY=sk-...")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
# Create SINGLE SecureOpenAI service (ONCE!)
|
|
264
|
+
print("\n" + "="*70)
|
|
265
|
+
print("Creating single SecureOpenAI service agent...")
|
|
266
|
+
print("="*70)
|
|
267
|
+
|
|
268
|
+
openai_service = SecureOpenAI(
|
|
269
|
+
api_key=os.environ.get("OPENAI_API_KEY"),
|
|
270
|
+
app_name="openai-service"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
print(f"OK SecureOpenAI service: {openai_service.server_id}")
|
|
274
|
+
|
|
275
|
+
# Test Alice with PATH 1 (invoke_tool)
|
|
276
|
+
try:
|
|
277
|
+
test_user_path1("alice", openai_service)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
print(f"\nFAIL Failed PATH 1 for alice: {e}")
|
|
280
|
+
|
|
281
|
+
# Test Alice with PATH 2 (bind_to_user)
|
|
282
|
+
try:
|
|
283
|
+
test_user_path2("alice", openai_service)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(f"\nFAIL Failed PATH 2 for alice: {e}")
|
|
286
|
+
|
|
287
|
+
# Test Bob with PATH 1 (invoke_tool)
|
|
288
|
+
try:
|
|
289
|
+
test_user_path1("bob", openai_service)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"\nFAIL Failed PATH 1 for bob: {e}")
|
|
292
|
+
|
|
293
|
+
# Test Bob with PATH 2 (bind_to_user)
|
|
294
|
+
try:
|
|
295
|
+
test_user_path2("bob", openai_service)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
print(f"\nFAIL Failed PATH 2 for bob: {e}")
|
|
298
|
+
|
|
299
|
+
# Summary
|
|
300
|
+
print("\n" + "="*70)
|
|
301
|
+
print("Demo complete!")
|
|
302
|
+
print("="*70)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
main()
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# MACAW Identity Bridge Configuration
|
|
2
|
+
# Universal translator for JWT tokens from any identity provider
|
|
3
|
+
|
|
4
|
+
# MACAW Internal Security Context Model (READ-ONLY - DO NOT MODIFY)
|
|
5
|
+
# These attributes are used by PolicyResolver for hierarchical policy lookup
|
|
6
|
+
internal_model:
|
|
7
|
+
company: "Organization/company for policy hierarchy (maps from organization claim)"
|
|
8
|
+
business_unit: "Department or business unit"
|
|
9
|
+
team: "Team or group"
|
|
10
|
+
user: "User identifier for user-specific policies"
|
|
11
|
+
roles: "User roles for role-based access control"
|
|
12
|
+
email: "User email address"
|
|
13
|
+
|
|
14
|
+
# Identity Provider Configurations
|
|
15
|
+
# Each provider has detection rules, claim mappings, and role processing
|
|
16
|
+
identity_providers:
|
|
17
|
+
keycloak:
|
|
18
|
+
name: "Keycloak (Red Hat SSO)"
|
|
19
|
+
detection:
|
|
20
|
+
iss_pattern: "*realms/*"
|
|
21
|
+
required_claims: ["realm_access"]
|
|
22
|
+
mappings:
|
|
23
|
+
# Core identity fields
|
|
24
|
+
subject_path: "sub"
|
|
25
|
+
email_path: "email"
|
|
26
|
+
name_path: "preferred_username"
|
|
27
|
+
# Organizational hierarchy (organization maps to company)
|
|
28
|
+
organization_path: "organization"
|
|
29
|
+
business_unit_path: "business_unit"
|
|
30
|
+
team_path: "team"
|
|
31
|
+
# Role extraction from Keycloak structure
|
|
32
|
+
roles_path: "realm_access.roles"
|
|
33
|
+
role_filter:
|
|
34
|
+
allowed: ["analyst", "manager", "admin", "viewer"]
|
|
35
|
+
case_sensitive: false
|
|
36
|
+
|
|
37
|
+
azure_ad:
|
|
38
|
+
name: "Azure Active Directory"
|
|
39
|
+
detection:
|
|
40
|
+
iss_pattern: "https://sts.windows.net/*"
|
|
41
|
+
required_claims: ["oid", "tid"]
|
|
42
|
+
mappings:
|
|
43
|
+
# Core identity fields
|
|
44
|
+
subject_path: "oid"
|
|
45
|
+
email_path: "upn"
|
|
46
|
+
name_path: "name"
|
|
47
|
+
# Organizational hierarchy (tid maps to company)
|
|
48
|
+
organization_path: "tid"
|
|
49
|
+
business_unit_path: "department"
|
|
50
|
+
team_path: "jobTitle"
|
|
51
|
+
# Role extraction
|
|
52
|
+
roles_path: "roles"
|
|
53
|
+
role_filter:
|
|
54
|
+
allowed: ["analyst", "manager", "admin", "viewer"]
|
|
55
|
+
case_sensitive: false
|
|
56
|
+
|
|
57
|
+
okta:
|
|
58
|
+
name: "Okta"
|
|
59
|
+
detection:
|
|
60
|
+
iss_pattern: "https://*.okta.com"
|
|
61
|
+
required_claims: ["groups"]
|
|
62
|
+
mappings:
|
|
63
|
+
# Core identity fields
|
|
64
|
+
subject_path: "sub"
|
|
65
|
+
email_path: "email"
|
|
66
|
+
name_path: "name"
|
|
67
|
+
# Organizational hierarchy (org maps to company)
|
|
68
|
+
organization_path: "org"
|
|
69
|
+
business_unit_path: "department"
|
|
70
|
+
team_path: "division"
|
|
71
|
+
# Role extraction from groups
|
|
72
|
+
roles_path: "groups"
|
|
73
|
+
role_filter:
|
|
74
|
+
allowed: ["analysts", "managers", "admins", "viewers"] # Okta often uses plurals
|
|
75
|
+
case_sensitive: false
|
|
76
|
+
# Map Okta group names to standard MACAW roles
|
|
77
|
+
role_mapping:
|
|
78
|
+
analysts: analyst
|
|
79
|
+
managers: manager
|
|
80
|
+
admins: admin
|
|
81
|
+
viewers: viewer
|
|
82
|
+
|
|
83
|
+
auth0:
|
|
84
|
+
name: "Auth0"
|
|
85
|
+
detection:
|
|
86
|
+
iss_pattern: "auth0.com" # Matches any Auth0 issuer (including regional like .us.auth0.com)
|
|
87
|
+
required_claims: [] # Auth0 access tokens use namespaced custom claims
|
|
88
|
+
mappings:
|
|
89
|
+
# Core identity fields (standard OIDC claims)
|
|
90
|
+
subject_path: "sub"
|
|
91
|
+
email_path: "email" # Standard OIDC claim, not namespaced
|
|
92
|
+
name_path: "https://macaw.local/username" # Added by our Action
|
|
93
|
+
# Organizational hierarchy from Auth0 Action custom claims
|
|
94
|
+
organization_path: "https://macaw.local/organization"
|
|
95
|
+
business_unit_path: "https://macaw.local/business_unit"
|
|
96
|
+
team_path: "https://macaw.local/team"
|
|
97
|
+
# Role extraction from Auth0 Action custom claims
|
|
98
|
+
roles_path: "https://macaw.local/roles"
|
|
99
|
+
# Additional MACAW-specific claims for policy enforcement
|
|
100
|
+
tier_path: "https://macaw.local/tier"
|
|
101
|
+
max_tokens_path: "https://macaw.local/max_tokens"
|
|
102
|
+
allowed_models_path: "https://macaw.local/allowed_models"
|
|
103
|
+
role_filter:
|
|
104
|
+
allowed: ["analyst", "manager", "admin", "viewer"]
|
|
105
|
+
case_sensitive: false
|
|
106
|
+
|
|
107
|
+
google_workspace:
|
|
108
|
+
name: "Google Workspace"
|
|
109
|
+
detection:
|
|
110
|
+
iss_pattern: "https://accounts.google.com"
|
|
111
|
+
required_claims: ["hd"]
|
|
112
|
+
mappings:
|
|
113
|
+
# Core identity fields
|
|
114
|
+
subject_path: "sub"
|
|
115
|
+
email_path: "email"
|
|
116
|
+
name_path: "name"
|
|
117
|
+
# Organizational hierarchy (hd = hosted domain maps to company)
|
|
118
|
+
organization_path: "hd"
|
|
119
|
+
business_unit_path: "department"
|
|
120
|
+
team_path: "team"
|
|
121
|
+
# Role extraction
|
|
122
|
+
roles_path: "custom_roles"
|
|
123
|
+
role_filter:
|
|
124
|
+
allowed: ["analyst", "manager", "admin", "viewer"]
|
|
125
|
+
case_sensitive: false
|
|
126
|
+
|
|
127
|
+
# Default fallback for unknown providers
|
|
128
|
+
default:
|
|
129
|
+
name: "Generic JWT Provider"
|
|
130
|
+
mappings:
|
|
131
|
+
subject_path: "sub"
|
|
132
|
+
email_path: "email"
|
|
133
|
+
name_path: "name"
|
|
134
|
+
organization_path: "org"
|
|
135
|
+
business_unit_path: "department"
|
|
136
|
+
team_path: "team"
|
|
137
|
+
roles_path: "roles"
|
|
138
|
+
role_filter:
|
|
139
|
+
allowed: ["analyst", "manager", "admin", "viewer"]
|
|
140
|
+
case_sensitive: false
|
|
141
|
+
|
|
142
|
+
# Security and validation settings
|
|
143
|
+
security:
|
|
144
|
+
require_roles: true
|
|
145
|
+
require_organization: true
|
|
146
|
+
max_token_age_minutes: 60
|
|
147
|
+
|
|
148
|
+
# Audit and logging settings
|
|
149
|
+
audit:
|
|
150
|
+
log_identity_mapping: true
|
|
151
|
+
log_role_filtering: true
|
|
152
|
+
log_provider_detection: true
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SecureOpenAI Demo with MACAW Policies
|
|
4
|
+
|
|
5
|
+
This demo shows the efficient architecture:
|
|
6
|
+
1. Single SecureOpenAI service agent (created once)
|
|
7
|
+
2. User agents with JWT delegation (automatic embedded_context)
|
|
8
|
+
3. A2A calls from users to service
|
|
9
|
+
4. Policy enforcement at ToolAgent PEP level
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
16
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# User credentials
|
|
20
|
+
USERS = {
|
|
21
|
+
"alice": "Alice123!",
|
|
22
|
+
"bob": "Bob@123!",
|
|
23
|
+
"carol": "Carol123!",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_user(username: str, password: str, openai_service: SecureOpenAI):
|
|
28
|
+
"""Test what a user can access."""
|
|
29
|
+
print(f"\n{'='*60}")
|
|
30
|
+
print(f"Testing {username.upper()}")
|
|
31
|
+
print("="*60)
|
|
32
|
+
|
|
33
|
+
# 1. Get JWT
|
|
34
|
+
print(f"1. Authenticating...")
|
|
35
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, password)
|
|
36
|
+
print(" OK Got JWT token")
|
|
37
|
+
|
|
38
|
+
# 2. Create user agent with JWT
|
|
39
|
+
print("2. Creating user agent...")
|
|
40
|
+
user = MACAWClient(
|
|
41
|
+
user_name=username,
|
|
42
|
+
iam_token=jwt_token, # Converted to embedded_context automatically!
|
|
43
|
+
agent_type="user",
|
|
44
|
+
app_name="financial-analyzer"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if not user.register():
|
|
48
|
+
print(" FAIL Failed to create user agent")
|
|
49
|
+
return
|
|
50
|
+
print(f" OK User agent: {user.agent_id}")
|
|
51
|
+
|
|
52
|
+
# 3. Test different models and tokens
|
|
53
|
+
tests = [
|
|
54
|
+
("gpt-3.5-turbo", 400, "Quick analysis"),
|
|
55
|
+
("gpt-3.5-turbo", 600, "Detailed analysis"),
|
|
56
|
+
("gpt-4", 1500, "Deep analysis"),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
print(f"\n3. Testing OpenAI access via A2A to service {openai_service.server_id}:")
|
|
60
|
+
for model, max_tokens, query in tests:
|
|
61
|
+
print(f"\n -> {model} with {max_tokens} tokens")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# A2A call to single service agent
|
|
65
|
+
result = user.invoke_tool(
|
|
66
|
+
tool_name="tool:openai-service/generate", # MAPL-compliant resource name
|
|
67
|
+
parameters={
|
|
68
|
+
"model": model,
|
|
69
|
+
"max_tokens": max_tokens,
|
|
70
|
+
"messages": [
|
|
71
|
+
{"role": "system", "content": "You are a financial analyst."},
|
|
72
|
+
{"role": "user", "content": query}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
target_agent=openai_service.server_id # Same service for all users!
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Check if we got a valid response
|
|
79
|
+
if isinstance(result, dict):
|
|
80
|
+
if "error" in result:
|
|
81
|
+
print(f" FAIL Failed: {result['error']}")
|
|
82
|
+
elif "choices" in result:
|
|
83
|
+
print(f" PASS SUCCESS")
|
|
84
|
+
print(f" Model: {result.get('model')}")
|
|
85
|
+
content = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
|
86
|
+
print(f" Response: {content[:80]}...")
|
|
87
|
+
else:
|
|
88
|
+
print(f" FAIL Unexpected response: {result}")
|
|
89
|
+
else:
|
|
90
|
+
print(f" FAIL Invalid response type: {type(result)}")
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
error = str(e)
|
|
94
|
+
if "not in allowed" in error or "not permitted" in error or "policy" in error.lower():
|
|
95
|
+
print(f" BLOCKED by policy")
|
|
96
|
+
elif "max_tokens" in error:
|
|
97
|
+
print(f" BLOCKED: Exceeds token limit")
|
|
98
|
+
elif "model" in error:
|
|
99
|
+
print(f" BLOCKED: Model not allowed")
|
|
100
|
+
else:
|
|
101
|
+
print(f" Error: {error}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main():
|
|
105
|
+
"""Test all users."""
|
|
106
|
+
print("\n" + "="*70)
|
|
107
|
+
print("SecureOpenAI + MACAW Policy Demo")
|
|
108
|
+
print("="*70)
|
|
109
|
+
print("\nArchitecture:")
|
|
110
|
+
print(" - Single SecureOpenAI service agent (created once)")
|
|
111
|
+
print(" - User agents with JWT -> embedded_context (automatic)")
|
|
112
|
+
print(" - A2A calls: User -> SecureOpenAI service")
|
|
113
|
+
print(" - Policy enforcement at ToolAgent PEP level")
|
|
114
|
+
|
|
115
|
+
print("\nExpected results:")
|
|
116
|
+
print(" - Alice: GPT-3.5 only, max 500 tokens")
|
|
117
|
+
print(" - Bob: GPT-3.5/4, max 2000 tokens")
|
|
118
|
+
print(" - Carol: All models, max 4000 tokens")
|
|
119
|
+
|
|
120
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
121
|
+
print("\nWARNING: No OPENAI_API_KEY - demo will fail")
|
|
122
|
+
print(" Set with: export OPENAI_API_KEY=sk-...")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Create SINGLE SecureOpenAI service (ONCE!)
|
|
126
|
+
print("\n" + "="*70)
|
|
127
|
+
print("Creating single SecureOpenAI service agent...")
|
|
128
|
+
print("="*70)
|
|
129
|
+
|
|
130
|
+
openai_service = SecureOpenAI(
|
|
131
|
+
api_key=os.environ.get("OPENAI_API_KEY"),
|
|
132
|
+
app_name="openai-service"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
print(f"OK SecureOpenAI service: {openai_service.server_id}")
|
|
136
|
+
|
|
137
|
+
# Test each user with same service
|
|
138
|
+
for username, password in USERS.items():
|
|
139
|
+
try:
|
|
140
|
+
test_user(username, password, openai_service)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"\nFAIL Failed to test {username}: {e}")
|
|
143
|
+
|
|
144
|
+
print("\n" + "="*70)
|
|
145
|
+
print("Demo complete!")
|
|
146
|
+
print("="*70)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"constraints": {
|
|
3
|
+
"parameters": {
|
|
4
|
+
"tool:*/complete": {
|
|
5
|
+
"max_tokens": {
|
|
6
|
+
"max": 4000
|
|
7
|
+
},
|
|
8
|
+
"model": [
|
|
9
|
+
"gpt-3.5-turbo-instruct",
|
|
10
|
+
"davinci-002",
|
|
11
|
+
"babbage-002"
|
|
12
|
+
],
|
|
13
|
+
"temperature": {
|
|
14
|
+
"max": 2,
|
|
15
|
+
"min": 0
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"tool:*/embed": {
|
|
19
|
+
"model": [
|
|
20
|
+
"text-embedding-ada-002",
|
|
21
|
+
"text-embedding-3-small",
|
|
22
|
+
"text-embedding-3-large"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"tool:*/generate": {
|
|
26
|
+
"frequency_penalty": {
|
|
27
|
+
"max": 2,
|
|
28
|
+
"min": -2
|
|
29
|
+
},
|
|
30
|
+
"max_tokens": {
|
|
31
|
+
"max": 4000
|
|
32
|
+
},
|
|
33
|
+
"model": [
|
|
34
|
+
"gpt-3.5-turbo",
|
|
35
|
+
"gpt-4",
|
|
36
|
+
"claude-3-haiku-20240307",
|
|
37
|
+
"claude-3-sonnet-20240229",
|
|
38
|
+
"claude-opus-4-5-20251101"
|
|
39
|
+
],
|
|
40
|
+
"presence_penalty": {
|
|
41
|
+
"max": 2,
|
|
42
|
+
"min": -2
|
|
43
|
+
},
|
|
44
|
+
"temperature": {
|
|
45
|
+
"max": 1,
|
|
46
|
+
"min": 0
|
|
47
|
+
},
|
|
48
|
+
"top_p": {
|
|
49
|
+
"max": 1,
|
|
50
|
+
"min": 0
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"rate_limit": 100
|
|
55
|
+
},
|
|
56
|
+
"denied_resources": [
|
|
57
|
+
"*.secret",
|
|
58
|
+
"*.password",
|
|
59
|
+
"*.key",
|
|
60
|
+
"*.pem"
|
|
61
|
+
],
|
|
62
|
+
"description": "Company-wide base policy for FinTech Corp",
|
|
63
|
+
"policy_id": "company:FinTech Corp",
|
|
64
|
+
"resources": [
|
|
65
|
+
"tool:*/generate",
|
|
66
|
+
"tool:financial_analysis"
|
|
67
|
+
],
|
|
68
|
+
"scope": "global",
|
|
69
|
+
"version": "1.0"
|
|
70
|
+
}
|