add audiolog
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
from aws_lambda_powertools.event_handler.api_gateway import (
|
||||
APIGatewayHttpResolver,
|
||||
Response,
|
||||
@@ -6,10 +8,13 @@ from aws_lambda_powertools.event_handler.middlewares import (
|
||||
BaseMiddlewareHandler,
|
||||
NextMiddleware,
|
||||
)
|
||||
from aws_lambda_powertools.shared.json_encoder import Encoder
|
||||
from layercake.dateutils import now, ttl
|
||||
from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair
|
||||
from pydantic import UUID4, BaseModel, Field
|
||||
|
||||
LOG_RETENTION_DAYS = 365 * 2 # 2 years
|
||||
|
||||
|
||||
class AuthorizerMiddleware(BaseMiddlewareHandler):
|
||||
def handler(
|
||||
@@ -37,32 +42,52 @@ class AuthenticatedUser(BaseModel):
|
||||
|
||||
|
||||
class AuditLogMiddleware(BaseMiddlewareHandler):
|
||||
def __init__(self, action: str, /, collect: DynamoDBCollection) -> None:
|
||||
"""This middleware logs audit details for successful requests, storing user,
|
||||
action, and IP info with a specified retention period.."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action: str,
|
||||
/,
|
||||
collect: DynamoDBCollection,
|
||||
retention_days: int | None = LOG_RETENTION_DAYS,
|
||||
) -> None:
|
||||
self.action = action
|
||||
self.collect = collect
|
||||
self.retention_days = retention_days
|
||||
|
||||
def handler(
|
||||
self,
|
||||
app: APIGatewayHttpResolver,
|
||||
next_middleware: NextMiddleware,
|
||||
) -> Response:
|
||||
collect = self.collect
|
||||
response = next_middleware(app)
|
||||
auth_user = app.context.get('authenticated_user')
|
||||
user = app.context.get('authenticated_user')
|
||||
request_ctx = app.current_event.request_context
|
||||
ip_addr = request_ctx.http.source_ip
|
||||
|
||||
# Successful request
|
||||
if 200 <= response.status_code < 300 and auth_user:
|
||||
if 200 <= response.status_code < 300 and user:
|
||||
now_ = now()
|
||||
data = (
|
||||
json.dumps(response.body, cls=Encoder) if response.is_json() else None
|
||||
)
|
||||
retention_days = (
|
||||
ttl(start_dt=now_, days=self.retention_days)
|
||||
if self.retention_days
|
||||
else None
|
||||
)
|
||||
|
||||
self.collect.put_item(
|
||||
collect.put_item(
|
||||
key=KeyPair(
|
||||
pk=ComposeKey(auth_user.id, prefix='logs'),
|
||||
pk=ComposeKey(user.id, prefix='logs'),
|
||||
sk=now_.isoformat(),
|
||||
),
|
||||
action=self.action,
|
||||
data=data,
|
||||
ip=ip_addr,
|
||||
ttl=ttl(start_dt=now_, days=365 * 2),
|
||||
ttl=retention_days,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -5,7 +5,6 @@ from aws_lambda_powertools.event_handler import Response, content_types
|
||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||
from elasticsearch import Elasticsearch
|
||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||
from pydantic import BaseModel
|
||||
|
||||
import elastic
|
||||
from boto3clients import dynamodb_client
|
||||
@@ -40,25 +39,21 @@ def get_courses():
|
||||
)
|
||||
|
||||
|
||||
class CoursePayload(BaseModel):
|
||||
course: Course
|
||||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
compress=True,
|
||||
tags=['Course'],
|
||||
middlewares=[AuditLogMiddleware('COURSE_ADD', collect)],
|
||||
)
|
||||
def post_course(payload: CoursePayload):
|
||||
def post_course(payload: Course):
|
||||
create_course(
|
||||
course=payload.course,
|
||||
course=payload,
|
||||
org=Org(id='*', name='default'),
|
||||
persistence_layer=course_layer,
|
||||
)
|
||||
|
||||
return Response(
|
||||
body=payload.course,
|
||||
body={'id': str(payload.id), },
|
||||
content_type=content_types.APPLICATION_JSON,
|
||||
status_code=HTTPStatus.CREATED,
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ LIMIT = 25
|
||||
|
||||
@router.get('/', include_in_schema=False)
|
||||
def me():
|
||||
user: AuthenticatedUser = router.context['user']
|
||||
user: AuthenticatedUser = router.context['authenticated_user']
|
||||
acls = collect.get_items(
|
||||
KeyPair(user.id, PrefixKey('acls')),
|
||||
limit=LIMIT,
|
||||
@@ -39,7 +39,7 @@ def me():
|
||||
|
||||
@router.get('/konviva', include_in_schema=False)
|
||||
def konviva_():
|
||||
user: AuthenticatedUser = router.context['user']
|
||||
user: AuthenticatedUser = router.context['authenticated_user']
|
||||
token = konviva.token(user.email)
|
||||
|
||||
return {'redirect_uri': konviva.redirect_uri(token)}
|
||||
|
||||
@@ -8,7 +8,6 @@ COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
|
||||
KONVIVA_API_URL: str = os.getenv('KONVIVA_API_URL') # type: ignore
|
||||
KONVIVA_SECRET_KEY: str = os.getenv('KONVIVA_SECRET_KEY') # type: ignore
|
||||
|
||||
|
||||
match os.getenv('AWS_SAM_LOCAL'), os.getenv('ELASTIC_HOSTS'):
|
||||
case str() as AWS_SAM_LOCAL, _ if (
|
||||
AWS_SAM_LOCAL
|
||||
|
||||
@@ -136,7 +136,10 @@ def dynamodb_seeds(dynamodb_client):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_app():
|
||||
def mock_app(monkeypatch):
|
||||
for table_name in ['USER_TABLE', 'COURSE_TABLE']:
|
||||
monkeypatch.setenv(table_name, PYTEST_TABLE_NAME)
|
||||
|
||||
import app
|
||||
|
||||
return app
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
import json
|
||||
from http import HTTPMethod, HTTPStatus
|
||||
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||
|
||||
import app
|
||||
from layercake.dynamodb import ComposeKey, DynamoDBCollection, PartitionKey
|
||||
|
||||
from ..conftest import HttpApiProxy, LambdaContext
|
||||
|
||||
|
||||
def test_post_course(
|
||||
mock_app,
|
||||
monkeypatch,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
dynamodb_persistence_layer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
app.courses.course_layer = dynamodb_persistence_layer
|
||||
app.courses.user_layer = dynamodb_persistence_layer
|
||||
|
||||
r = app.lambda_handler(
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/courses',
|
||||
method=HTTPMethod.POST,
|
||||
headers={'X-Tenant': '*'},
|
||||
body={
|
||||
'course': {
|
||||
'name': 'pytest',
|
||||
'access_period': 365,
|
||||
'cert': {
|
||||
'exp_interval': 730, # 2 years
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
lambda_context,
|
||||
@@ -40,3 +32,9 @@ def test_post_course(
|
||||
|
||||
assert 'id' in json.loads(r['body'])
|
||||
assert r['statusCode'] == HTTPStatus.CREATED
|
||||
|
||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
logs = collect.get_items(
|
||||
PartitionKey(ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='logs'))
|
||||
)
|
||||
print(logs)
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import json
|
||||
from http import HTTPMethod, HTTPStatus
|
||||
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||
|
||||
from ..conftest import HttpApiProxy, LambdaContext
|
||||
|
||||
|
||||
def test_lookup(
|
||||
mock_app,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.courses.course_layer = dynamodb_persistence_layer
|
||||
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/lookup/sergio@somosbeta.com.br',
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import json
|
||||
from http import HTTPMethod, HTTPStatus
|
||||
|
||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||
|
||||
from ..conftest import HttpApiProxy, LambdaContext
|
||||
|
||||
|
||||
def test_me(
|
||||
mock_app,
|
||||
dynamodb_seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.me.collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
|
||||
# This data was added from seeds
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import json
|
||||
from http import HTTPMethod, HTTPStatus
|
||||
|
||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||
|
||||
from ..conftest import HttpApiProxy, LambdaContext
|
||||
|
||||
|
||||
def test_get_emails(
|
||||
mock_app,
|
||||
dynamodb_seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.users.collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/users/5OxmMjL-ujoR5IMGegQz/emails',
|
||||
@@ -43,12 +38,9 @@ def test_get_emails(
|
||||
def test_get_orgs(
|
||||
mock_app,
|
||||
dynamodb_seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.users.collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/users/5OxmMjL-ujoR5IMGegQz/orgs',
|
||||
@@ -63,12 +55,9 @@ def test_get_orgs(
|
||||
def test_get_logs(
|
||||
mock_app,
|
||||
dynamodb_seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.users.collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
|
||||
r = mock_app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/users/5OxmMjL-ujoR5IMGegQz/logs',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||
|
||||
import auth as app
|
||||
from auth import _parse_bearer_token
|
||||
|
||||
@@ -33,11 +31,8 @@ def test_bearer_jwt(lambda_context: LambdaContext):
|
||||
|
||||
def test_bearer_apikey(
|
||||
dynamodb_seeds,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
app.collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||
|
||||
event = {
|
||||
'headers': {
|
||||
'authorization': 'Bearer sk-MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw',
|
||||
|
||||
Reference in New Issue
Block a user