Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AYR-1348 - Page view logs #674

Merged
merged 8 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
20 changes: 20 additions & 0 deletions app/main/middlewares/log_page_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
from functools import wraps

from flask import current_app, request, session


def log_page_view(route_function):
@wraps(route_function)
def wrapper(*args, **kwargs):
user_id = session.get("user_id", "anonymous")
log_data = {
"event": "page_view",
anthonyhashemi marked this conversation as resolved.
Show resolved Hide resolved
"user_id": user_id,
"route": request.path,
"method": request.method,
}
current_app.audit_logger.info(json.dumps(log_data))
return route_function(*args, **kwargs)

return wrapper
14 changes: 14 additions & 0 deletions app/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from app.main.flask_config_helpers import (
get_keycloak_instance_from_flask_config,
)
from app.main.middlewares.log_page_view import log_page_view
from app.main.util.date_filters_validator import validate_date_filters
from app.main.util.filter_sort_builder import (
build_browse_consignment_filters,
Expand Down Expand Up @@ -76,6 +77,7 @@ def index():

@bp.route("/sign-out", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def sign_out():
keycloak_openid = get_keycloak_instance_from_flask_config()
keycloak_openid.logout(session["refresh_token"])
Expand All @@ -85,6 +87,7 @@ def sign_out():


@bp.route("/sign-in", methods=["GET"])
@log_page_view
def sign_in():
keycloak_openid = get_keycloak_instance_from_flask_config()
auth_url = keycloak_openid.auth_url(
Expand All @@ -96,6 +99,7 @@ def sign_in():


@bp.route("/callback", methods=["GET"])
@log_page_view
def callback():
keycloak_openid = get_keycloak_instance_from_flask_config()
code = request.args.get("code")
Expand Down Expand Up @@ -126,6 +130,7 @@ def accessibility():

@bp.route("/browse", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def browse():
form = SearchForm()
page = int(request.args.get("page", 1))
Expand Down Expand Up @@ -205,6 +210,7 @@ def browse():

@bp.route("/browse/transferring_body/<uuid:_id>", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def browse_transferring_body(_id: uuid.UUID):
"""
Render the browse transferring body view page.
Expand Down Expand Up @@ -288,6 +294,7 @@ def browse_transferring_body(_id: uuid.UUID):

@bp.route("/browse/series/<uuid:_id>", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def browse_series(_id: uuid.UUID):
"""
Render the browse series view page.
Expand Down Expand Up @@ -376,6 +383,7 @@ def browse_series(_id: uuid.UUID):

@bp.route("/browse/consignment/<uuid:_id>", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def browse_consignment(_id: uuid.UUID):
"""
Render the browse consignment view page.
Expand Down Expand Up @@ -464,6 +472,7 @@ def browse_consignment(_id: uuid.UUID):

@bp.route("/search", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def search():
form_data = request.form.to_dict()
args_data = request.args.to_dict()
Expand Down Expand Up @@ -495,6 +504,7 @@ def search():

@bp.route("/search_results_summary", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def search_results_summary():
ayr_user = AYRUser(session.get("user_groups"))
if ayr_user.is_standard_user:
Expand Down Expand Up @@ -550,6 +560,7 @@ def search_results_summary():

@bp.route("/search/transferring_body/<uuid:_id>", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def search_transferring_body(_id: uuid.UUID):
body = db.session.get(Body, _id)
validate_body_user_groups_or_404(body.Name)
Expand Down Expand Up @@ -646,6 +657,7 @@ def search_transferring_body(_id: uuid.UUID):

@bp.route("/record/<uuid:record_id>", methods=["GET"])
@access_token_sign_in_required
@log_page_view
def record(record_id: uuid.UUID):
"""
Render the record details page.
Expand Down Expand Up @@ -705,6 +717,7 @@ def record(record_id: uuid.UUID):

@bp.route("/download/<uuid:record_id>")
@access_token_sign_in_required
@log_page_view
def download_record(record_id: uuid.UUID):
file = db.session.get(File, record_id)
render = request.args.get("render", False)
Expand Down Expand Up @@ -798,6 +811,7 @@ def http_exception(error):

@bp.route("/record/<uuid:record_id>/manifest")
@access_token_sign_in_required
@log_page_view
def generate_manifest(record_id: uuid.UUID):
file = db.session.get(File, record_id)

Expand Down
84 changes: 84 additions & 0 deletions app/tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import json
from unittest.mock import MagicMock

import pytest
from flask import current_app
from flask.testing import FlaskClient

from app.main.middlewares.log_page_view import log_page_view


@pytest.mark.parametrize(
"route_path, method, user_id_in_session, route_function, expected_response, expected_log_data",
[
(
"/test_route",
"GET",
"test_user",
lambda: "Test Response",
b"Test Response",
{
"event": "page_view",
"user_id": "test_user",
"route": "/test_route",
"method": "GET",
},
),
(
"/anonymous_route",
"GET",
None,
lambda: "Anonymous Response",
b"Anonymous Response",
{
"event": "page_view",
"user_id": "anonymous",
"route": "/anonymous_route",
"method": "GET",
},
),
(
"/post_route",
"POST",
"test_user",
lambda: "Post Response",
b"Post Response",
{
"event": "page_view",
"user_id": "test_user",
"route": "/post_route",
"method": "POST",
},
),
],
)
def test_log_page_view(
app,
client: FlaskClient,
route_path,
method,
user_id_in_session,
route_function,
expected_response,
expected_log_data,
):
anthonyhashemi marked this conversation as resolved.
Show resolved Hide resolved
mock_logger = MagicMock()
anthonyhashemi marked this conversation as resolved.
Show resolved Hide resolved

@app.route(route_path, methods=[method])
@log_page_view
def dynamic_route():
return route_function()

with app.app_context():
current_app.audit_logger = mock_logger

if user_id_in_session is not None:
ltrasca marked this conversation as resolved.
Show resolved Hide resolved
with client.session_transaction() as session:
session["user_id"] = user_id_in_session

response = client.open(route_path, method=method)

assert response.status_code == 200
assert response.data == expected_response

mock_logger.info.assert_called_once_with(json.dumps(expected_log_data))