Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN pip install uv
ADD ./mavis/reporting /app/mavis/reporting
ADD README.md /app/

RUN uv sync --frozen --all-extras
RUN uv sync --frozen
RUN npm install

FROM builder
Expand Down
12 changes: 7 additions & 5 deletions mavis/reporting/helpers/auth_helper.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from datetime import datetime, timedelta, timezone
import jwt
import urllib
from urllib.parse import quote
from typing import Any

from flask import (
request,
session,
redirect,
current_app,
)
from flask.sessions import SessionMixin

from functools import wraps

Expand All @@ -34,7 +36,7 @@ def decorated_function(*args, **kwargs):

log_user_in(verification_data, session)
return redirect(
url_helper.url_without_param(request.full_path, "code")
str(url_helper.url_without_param(request.full_path, "code"))
)
except KeyError:
pass
Expand All @@ -44,9 +46,9 @@ def decorated_function(*args, **kwargs):
return_url = url_helper.externalise_current_url(current_app, request)
target_url = mavis_helper.mavis_url(
current_app,
"/start?redirect_uri=" + urllib.parse.quote(return_url),
"/start?redirect_uri=" + quote(return_url),
)
return redirect(target_url)
return redirect(str(target_url))

return f(*args, **kwargs)

Expand Down Expand Up @@ -85,7 +87,7 @@ def encode_jwt(payload, current_app=current_app):
return jwt.encode(payload, secret, algorithm="HS512")


def log_user_in(data, session=session):
def log_user_in(data: dict[str, Any], session: SessionMixin):
jwt_data = data["jwt_data"]

session["last_visit"] = datetime.now().astimezone(timezone.utc)
Expand Down
6 changes: 3 additions & 3 deletions mavis/reporting/helpers/mavis_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from http import HTTPStatus
import requests
import urllib.parse
import werkzeug
from werkzeug.exceptions import Unauthorized

from flask import redirect

Expand Down Expand Up @@ -49,7 +49,7 @@ def api_call(current_app, session, path, params={}):
response = get_request(url, headers=headers)
if response.status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
session.clear()
raise (werkzeug.exceptions.Unauthorized)
raise Unauthorized()

return response

Expand All @@ -60,7 +60,7 @@ def login_and_return_after(current_app, return_url):
"/start?redirect_uri=" + urllib.parse.quote_plus(return_url),
)
current_app.logger.warning("REDIRECTING TO %s", target_url)
return redirect(target_url)
return redirect(str(target_url))


def get_request(url, headers={}):
Expand Down
4 changes: 2 additions & 2 deletions mavis/reporting/helpers/url_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from urllib.parse import urlparse, parse_qs, parse_qsl, urlencode, urlunparse, urljoin


def url_without_param(url, param):
def url_without_param(url: str, param: str) -> str:
parsed_url = urlparse(url)
query_params_as_dict = parse_qs(parsed_url.query)
if param in query_params_as_dict:
Expand All @@ -16,7 +16,7 @@ def url_without_param(url, param):
return url


def externalise_current_url(current_app, request):
def externalise_current_url(current_app, request) -> str:
return urljoin(
current_app.config["ROOT_URL"] or request.host_url, request.full_path
)
2 changes: 1 addition & 1 deletion mavis/reporting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def download():


@main.errorhandler(404)
def page_not_found():
def page_not_found(_error):
return render_template("errors/404.html"), 404


Expand Down
9 changes: 7 additions & 2 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ run = "npm install"

[tasks."uv:sync"]
description = "Sync Python dependencies with uv"
run = "uv sync --all-extras"
run = "uv sync --group dev"

[tasks."build:favicons"]
description = "Copy NHS UK frontend favicons"
Expand Down Expand Up @@ -66,6 +66,11 @@ description = "Run code linting with autofix"
depends = ["uv:sync"]
run = "uv run ruff check --fix ."

[tasks.typecheck]
description = "Run type checking with pyright"
depends = ["uv:sync"]
run = "uv run pyright"

[tasks."dev:scss"]
description = "Watch and build SCSS files"
depends = ["npm:install"]
Expand Down Expand Up @@ -121,4 +126,4 @@ run = "mise docker:run"

[tasks.ci]
description = "Run CI checks in parallel"
depends = ["gitleaks", "lint", "test"]
depends = ["gitleaks", "lint", "test", "typecheck"]
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ dependencies = [
"pyjwt>=2.10.1",
]

[project.optional-dependencies]
[dependency-groups]
dev = [
"ruff==0.12.12",
"pytest>=8.4.1",
"coverage>=7.10.6",
"coverage-badge>=1.1.2",
"boto3==1.40.24",
"pyyaml>=6.0.2",
"pyright>=1.1.405",
]

[tool.ruff]
output-format = "github"

[tool.ruff.lint]
select = ["ARG", "PL", "E", "W", "F"]

Expand All @@ -34,3 +38,7 @@ only-include = ["mavis", "scripts"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pyright]
venvPath = "."
venv = ".venv"
11 changes: 6 additions & 5 deletions tests/helpers/test_auth_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from os import urandom

from typing import cast
from datetime import datetime, timedelta, timezone

from flask.sessions import SessionMixin
from mavis.reporting.helpers import auth_helper


Expand Down Expand Up @@ -57,7 +58,7 @@ def test_that_log_user_in_sets_last_visit_to_now(app):
with app.app_context():
configure_app(app)
mock_session = {}
auth_helper.log_user_in(mock_user_info(), mock_session)
auth_helper.log_user_in(mock_user_info(), cast(SessionMixin, mock_session))
assert mock_session["last_visit"] is not None
assert datetime.now().astimezone(timezone.utc) - mock_session[
"last_visit"
Expand All @@ -68,7 +69,7 @@ def test_that_log_user_in_copies_cis2_info_from_the_given_data(app):
mock_session = {}
with app.app_context():
configure_app(app)
auth_helper.log_user_in(mock_user_info(), mock_session)
auth_helper.log_user_in(mock_user_info(), cast(SessionMixin, mock_session))
assert mock_session["cis2_info"] == mock_user_info()["jwt_data"]["cis2_info"]


Expand All @@ -78,7 +79,7 @@ def test_that_log_user_in_copies_user_from_the_given_data(app):

with app.app_context():
configure_app(app)
auth_helper.log_user_in(fake_data, mock_session)
auth_helper.log_user_in(fake_data, cast(SessionMixin, mock_session))
assert mock_session["user"] == fake_data["jwt_data"]["user"]


Expand All @@ -89,7 +90,7 @@ def test_that_log_user_in_sets_minimal_jwt(
fake_data = mock_user_info()
with app.app_context():
configure_app(app)
auth_helper.log_user_in(fake_data, mock_session)
auth_helper.log_user_in(fake_data, cast(SessionMixin, mock_session))
assert mock_session["jwt"] is not None
jwt_payload = auth_helper.decode_jwt(mock_session["jwt"])

Expand Down
6 changes: 3 additions & 3 deletions tests/helpers/test_mavis_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from http import HTTPStatus
import pytest
import werkzeug
from werkzeug.exceptions import Unauthorized

from flask import current_app
from unittest.mock import patch
Expand Down Expand Up @@ -60,7 +60,7 @@ def __init__(self, **kwargs):
self.json_obj = kwargs.get("json_obj", None)

def json(self):
return self.json_obj or json.loads(self.text)
return self.json_obj or json.loads(self.text or "{}")


def test_that_verify_auth_code_is_called_correctly(
Expand Down Expand Up @@ -146,7 +146,7 @@ def test_that_an_unauthorized_api_call_raises_an_exception_and_clears_the_sessio
"mavis.reporting.helpers.mavis_helper.get_request",
return_value=mock_response,
):
with pytest.raises(werkzeug.exceptions.Unauthorized):
with pytest.raises(Unauthorized):
mavis_helper.api_call(
app,
mock_session,
Expand Down
Loading
Loading