@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,20 @@
|
|
|
1
|
+
Q4 2024 Sales Performance Report
|
|
2
|
+
=================================
|
|
3
|
+
|
|
4
|
+
Executive Summary:
|
|
5
|
+
Our Q4 performance shows strong growth with revenue reaching $11M across all segments.
|
|
6
|
+
|
|
7
|
+
Revenue Breakdown:
|
|
8
|
+
- Retail: $4.2M (38%)
|
|
9
|
+
- Enterprise: $3.8M (35%)
|
|
10
|
+
- Cloud Services: $2.1M (19%)
|
|
11
|
+
- Consulting: $0.9M (8%)
|
|
12
|
+
|
|
13
|
+
Key Achievements:
|
|
14
|
+
1. Exceeded quarterly targets by 23%
|
|
15
|
+
2. Expanded to 12 new markets
|
|
16
|
+
3. Customer satisfaction at 94%
|
|
17
|
+
4. Reduced churn to 2.1%
|
|
18
|
+
|
|
19
|
+
Outlook:
|
|
20
|
+
Strong momentum heading into 2025 with pipeline at $15M.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Tutorial 1: Role-Based AI Access Control
|
|
2
|
+
|
|
3
|
+
This tutorial demonstrates MACAW's policy enforcement for a fictional FinTech Corp with hierarchical policies.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The demo shows how different users get different AI capabilities based on their organizational role:
|
|
8
|
+
|
|
9
|
+
| User | Role | Models | Max Tokens |
|
|
10
|
+
|------|------|--------|------------|
|
|
11
|
+
| alice | Financial Analyst | GPT-3.5 only | 500 |
|
|
12
|
+
| bob | Finance Manager | GPT-3.5, GPT-4 | 2000 |
|
|
13
|
+
| carol | IT Administrator | All models | 4000 |
|
|
14
|
+
|
|
15
|
+
## Directory Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
tutorial-1/
|
|
19
|
+
├── app.py # Main demo (recommended starting point)
|
|
20
|
+
├── authprompts_demo.py # Advanced: authenticated prompts demo
|
|
21
|
+
├── demo_secureopenai_with_policies.py # Advanced: invoke_tool patterns
|
|
22
|
+
├── config/
|
|
23
|
+
│ └── claims-config.yaml # Universal JWT claims mapper
|
|
24
|
+
├── policies/
|
|
25
|
+
│ ├── company_FinTech_Corp.json
|
|
26
|
+
│ ├── bu_Analytics.json
|
|
27
|
+
│ ├── team_Reporting.json
|
|
28
|
+
│ ├── user_alice.json
|
|
29
|
+
│ ├── user_bob.json
|
|
30
|
+
│ ├── user_carol.json
|
|
31
|
+
│ └── llm-base.json
|
|
32
|
+
├── setup/
|
|
33
|
+
│ ├── keycloak_complete_setup.sh # For Keycloak IdP
|
|
34
|
+
│ └── auth0_complete_setup.sh # For Auth0 IdP
|
|
35
|
+
└── README.md
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### 1. Prerequisites
|
|
41
|
+
|
|
42
|
+
- Python 3.9+
|
|
43
|
+
- MACAW Console account ([console.macawsecurity.ai](https://console.macawsecurity.ai))
|
|
44
|
+
- macaw_client installed and configured
|
|
45
|
+
- OpenAI API key
|
|
46
|
+
|
|
47
|
+
### 2. Install Dependencies
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install macaw-adapters[openai]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Set Up Identity Provider
|
|
54
|
+
|
|
55
|
+
Choose one:
|
|
56
|
+
|
|
57
|
+
**Option A: Keycloak (Local)**
|
|
58
|
+
```bash
|
|
59
|
+
# Start Keycloak
|
|
60
|
+
docker run -d -p 8080:8080 \
|
|
61
|
+
-e KEYCLOAK_ADMIN=admin \
|
|
62
|
+
-e KEYCLOAK_ADMIN_PASSWORD=admin123 \
|
|
63
|
+
quay.io/keycloak/keycloak:23.0 start-dev
|
|
64
|
+
|
|
65
|
+
# Wait for startup, then run setup
|
|
66
|
+
cd setup
|
|
67
|
+
./keycloak_complete_setup.sh
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Option B: Auth0 (Cloud)**
|
|
71
|
+
```bash
|
|
72
|
+
# Set your Auth0 credentials
|
|
73
|
+
export AUTH0_DOMAIN=your-tenant.auth0.com
|
|
74
|
+
export AUTH0_M2M_CLIENT_ID=your-m2m-client-id
|
|
75
|
+
export AUTH0_M2M_CLIENT_SECRET=your-m2m-secret
|
|
76
|
+
|
|
77
|
+
cd setup
|
|
78
|
+
./auth0_complete_setup.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4. Configure MACAW
|
|
82
|
+
|
|
83
|
+
Update `~/.macaw/config.json` with your IdP settings. The setup scripts will provide the exact values.
|
|
84
|
+
|
|
85
|
+
### 5. Load Policies
|
|
86
|
+
|
|
87
|
+
Import the policies from the `policies/` directory into your MACAW workspace via the Console.
|
|
88
|
+
|
|
89
|
+
### 6. Run the Demo
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
export OPENAI_API_KEY=sk-your-key
|
|
93
|
+
python3 app.py
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## What the Demo Shows
|
|
97
|
+
|
|
98
|
+
1. **Authentication**: Each user authenticates with the IdP (Keycloak/Auth0)
|
|
99
|
+
2. **Policy Resolution**: MACAW resolves the user's effective policy from the hierarchy
|
|
100
|
+
3. **Enforcement**: Requests are allowed or blocked based on policy rules
|
|
101
|
+
|
|
102
|
+
### Expected Output
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Testing alice (Financial Analyst)
|
|
106
|
+
Test 1: GPT-3.5 with 400 tokens
|
|
107
|
+
[PASS] Response received: ...
|
|
108
|
+
Test 2: GPT-4 with 400 tokens
|
|
109
|
+
[PASS] Correctly blocked: model not allowed
|
|
110
|
+
Test 3: GPT-3.5 with 1000 tokens (exceeds limit)
|
|
111
|
+
[PASS] Correctly blocked: exceeds max_tokens
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Policy Hierarchy
|
|
115
|
+
|
|
116
|
+
The policies demonstrate MACAW's hierarchical policy model:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
company_FinTech_Corp (base restrictions)
|
|
120
|
+
└── bu_Analytics (business unit)
|
|
121
|
+
└── team_Reporting (team level)
|
|
122
|
+
└── user_alice (individual user)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Lower levels can only restrict, never expand permissions from parent policies.
|
|
126
|
+
|
|
127
|
+
## Claims Mapping
|
|
128
|
+
|
|
129
|
+
The `config/claims-config.yaml` shows how MACAW maps JWT claims from different IdPs to a common format:
|
|
130
|
+
|
|
131
|
+
- Keycloak: `realm_access.roles` → `macaw.roles`
|
|
132
|
+
- Auth0: `https://macaw.local/roles` → `macaw.roles`
|
|
133
|
+
- Azure AD: `roles` → `macaw.roles`
|
|
134
|
+
- etc.
|
|
135
|
+
|
|
136
|
+
This allows the same policies to work with any OIDC-compliant IdP.
|
|
137
|
+
|
|
138
|
+
## Next Steps
|
|
139
|
+
|
|
140
|
+
- Explore the full policies in `policies/`
|
|
141
|
+
- Try modifying user policies and re-running
|
|
142
|
+
- Check the MACAW Console Logs tab to see policy decisions
|
|
143
|
+
- Read about [Authenticated Prompts](https://docs.macawsecurity.ai/concepts/authenticated-prompts) for the advanced demos
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Role-Based AI Access Control Demo
|
|
4
|
+
|
|
5
|
+
Demonstrates MACAW policy enforcement with three users:
|
|
6
|
+
- alice: Financial Analyst (GPT-3.5 only, 500 tokens)
|
|
7
|
+
- bob: Finance Manager (GPT-3.5/4, 2000 tokens)
|
|
8
|
+
- carol: IT Administrator (All models, 4000 tokens)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
# MACAW imports
|
|
15
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
16
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Test users configuration
|
|
20
|
+
TEST_USERS = {
|
|
21
|
+
"alice": {
|
|
22
|
+
"password": "Alice123!",
|
|
23
|
+
"role": "Financial Analyst",
|
|
24
|
+
"allowed_models": ["gpt-3.5-turbo"],
|
|
25
|
+
"max_tokens": 500,
|
|
26
|
+
},
|
|
27
|
+
"bob": {
|
|
28
|
+
"password": "Bob@123!",
|
|
29
|
+
"role": "Finance Manager",
|
|
30
|
+
"allowed_models": ["gpt-3.5-turbo", "gpt-4"],
|
|
31
|
+
"max_tokens": 2000,
|
|
32
|
+
},
|
|
33
|
+
"carol": {
|
|
34
|
+
"password": "Carol123!",
|
|
35
|
+
"role": "IT Administrator",
|
|
36
|
+
"allowed_models": ["gpt-3.5-turbo", "gpt-4", "gpt-4-turbo"],
|
|
37
|
+
"max_tokens": 4000,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_user_with_bind_to_user(openai_service, username: str, user_config: dict):
|
|
43
|
+
"""Test a user using the bind_to_user pattern."""
|
|
44
|
+
print(f"\n{'='*60}")
|
|
45
|
+
print(f"Testing {username} ({user_config['role']})")
|
|
46
|
+
print(f" Allowed models: {user_config['allowed_models']}")
|
|
47
|
+
print(f" Max tokens: {user_config['max_tokens']}")
|
|
48
|
+
print(f"{'='*60}")
|
|
49
|
+
|
|
50
|
+
# Authenticate user
|
|
51
|
+
try:
|
|
52
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, user_config["password"])
|
|
53
|
+
print(f" [OK] Authenticated {username}")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f" [ERROR] Failed to authenticate: {e}")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Create user agent
|
|
59
|
+
user = MACAWClient(
|
|
60
|
+
user_name=username,
|
|
61
|
+
iam_token=jwt_token,
|
|
62
|
+
agent_type="user",
|
|
63
|
+
app_name="financial-analyzer"
|
|
64
|
+
)
|
|
65
|
+
user.register()
|
|
66
|
+
print(f" [OK] Registered user agent: {user.agent_id}")
|
|
67
|
+
|
|
68
|
+
# Bind user to service
|
|
69
|
+
user_openai = openai_service.bind_to_user(user)
|
|
70
|
+
print(f" [OK] Bound user to OpenAI service")
|
|
71
|
+
|
|
72
|
+
# Test 1: Allowed request (GPT-3.5)
|
|
73
|
+
print(f"\n Test 1: GPT-3.5 with 400 tokens")
|
|
74
|
+
try:
|
|
75
|
+
response = user_openai.chat.completions.create(
|
|
76
|
+
model="gpt-3.5-turbo",
|
|
77
|
+
max_tokens=400,
|
|
78
|
+
messages=[{"role": "user", "content": "What is compound interest? Brief answer."}]
|
|
79
|
+
)
|
|
80
|
+
print(f" [PASS] Response received: {response.choices[0].message.content[:50]}...")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f" [FAIL] Unexpected error: {e}")
|
|
83
|
+
|
|
84
|
+
# Test 2: GPT-4 (only bob and carol allowed)
|
|
85
|
+
print(f"\n Test 2: GPT-4 with 400 tokens")
|
|
86
|
+
try:
|
|
87
|
+
response = user_openai.chat.completions.create(
|
|
88
|
+
model="gpt-4",
|
|
89
|
+
max_tokens=400,
|
|
90
|
+
messages=[{"role": "user", "content": "What is revenue growth? Brief answer."}]
|
|
91
|
+
)
|
|
92
|
+
if "gpt-4" in user_config["allowed_models"]:
|
|
93
|
+
print(f" [PASS] Response received (GPT-4 allowed for {username})")
|
|
94
|
+
else:
|
|
95
|
+
print(f" [FAIL] Should have been blocked!")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
if "gpt-4" not in user_config["allowed_models"]:
|
|
98
|
+
print(f" [PASS] Correctly blocked: {str(e)[:50]}...")
|
|
99
|
+
else:
|
|
100
|
+
print(f" [FAIL] Should have been allowed: {e}")
|
|
101
|
+
|
|
102
|
+
# Test 3: Exceeding token limit
|
|
103
|
+
exceeded_tokens = user_config["max_tokens"] + 500
|
|
104
|
+
print(f"\n Test 3: GPT-3.5 with {exceeded_tokens} tokens (exceeds limit)")
|
|
105
|
+
try:
|
|
106
|
+
response = user_openai.chat.completions.create(
|
|
107
|
+
model="gpt-3.5-turbo",
|
|
108
|
+
max_tokens=exceeded_tokens,
|
|
109
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
110
|
+
)
|
|
111
|
+
print(f" [FAIL] Should have been blocked (exceeds max_tokens)!")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(f" [PASS] Correctly blocked: {str(e)[:50]}...")
|
|
114
|
+
|
|
115
|
+
# Cleanup
|
|
116
|
+
user_openai.unbind()
|
|
117
|
+
print(f"\n [OK] Unbound user from service")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
# Check for API key
|
|
122
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
123
|
+
print("ERROR: Set OPENAI_API_KEY environment variable")
|
|
124
|
+
print(" export OPENAI_API_KEY=sk-your-key-here")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
print("=" * 60)
|
|
128
|
+
print("MACAW Role-Based AI Access Control Demo")
|
|
129
|
+
print("=" * 60)
|
|
130
|
+
|
|
131
|
+
# Create shared OpenAI service
|
|
132
|
+
print("\n[Setup] Creating SecureOpenAI service...")
|
|
133
|
+
openai_service = SecureOpenAI(app_name="financial-analyzer")
|
|
134
|
+
print(f" Service ID: {openai_service.server_id}")
|
|
135
|
+
|
|
136
|
+
# Test each user
|
|
137
|
+
for username, config in TEST_USERS.items():
|
|
138
|
+
test_user_with_bind_to_user(openai_service, username, config)
|
|
139
|
+
|
|
140
|
+
print("\n" + "=" * 60)
|
|
141
|
+
print("Demo Complete!")
|
|
142
|
+
print("=" * 60)
|
|
143
|
+
print("\nCheck the MACAW Console Logs tab to see policy decisions.")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
main()
|
|
@@ -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()
|