Skip to content

Commit a27a176

Browse files
authored
Merge pull request #407 from aniloncloud/updates/fine_grained_access_controls_blog_feedback
Updates/fine grained access controls blog feedback
2 parents c999ebf + fb3513c commit a27a176

File tree

5 files changed

+225
-106
lines changed

5 files changed

+225
-106
lines changed

agents-and-function-calling/bedrock-agents/use-case-examples/fine-grained-access-permissions-agent/003_bedrock-agent/agent/agent.py

Lines changed: 141 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,114 +4,190 @@
44
import logging
55
import cognitojwt
66

7-
8-
# Set up logging
7+
# Enhanced logging configuration
98
logger = logging.getLogger(__name__)
109
logger.setLevel(logging.INFO)
10+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
11+
handler = logging.StreamHandler()
12+
handler.setFormatter(formatter)
13+
logger.addHandler(handler)
1114

1215
verified_permissions_client = boto3.client("verifiedpermissions")
1316

14-
1517
def get_named_parameter(event, name):
16-
return next(item for item in event['parameters'] if item['name'] == name)['value']
17-
18-
19-
def list_claims(event):
20-
# TODO: Implement logic to retrieve and return a list of claims
18+
logger.info(f"Retrieving parameter: {name} from event")
19+
value = next(item for item in event['parameters'] if item['name'] == name)['value']
20+
logger.info(f"Retrieved parameter value: {value}")
21+
return value
22+
23+
def list_claims(event, user_info):
24+
# This function retrieves claims based on user role, filtering for adjusters by region.
25+
# TODO: Implement logic to retrieve and return a list of claims from DynamoDB.
26+
# For now, we'll use dummy claims for demonstration purposes.
27+
logger.info(f"Entering list_claims function with user_info: {user_info}")
2128
dummy_claims = [
2229
{
2330
"id": 1,
2431
"claimAmount": 1000.0,
2532
"claimDescription": "Dummy claim 1",
26-
"claimStatus": "approved"
33+
"claimStatus": "approved",
34+
"region": "northeast"
2735
},
2836
{
2937
"id": 2,
3038
"claimAmount": 2500.75,
3139
"claimDescription": "Dummy claim 2",
32-
"claimStatus": "pending"
40+
"claimStatus": "pending",
41+
"region": "northwest"
3342
}
3443
]
35-
return dummy_claims
36-
44+
logger.info(f"Retrieved dummy claims: {dummy_claims}")
45+
46+
user_role = user_info.get('role')
47+
user_region = user_info.get('region')
48+
logger.info(f"Processing claims for user_role: {user_role}, region: {user_region}")
49+
50+
# Adjusters can only access claims in their assigned region
51+
if user_role == 'ClaimsAdjuster':
52+
logger.info("Filtering claims for adjuster role")
53+
filtered_claims = [claim for claim in dummy_claims if claim['region'] == user_region]
54+
logger.info(f"Filtered claims count: {len(filtered_claims)}")
55+
return filtered_claims
56+
else:
57+
# Non-adjusters (assumed to be admins) can access all claims
58+
logger.info("Returning all claims for admin role")
59+
return dummy_claims
3760

38-
def get_claim(event):
61+
def get_claim(event, user_info):
62+
# This function retrieves a claim by ID and applies role-based access control.
63+
logger.info(f"Entering get_claim function with user_info: {user_info}")
3964
claim_id = int(get_named_parameter(event, 'claimId'))
40-
# TODO: Implement logic to retrieve and return a claim by ID
65+
logger.info(f"Retrieving claim with ID: {claim_id}")
66+
67+
# TODO: Implement logic to retrieve a claim by ID from DynamoDB.
68+
# For now, we'll use a dummy claim for demonstration purposes.
4169
dummy_claim = {
4270
"id": claim_id,
4371
"claimAmount": 1000.0,
4472
"claimDescription": "Dummy claim 1",
45-
"claimStatus": "approved"
73+
"claimStatus": "approved",
74+
"region": "northeast"
4675
}
47-
return dummy_claim
76+
logger.info(f"Retrieved dummy claim: {dummy_claim}")
77+
78+
user_role = user_info.get('role')
79+
user_region = user_info.get('region')
80+
logger.info(f"Checking access for user_role: {user_role}, region: {user_region}")
81+
82+
if user_role == 'ClaimsAdjuster':
83+
# Adjusters can only access claims in their assigned region
84+
if dummy_claim['region'] == user_region:
85+
logger.info("Access granted: Claim region matches adjuster region")
86+
return dummy_claim
87+
else:
88+
logger.warning("Access denied: Claim region does not match adjuster region")
89+
return None
90+
else:
91+
# Non-adjusters (assumed to be admins) can access all claims
92+
logger.info("Access granted: Admin role")
93+
return dummy_claim
94+
95+
96+
# TODO: Implement error handling for cases where the claim doesn't exist
4897

4998

50-
def update_claim(event):
99+
def update_claim(event, user_info):
100+
# This method allows adjusters to update claims they are managing.
101+
# It assumes any calling method has already validated the adjuster's region.
102+
logger.info(f"Entering update_claim function with user_info: {user_info}")
103+
51104
claim_id = int(get_named_parameter(event, 'claimId'))
52-
# TODO: Implement logic to update and return a claim by ID
105+
logger.info(f"Updating claim with ID: {claim_id}")
106+
107+
# Using dummy claim data for demonstration purpose (to be replaced by database retrieval).
53108
dummy_claim = {
54109
"id": claim_id,
55110
"claimAmount": 3000.0,
56111
"claimDescription": "Updated dummy claim",
57-
"claimStatus": "approved"
112+
"claimStatus": "approved",
113+
"region": "northeast"
58114
}
59-
return dummy_claim
60115

116+
# Here we would normally retrieve the existing claim from the database but for now, we use the dummy claim.
117+
existing_claim = dummy_claim # Placeholder: Replace this with actual database retrieval logic.
118+
logger.info(f"Retrieved existing claim: {existing_claim}")
119+
120+
# Since the adjuster's region is already validated in the calling method,
121+
# we can proceed to update the claim data. Assuming we're updating to some new values:
122+
updated_claim = {
123+
"id": existing_claim['id'],
124+
"claimAmount": existing_claim['claimAmount'] + 500.0, # Example update
125+
"claimDescription": "Further updated dummy claim",
126+
"claimStatus": "approved",
127+
"region": existing_claim['region']
128+
}
61129

62-
def lambda_handler(event, context):
63-
logger.info(f'event: {event}')
64-
logger.info(f'context: {context}')
130+
# TODO: Implement logic to save the updated claim to the database.
131+
logger.info(f"Updated claim: {updated_claim}")
132+
return updated_claim # Return the updated claim to confirm the changes.
65133

66-
# sessionAttributes contain the authorization_header which is later retrieved to validate the request. This is the JWT token.
67-
sessionAttributes = event.get("sessionAttributes")
68-
69-
# print("sessionAttributes:", sessionAttributes)
70-
response_code = 200
71-
action_group = event['actionGroup']
72-
api_path = event['apiPath']
73-
http_method = event['httpMethod'].upper()
74-
action_id = getActionID(api_path, http_method)
75-
76-
auth, reason = verifyAccess(sessionAttributes, action_id)
77-
print("auth",auth)
78-
print("reason",reason)
79-
80-
81-
if auth == "ALLOW":
82-
if api_path == '/listClaims' and http_method == 'GET':
83-
result = list_claims(event)
84-
elif api_path == '/getClaim/{claimId}' and http_method == 'GET':
85-
result = get_claim(event)
86-
elif api_path == '/updateClaim/{claimId}' and http_method == 'PUT':
87-
result = update_claim(event)
134+
def lambda_handler(event, context):
135+
logger.info(f"Lambda handler invoked with event: {event}")
136+
try:
137+
# sessionAttributes contain the authorization_header which is later retrieved to validate the request.
138+
# This is the JWT token.
139+
sessionAttributes = event.get("sessionAttributes")
140+
logger.info(f"Session attributes retrieved: {sessionAttributes}")
141+
142+
action_group = event['actionGroup']
143+
api_path = event['apiPath']
144+
http_method = event['httpMethod'].upper()
145+
action_id = getActionID(api_path, http_method)
146+
logger.info(f"Processing request - Path: {api_path}, Method: {http_method}, Action ID: {action_id}")
147+
148+
user_info, auth, reason = verifyAccess(sessionAttributes, action_id)
149+
logger.info(f"Access verification result - Auth: {auth}, Reason: {reason}")
150+
151+
if auth == "ALLOW":
152+
response_code = 200
153+
if api_path == '/listClaims' and http_method == 'GET':
154+
claims = list_claims(event, user_info)
155+
# Return the raw claims data without any text formatting
156+
result = claims
157+
elif api_path == '/getClaim/{claimId}' and http_method == 'GET':
158+
result = get_claim(event, user_info)
159+
elif api_path == '/updateClaim/{claimId}' and http_method == 'PUT':
160+
result = update_claim(event, user_info)
161+
else:
162+
response_code = 404
163+
result = {"error": f"Unrecognized api path: {action_group}::{api_path}"}
164+
logger.error(f"Unrecognized API path: {api_path}")
88165
else:
89-
response_code = 404
90-
result = f"Unrecognized api path: {action_group}::{api_path}"
166+
response_code = 401
167+
result = {"error": reason}
168+
logger.warning(f"Access denied: {reason}")
91169

92170
response_body = {
93171
'application/json': {
94-
'body': result
172+
'body': result # Don't use json.dumps here as it's not needed
95173
}
96174
}
97-
else: # auth == "DENY"
98-
response_code = 401
99-
response_body = {
100-
'application/json': {
101-
'body': reason
175+
176+
api_response = {
177+
'messageVersion': '1.0',
178+
'response': {
179+
'actionGroup': action_group,
180+
'apiPath': api_path,
181+
'httpMethod': http_method,
182+
'httpStatusCode': response_code,
183+
'responseBody': response_body
102184
}
103185
}
104-
105-
action_response = {
106-
'actionGroup': event['actionGroup'],
107-
'apiPath': event['apiPath'],
108-
'httpMethod': event['httpMethod'],
109-
'httpStatusCode': response_code,
110-
'responseBody': response_body
111-
}
112-
113-
api_response = {'messageVersion': '1.0', 'response': action_response}
114-
return api_response
186+
logger.info(f"Returning response with status code: {response_code}")
187+
return api_response
188+
except Exception as e:
189+
logger.error(f"Error in lambda_handler: {str(e)}", exc_info=True)
190+
raise
115191

116192

117193
def verifyAccess(sessionAttributes, action_id):
@@ -142,7 +218,7 @@ def verifyAccess(sessionAttributes, action_id):
142218
else:
143219
reason = "Unauthorized request"
144220

145-
return auth, reason
221+
return user_info, auth, reason
146222

147223

148224
def verifyJWT_getUserInfo(token):

agents-and-function-calling/bedrock-agents/use-case-examples/fine-grained-access-permissions-agent/003_bedrock-agent/template.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ Resources:
201201
MemorySize: 128
202202
Timeout: 3
203203
Handler: agent.lambda_handler
204-
Runtime: python3.9
204+
Runtime: python3.12
205205
Architectures:
206206
- x86_64
207207
EphemeralStorage:
@@ -221,7 +221,7 @@ Resources:
221221
Action:
222222
- verifiedpermissions:IsAuthorized
223223
- verifiedpermissions:IsAuthorizedWithToken
224-
Resource: !Sub 'arn:aws:verifiedpermissions:${AWS::Region}:${AWS::AccountId}:policy-store/${PolicyStoreId}'
224+
Resource: !Sub 'arn:aws:verifiedpermissions::${AWS::AccountId}:policy-store/${PolicyStoreId}'
225225
- Effect: Allow
226226
Action:
227227
- verifiedpermissions:GetPolicyStore

agents-and-function-calling/bedrock-agents/use-case-examples/fine-grained-access-permissions-agent/004_apigateway/template.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ Resources:
148148
MemorySize: 128
149149
Timeout: 180
150150
Handler: invoke_lambda.lambda_handler
151-
Runtime: python3.9
151+
Runtime: python3.12
152152
Architectures:
153153
- x86_64
154154
EphemeralStorage:
@@ -163,7 +163,7 @@ Resources:
163163
- Effect: Allow
164164
Action:
165165
- bedrock:InvokeAgent
166-
Resource: !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${BedrockAgentId}/alias/${BedrockAgentAliasId}'
166+
Resource: !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/${BedrockAgentId}/${BedrockAgentAliasId}'
167167
SnapStart:
168168
ApplyOn: None
169169
Events:

agents-and-function-calling/bedrock-agents/use-case-examples/fine-grained-access-permissions-agent/005_Frontend/src/App.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,16 @@ const parseJwtToken = (tokenType) => {
8181
<button onClick={() => parseJwtToken('accessToken')}>Parse Access Token</button>
8282
<pre>{parsedAccessToken}</pre>
8383

84-
<input
84+
Chat Box: <input
8585
type="text"
8686
value={inputPrompt}
8787
onChange={handleInputChange}
8888
placeholder="Enter claim action"
8989
/>
90-
<button onClick={() => callAPIGW(accessToken, idToken, inputPrompt, setIsLoading, sessionId)}>Call API Gateway</button>
90+
<br/><br/><button onClick={() => callAPIGW(accessToken, idToken, inputPrompt, setIsLoading, sessionId)}>Call API Gateway</button>
9191

9292
<button onClick={generateSessionId}>Reset Session</button>
93+
<br/>
9394
{isLoading && <ClipLoader color="#36d7b7" />} {/* Render the spinner when isLoading is true */}
9495
<div id="apiresponse"></div>
9596
</>

0 commit comments

Comments
 (0)