geek-cafe-saas-sdk 0.6.0__py3-none-any.whl
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.
Potentially problematic release.
This version of geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication and authorization decorators for Lambda handlers.
|
|
3
|
+
|
|
4
|
+
These decorators integrate with the authorization middleware to provide
|
|
5
|
+
fine-grained access control for hierarchical multi-tenant routes.
|
|
6
|
+
|
|
7
|
+
Design:
|
|
8
|
+
- Uses existing authorization middleware (tested, proven)
|
|
9
|
+
- Composable with other decorators
|
|
10
|
+
- Explicit and readable
|
|
11
|
+
- Follows industry patterns (Flask, FastAPI, Django)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import functools
|
|
16
|
+
from typing import Callable, Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
from aws_lambda_powertools import Logger
|
|
19
|
+
|
|
20
|
+
from geek_cafe_saas_sdk.middleware.authorization import (
|
|
21
|
+
Operation,
|
|
22
|
+
Permission,
|
|
23
|
+
AuthorizationMiddleware,
|
|
24
|
+
extract_auth_context,
|
|
25
|
+
extract_resource_context,
|
|
26
|
+
AuthorizationResult
|
|
27
|
+
)
|
|
28
|
+
from geek_cafe_saas_sdk.utilities.response import error_response
|
|
29
|
+
|
|
30
|
+
logger = Logger()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def require_authorization(
|
|
34
|
+
operation: Optional[Operation] = None,
|
|
35
|
+
resource_type: Optional[str] = None,
|
|
36
|
+
extract_resource_fn: Optional[Callable] = None
|
|
37
|
+
) -> Callable:
|
|
38
|
+
"""
|
|
39
|
+
Require authorization for handler based on hierarchical route.
|
|
40
|
+
|
|
41
|
+
Checks:
|
|
42
|
+
- Can actor access target tenant?
|
|
43
|
+
- Can actor access target user's resources?
|
|
44
|
+
- Does actor have permission for operation?
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
operation: Operation to authorize (READ, WRITE, DELETE, CREATE).
|
|
48
|
+
If None, inferred from HTTP method.
|
|
49
|
+
resource_type: Type of resource (for logging/audit)
|
|
50
|
+
extract_resource_fn: Custom function to extract resource from event
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
@require_authorization(operation=Operation.READ, resource_type="message")
|
|
54
|
+
def handler(event, context):
|
|
55
|
+
# Authorization already checked
|
|
56
|
+
message_id = event['pathParameters']['message_id']
|
|
57
|
+
return {'statusCode': 200}
|
|
58
|
+
|
|
59
|
+
# Auto-infer operation from HTTP method
|
|
60
|
+
@require_authorization(resource_type="message")
|
|
61
|
+
def handler(event, context):
|
|
62
|
+
# GET -> READ, POST -> CREATE, etc.
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Decorated handler with authorization check
|
|
67
|
+
"""
|
|
68
|
+
def decorator(handler: Callable) -> Callable:
|
|
69
|
+
@functools.wraps(handler)
|
|
70
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
71
|
+
try:
|
|
72
|
+
# Extract actor from JWT
|
|
73
|
+
actor = extract_auth_context(event)
|
|
74
|
+
|
|
75
|
+
# Extract resource from path
|
|
76
|
+
if extract_resource_fn:
|
|
77
|
+
resource = extract_resource_fn(event)
|
|
78
|
+
else:
|
|
79
|
+
resource = extract_resource_context(event, resource_type)
|
|
80
|
+
|
|
81
|
+
# Validate tenant_id is in path
|
|
82
|
+
if not resource.tenant_id:
|
|
83
|
+
logger.warning("Authorization failed: tenant_id not in path parameters")
|
|
84
|
+
return error_response(
|
|
85
|
+
"tenant_id is required in path parameters",
|
|
86
|
+
"AUTHORIZATION_ERROR",
|
|
87
|
+
400
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Determine operation (explicit or inferred)
|
|
91
|
+
op = operation
|
|
92
|
+
if op is None:
|
|
93
|
+
# Infer from HTTP method
|
|
94
|
+
method = event.get('httpMethod', 'GET').upper()
|
|
95
|
+
if method == 'GET':
|
|
96
|
+
op = Operation.READ
|
|
97
|
+
elif method == 'POST':
|
|
98
|
+
op = Operation.CREATE
|
|
99
|
+
elif method in ['PUT', 'PATCH']:
|
|
100
|
+
op = Operation.WRITE
|
|
101
|
+
elif method == 'DELETE':
|
|
102
|
+
op = Operation.DELETE
|
|
103
|
+
else:
|
|
104
|
+
op = Operation.READ # Default
|
|
105
|
+
|
|
106
|
+
# Check authorization
|
|
107
|
+
result: AuthorizationResult = AuthorizationMiddleware.can_perform_operation(
|
|
108
|
+
actor, resource, op
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if not result.allowed:
|
|
112
|
+
logger.warning(
|
|
113
|
+
f"Authorization denied: {result.reason}",
|
|
114
|
+
extra={
|
|
115
|
+
'actor_user_id': actor.user_id,
|
|
116
|
+
'actor_tenant_id': actor.tenant_id,
|
|
117
|
+
'resource_tenant_id': resource.tenant_id,
|
|
118
|
+
'resource_user_id': resource.user_id,
|
|
119
|
+
'resource_id': resource.resource_id,
|
|
120
|
+
'operation': op.value,
|
|
121
|
+
'reason': result.reason
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
return error_response(
|
|
125
|
+
f"You do not have permission to access this resource. Reason: {result.reason}",
|
|
126
|
+
"AUTHORIZATION_DENIED",
|
|
127
|
+
403
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Add authorization context to event
|
|
131
|
+
event['authorization_context'] = {
|
|
132
|
+
'actor': actor,
|
|
133
|
+
'resource': resource,
|
|
134
|
+
'operation': op.value,
|
|
135
|
+
'reason': result.reason,
|
|
136
|
+
'context': result.context
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Log successful authorization for audit
|
|
140
|
+
logger.info(
|
|
141
|
+
f"Authorization granted: {result.reason}",
|
|
142
|
+
extra={
|
|
143
|
+
'actor_user_id': actor.user_id,
|
|
144
|
+
'actor_tenant_id': actor.tenant_id,
|
|
145
|
+
'resource_tenant_id': resource.tenant_id,
|
|
146
|
+
'resource_user_id': resource.user_id,
|
|
147
|
+
'resource_id': resource.resource_id,
|
|
148
|
+
'operation': op.value,
|
|
149
|
+
'reason': result.reason
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Authorization passed - call handler
|
|
154
|
+
return handler(event, context, *args, **kwargs)
|
|
155
|
+
|
|
156
|
+
except KeyError as e:
|
|
157
|
+
logger.error(f"Authorization check failed: Missing JWT claim: {e}")
|
|
158
|
+
return error_response(
|
|
159
|
+
f"Missing required authentication claim: {str(e)}",
|
|
160
|
+
"AUTHENTICATION_ERROR",
|
|
161
|
+
401
|
|
162
|
+
)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.exception(f"Authorization check failed: {e}")
|
|
165
|
+
return error_response(
|
|
166
|
+
"Authorization check failed",
|
|
167
|
+
"AUTHORIZATION_ERROR",
|
|
168
|
+
500
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return wrapper
|
|
172
|
+
return decorator
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def require_admin(handler: Callable) -> Callable:
|
|
176
|
+
"""
|
|
177
|
+
Require admin role (tenant admin or global admin).
|
|
178
|
+
|
|
179
|
+
This is a convenience decorator that checks for admin roles
|
|
180
|
+
without fine-grained resource-level authorization.
|
|
181
|
+
|
|
182
|
+
Usage:
|
|
183
|
+
@require_admin
|
|
184
|
+
def handler(event, context):
|
|
185
|
+
# User is guaranteed to be an admin
|
|
186
|
+
return {'statusCode': 200}
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Decorated handler requiring admin role
|
|
190
|
+
"""
|
|
191
|
+
@functools.wraps(handler)
|
|
192
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
193
|
+
try:
|
|
194
|
+
actor = extract_auth_context(event)
|
|
195
|
+
|
|
196
|
+
# Check for admin permissions
|
|
197
|
+
is_admin = (
|
|
198
|
+
actor.has_permission(Permission.PLATFORM_ADMIN) or
|
|
199
|
+
actor.has_permission(Permission.TENANT_ADMIN) or
|
|
200
|
+
actor.has_role('admin') or
|
|
201
|
+
actor.has_role('tenant_admin')
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not is_admin:
|
|
205
|
+
logger.warning(
|
|
206
|
+
f"Admin access denied for user {actor.user_id}",
|
|
207
|
+
extra={'user_id': actor.user_id, 'tenant_id': actor.tenant_id}
|
|
208
|
+
)
|
|
209
|
+
return error_response(
|
|
210
|
+
"Admin role required",
|
|
211
|
+
"ADMIN_REQUIRED",
|
|
212
|
+
403
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Add auth context to event
|
|
216
|
+
event['authorization_context'] = {
|
|
217
|
+
'actor': actor,
|
|
218
|
+
'reason': 'admin_role'
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
logger.info(
|
|
222
|
+
f"Admin access granted for user {actor.user_id}",
|
|
223
|
+
extra={'user_id': actor.user_id, 'tenant_id': actor.tenant_id}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return handler(event, context, *args, **kwargs)
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.exception(f"Admin check failed: {e}")
|
|
230
|
+
return error_response(
|
|
231
|
+
"Authorization check failed",
|
|
232
|
+
"AUTHORIZATION_ERROR",
|
|
233
|
+
500
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return wrapper
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def require_tenant_admin(handler: Callable) -> Callable:
|
|
240
|
+
"""
|
|
241
|
+
Require tenant admin role (admin for their own tenant).
|
|
242
|
+
|
|
243
|
+
Usage:
|
|
244
|
+
@require_tenant_admin
|
|
245
|
+
def handler(event, context):
|
|
246
|
+
# User is tenant admin for their tenant
|
|
247
|
+
return {'statusCode': 200}
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Decorated handler requiring tenant admin role
|
|
251
|
+
"""
|
|
252
|
+
@functools.wraps(handler)
|
|
253
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
254
|
+
try:
|
|
255
|
+
actor = extract_auth_context(event)
|
|
256
|
+
|
|
257
|
+
# Check for tenant admin permission
|
|
258
|
+
is_tenant_admin = (
|
|
259
|
+
actor.has_permission(Permission.TENANT_ADMIN) or
|
|
260
|
+
actor.has_role('tenant_admin')
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if not is_tenant_admin:
|
|
264
|
+
logger.warning(
|
|
265
|
+
f"Tenant admin access denied for user {actor.user_id}",
|
|
266
|
+
extra={'user_id': actor.user_id, 'tenant_id': actor.tenant_id}
|
|
267
|
+
)
|
|
268
|
+
return error_response(
|
|
269
|
+
"Tenant admin role required",
|
|
270
|
+
"TENANT_ADMIN_REQUIRED",
|
|
271
|
+
403
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Add auth context to event
|
|
275
|
+
event['authorization_context'] = {
|
|
276
|
+
'actor': actor,
|
|
277
|
+
'reason': 'tenant_admin_role'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return handler(event, context, *args, **kwargs)
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.exception(f"Tenant admin check failed: {e}")
|
|
284
|
+
return error_response(
|
|
285
|
+
"Authorization check failed",
|
|
286
|
+
"AUTHORIZATION_ERROR",
|
|
287
|
+
500
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return wrapper
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def require_platform_admin(handler: Callable) -> Callable:
|
|
294
|
+
"""
|
|
295
|
+
Require platform admin role (platform-level admin).
|
|
296
|
+
|
|
297
|
+
Usage:
|
|
298
|
+
@require_platform_admin
|
|
299
|
+
def handler(event, context):
|
|
300
|
+
# User is platform admin
|
|
301
|
+
return {'statusCode': 200}
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Decorated handler requiring global admin role
|
|
305
|
+
"""
|
|
306
|
+
@functools.wraps(handler)
|
|
307
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
308
|
+
try:
|
|
309
|
+
actor = extract_auth_context(event)
|
|
310
|
+
|
|
311
|
+
# Check for platform admin permission
|
|
312
|
+
is_platform_admin = (
|
|
313
|
+
actor.has_permission(Permission.PLATFORM_ADMIN) or
|
|
314
|
+
actor.has_role('platform_admin')
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if not is_platform_admin:
|
|
318
|
+
logger.warning(
|
|
319
|
+
f"Platform admin access denied for user {actor.user_id}",
|
|
320
|
+
extra={'user_id': actor.user_id, 'tenant_id': actor.tenant_id}
|
|
321
|
+
)
|
|
322
|
+
return error_response(
|
|
323
|
+
"Platform admin role required",
|
|
324
|
+
"PLATFORM_ADMIN_REQUIRED",
|
|
325
|
+
403
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Add auth context to event
|
|
329
|
+
event['authorization_context'] = {
|
|
330
|
+
'actor': actor,
|
|
331
|
+
'reason': 'platform_admin_role'
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
logger.info(
|
|
335
|
+
f"Platform admin access granted for user {actor.user_id}",
|
|
336
|
+
extra={'user_id': actor.user_id, 'tenant_id': actor.tenant_id}
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return handler(event, context, *args, **kwargs)
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.exception(f"Platform admin check failed: {e}")
|
|
343
|
+
return error_response(
|
|
344
|
+
"Authorization check failed",
|
|
345
|
+
"AUTHORIZATION_ERROR",
|
|
346
|
+
500
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return wrapper
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def public(handler: Callable) -> Callable:
|
|
353
|
+
"""
|
|
354
|
+
Mark handler as public (no authentication required).
|
|
355
|
+
|
|
356
|
+
This is primarily a marker decorator for documentation purposes.
|
|
357
|
+
The actual public access must be configured at the API Gateway level.
|
|
358
|
+
|
|
359
|
+
Usage:
|
|
360
|
+
@public
|
|
361
|
+
def handler(event, context):
|
|
362
|
+
# Public endpoint - no auth check
|
|
363
|
+
return {'statusCode': 200}
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Decorated handler (no actual modification)
|
|
367
|
+
"""
|
|
368
|
+
@functools.wraps(handler)
|
|
369
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
370
|
+
logger.info("Public endpoint accessed (no authentication required)")
|
|
371
|
+
return handler(event, context, *args, **kwargs)
|
|
372
|
+
|
|
373
|
+
return wrapper
|