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

LGA-3535 - Add traversal protection #186

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0456a54
Add means test answers to the reviews page
said-moj Feb 3, 2025
ca77394
Add markdown for multiple items
said-moj Feb 4, 2025
9fd71ff
Add tests for BaseMeansTestForm.summary
said-moj Feb 5, 2025
68f3cf7
Add functional tests for means review page
said-moj Feb 5, 2025
ba4f6b3
Add tests for check your answers views
said-moj Feb 5, 2025
f433fc8
Mock session.get_eligibility()
said-moj Feb 6, 2025
6106afb
Add ScopeAnswers model
said-moj Feb 18, 2025
b299cd8
Add means test answers to the reviews page
said-moj Feb 3, 2025
de726cc
Add tests for BaseMeansTestForm.summary
said-moj Feb 5, 2025
e38b50b
Add ScopeAnswers model
said-moj Feb 18, 2025
f535265
Get Scope answers dynamically
said-moj Feb 19, 2025
9f8fbf3
Move scope answers summary to separate function
said-moj Feb 19, 2025
51af010
Add scope answers on check your answers page
said-moj Feb 19, 2025
39be513
Fix change category on check your answers page
said-moj Feb 19, 2025
3f1d208
Fix failing unit tests due to changes to category answers structure
said-moj Feb 19, 2025
b03483d
Fix failing functional tests
said-moj Feb 19, 2025
b703b46
Add unit tests for CheckYourAnswers.get_category_answers_summary
said-moj Feb 20, 2025
f6bf94d
Add unit tests for QuestionPage.get_next_page
said-moj Feb 20, 2025
881a1a2
Fix failing get_category_answers_summary unit test
said-moj Feb 20, 2025
e39a246
Refactor get_category_answers_summary with less branches
said-moj Feb 20, 2025
803f685
Rename ScopeAnswer to CategoryAnswer
said-moj Feb 20, 2025
5ed950b
Add functional tests for check your answers page
said-moj Feb 21, 2025
f334e2d
Fix failing unit tests
said-moj Feb 21, 2025
1a36dd1
Add more functional tests for check your answers page
said-moj Feb 24, 2025
cfa8e08
Update heading for the review your answers page
said-moj Feb 24, 2025
6fd88d9
Update unit tests to reflect new categories landing page path
said-moj Feb 25, 2025
8ec2c69
Add welsh translation for check your answers page
said-moj Feb 25, 2025
b2a4361
Skip conditional fields that have not triggered
said-moj Feb 25, 2025
620d5cc
Add comments to BaseMeansTestForm.summary
said-moj Feb 25, 2025
f322c13
Add unit tests for app.means_test.forms.BaseMeansTestForm.is_unvalida…
said-moj Feb 28, 2025
5f00633
Rename choices filter for better documentation
said-moj Mar 3, 2025
6e79e23
Use MoneyField._value to get formatted answer for MoneyField instances
said-moj Mar 3, 2025
94fe265
Remove superfluous comment about form states
said-moj Mar 3, 2025
c06823b
Add docstring for BaseMeansTestForm.summary
said-moj Mar 3, 2025
c191967
Format multiple onward pages answers into separate lines
said-moj Mar 3, 2025
c659d42
Update item using .value.update instead of __setitem__
said-moj Mar 3, 2025
d9577f2
Add comment explaining how \n are converted to html breaks
said-moj Mar 3, 2025
aad734b
Add docstring for CategoryLandingPage.listing
said-moj Mar 3, 2025
20404f3
Use list for typing instead of []
said-moj Mar 4, 2025
d15528f
Translate check your answers page title
said-moj Mar 4, 2025
dcf1841
Add extra docs for BaseMeansTestForm.get_selected_answer
said-moj Mar 4, 2025
6c47f9c
Only save the category code in session and reconstruct it when its ne…
said-moj Mar 5, 2025
0571a62
Fix failing unit tests
said-moj Mar 5, 2025
6e29c85
Add in_scope Category field and set it for categories that are in_scope
said-moj Mar 6, 2025
8577847
Protect legal aid available page to categorys that are eligible only
said-moj Mar 6, 2025
76f05d3
Add means test form protection
said-moj Mar 7, 2025
6bcb544
Add documentation for MeansTest.ensure_form_protection
said-moj Mar 10, 2025
3088ac2
Enforce onward questions order
said-moj Mar 10, 2025
b3415f9
Fix means_test.views.CheckYourAnswers.post to redirect the correct pa…
said-moj Mar 10, 2025
39fb035
Add unit tests for session.in_scope
said-moj Mar 11, 2025
5b79b09
/review can only be access when a category is in scope
said-moj Mar 11, 2025
1ddd7fa
Update functional tests to do full traversal to get to the means pages
said-moj Mar 11, 2025
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
21 changes: 21 additions & 0 deletions app/categories/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Category:
chs_code: Optional[str] = None
# Internal code
code: Optional[str] = None
in_scope: Optional[bool] = False
children: dict[str, "Category"] | None = field(default_factory=dict)
parent_code: Optional[str] = None
_referrer_text: Optional[LazyString] = None
Expand Down Expand Up @@ -84,20 +85,23 @@ def __str__(self):
"Includes keeping you or your family safe, getting court orders and help if someone is ignoring a court order. Also, if you’re being stalked, threatened or harassed."
),
code="protect_you_and_your_children",
in_scope=True,
),
"leaving_an_abusive_relationship": Category(
title=_("Leaving an abusive relationship"),
description=_(
"Help with divorce, separation, or leaving your partner. Includes legal arrangements for children, money and housing."
),
code="leaving_an_abusive_relationship",
in_scope=True,
),
"problems_with_ex_partner": Category(
title=_("Problems with an ex-partner: children or money"),
description=_(
"Includes arrangements for children and money. If an ex-partner is not following agreements or court orders. If you’re worried about a child, or if a child is taken or kept without your permission."
),
code="problems_with_ex_partner",
in_scope=True,
),
"problems_with_neighbours": Category(
title=_("Problems with neighbours, landlords or other people"),
Expand All @@ -119,11 +123,13 @@ def __str__(self):
"Help with forced marriage and Forced Marriage Protection Orders."
),
code="forced_marriage",
in_scope=True,
),
"fgm": Category(
title=_("Female genital mutilation (FGM)"),
description=_("If you or someone else is at risk of FGM."),
code="fgm",
in_scope=True,
),
},
)
Expand Down Expand Up @@ -164,6 +170,7 @@ def __str__(self):
"Help to cover the costs of family mediation (solve problems about money and children before you go to court)."
),
code="family_mediation",
in_scope=True,
),
"child_abducted": Category(
title=_("Child taken without your consent"),
Expand All @@ -183,6 +190,7 @@ def __str__(self):
"Advice about legal action against a school. Includes if a child is out of school, exclusions, transport to school, judicial reviews."
),
code="education",
in_scope=True,
),
"forced_marriage": Category(
title=_("Forced marriage"),
Expand All @@ -209,44 +217,51 @@ def __str__(self):
"Help if you’re homeless, or might be homeless in the next 2 months. This could be because of rent arrears, debt, the end of a relationship, or because you have nowhere to live."
),
code="homelessness",
in_scope=True,
),
"eviction": Category(
title=_("Eviction, told to leave your home"),
description=_(
"Landlord has told you to leave or is trying to force you to leave. Includes if you’ve got a Section 21 or a possession order."
),
code="eviction",
in_scope=True,
),
"forced_to_sell": Category(
title=_("Forced to sell or losing the home you own"),
description=_(
"Repossession by your mortgage company; bankruptcy or other debt that means you will lose the home you own."
),
code="forced_to_sell",
in_scope=True,
),
"repairs": Category(
title=_("Repairs, health and safety"),
description=_(
"If your house is not safe to live in, or needs repairs, and this is causing health or safety problems."
),
code="repairs",
in_scope=True,
),
"council_housing": Category(
title=_("Problems with council housing"),
description=_(
"Help to challenge the council’s decision about giving you housing. It includes if the council has offered a house that is not right for you, or that needs repairs or adaptations."
),
code="council_housing",
in_scope=True,
),
"threatened": Category(
title=_("Being threatened or harassed where you live"),
description=_("By a landlord, neighbour or someone else."),
code="threatened",
in_scope=True,
),
"asylum_seeker": Category(
title=_("If you’re an asylum-seeker"),
description=_("Applying for housing, losing your housing or homelessness."),
code="asylum_seeker",
in_scope=True,
),
"discrimination": Category(
title=_("Discrimination"),
Expand All @@ -261,6 +276,7 @@ def __str__(self):
"Accused of anti-social behaviour by the landlord, council or housing association."
),
code="antisocial_behaviour",
in_scope=True,
),
},
)
Expand All @@ -273,6 +289,7 @@ def __str__(self):
article_category_name="Discrimination",
chs_code="discrimination",
code="discrimination",
in_scope=True,
)

EDUCATION = Category(
Expand All @@ -295,20 +312,23 @@ def __str__(self):
"Applying for or going to a SEND tribunal, appealing a decision by a tribunal."
),
code="tribunals",
in_scope=True,
),
"discrimination": Category(
title=_("Child treated unfairly at school, discrimination"),
description=_(
"If a child is treated unfairly at school because of their disability. Or if you were treated badly for complaining about this."
),
code="discrimination",
in_scope=True,
),
"schools": Category(
title=_("Other problems with schools"),
description=_(
"Advice about legal action against a school. Includes if a child is out of school, exclusions, transport to school, judicial reviews."
),
code="schools",
in_scope=True,
),
"care": Category(
title=_("Care needs for disability (social care)"),
Expand Down Expand Up @@ -422,6 +442,7 @@ def __str__(self):
"Help to apply for housing, problems with housing or if you are homeless."
),
code="housing",
in_scope=True,
),
"domestic_abuse": Category(
title=_("Stay in the UK if you experienced domestic abuse"),
Expand Down
2 changes: 2 additions & 0 deletions app/categories/discrimination/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class DiscriminationWhereForm(DiscriminationQuestionForm):


class DiscriminationWhyForm(DiscriminationQuestionForm):
depends_on = DiscriminationWhereForm
title = _("Why were you discriminated against?")

next_step_mapping = {
Expand Down Expand Up @@ -102,6 +103,7 @@ class DiscriminationWhyForm(DiscriminationQuestionForm):


class DiscriminationAreYouUnder18Form(AreYouUnder18Form):
depends_on = DiscriminationWhyForm
category = DISCRIMINATION

next_step_mapping = {
Expand Down
15 changes: 15 additions & 0 deletions app/categories/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from flask import session, redirect, url_for


class InScopeMixin:
def dispatch_request(self):
response = self.ensure_in_scope()
if not response:
response = super().dispatch_request()

return response

def ensure_in_scope(self):
if not session.in_scope:
return redirect(url_for("main.session_expired"))
return None
10 changes: 9 additions & 1 deletion app/categories/results/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
CannotFindYourProblemPage,
NextStepsPage,
)
from app.categories.mixins import InScopeMixin


class InScopeResultPage(InScopeMixin, ResultPage):
pass


bp.add_url_rule(
"/legal-aid-available",
view_func=ResultPage.as_view("in_scope", template="categories/in-scope.html"),
view_func=InScopeResultPage.as_view(
"in_scope", template="categories/in-scope.html"
),
)
bp.add_url_rule(
"/legal-aid-available-hlpas",
Expand Down
2 changes: 1 addition & 1 deletion app/categories/send/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@


class SendChildInCareQuestionForm(ChildInCareQuestionForm):
category = EDUCATION
category = EDUCATION.sub.tribunals
19 changes: 19 additions & 0 deletions app/categories/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask.sansio.blueprints import Blueprint
from flask.views import View
from flask import render_template, redirect, url_for, session, request
from flask_babel import LazyString
from app.categories.forms import QuestionForm
from app.categories.constants import Category
from app.categories.models import CategoryAnswer, QuestionType
Expand Down Expand Up @@ -214,6 +215,19 @@ def update_session(self, form: QuestionForm) -> None:
)
super().update_session(category_answer)

def ensure_form_dependency(self, form):
"""Ensure dependant forms have been completed"""
depends_on = getattr(form, "depends_on", None)
if depends_on and issubclass(depends_on, QuestionForm):
title = depends_on.title
if isinstance(title, LazyString):
title = title._args[0]
answer = session.get_category_question_answer(title)
if answer is None:
return redirect(url_for("main.session_expired"))

return None

def process_request(self):
"""Handle requests for the question page, including form submissions.

Expand All @@ -225,6 +239,11 @@ def process_request(self):
Either a redirect to the next page or the rendered template
"""
form = self.form_class(request.args)
# Make sure previous forms have been completed
form_protection_redirect = self.ensure_form_dependency(form)
if form_protection_redirect:
return form_protection_redirect

session.category = form.category

if form.submit.data and form.validate():
Expand Down
31 changes: 23 additions & 8 deletions app/contact/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
from app.contact import bp
from app.contact.address_finder.widgets import FormattedAddressLookup
from app.contact.views import ContactUs, ReasonForContacting
from flask import jsonify
from app.means_test.api import EligibilityState, is_eligible
from flask import jsonify, session, redirect, url_for
import logging
from app.contact.views import ConfirmationPage


logger = logging.getLogger(__name__)


class EligibleContactUsPage(ContactUs):
def dispatch_request(self):
if not session.ec_reference:
return redirect(url_for("main.session_expired"))

state = is_eligible(session.ec_reference)
if state != EligibilityState.YES:
return redirect(url_for("main.session_expired"))

return super().dispatch_request()


bp.add_url_rule(
"/eligible",
view_func=EligibleContactUsPage.as_view(
"eligible", template="contact/eligible.html", attach_eligiblity_data=True
),
)


bp.add_url_rule(
"/reasons-for-contacting",
view_func=ReasonForContacting.as_view("reasons_for_contacting"),
Expand All @@ -29,11 +51,4 @@ def geocode(postcode):
view_func=ContactUs.as_view("contact_us", attach_eligiblity_data=False),
)

bp.add_url_rule(
"/eligible",
view_func=ContactUs.as_view(
"eligible", template="contact/eligible.html", attach_eligiblity_data=True
),
)

bp.add_url_rule("/confirmation", view_func=ConfirmationPage.as_view("confirmation"))
18 changes: 13 additions & 5 deletions app/means_test/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from app.api import cla_backend
from flask import session
from app.means_test.forms.income import IncomeForm
Expand All @@ -7,10 +8,16 @@
from app.means_test.money_interval import MoneyInterval


class EligibilityState(str, Enum):
YES: str = "yes"
NO: str = "no"
UNKNOWN: str = "unknown"


def update_means_test(payload):
means_test_endpoint = "checker/api/v1/eligibility_check/"

ec_reference = session.get("ec_reference")
ec_reference = session.ec_reference

if ec_reference:
response = cla_backend.patch(
Expand All @@ -19,14 +26,15 @@ def update_means_test(payload):
return response
else:
response = cla_backend.post(means_test_endpoint, json=payload)
session["ec_reference"] = response["reference"]
session.ec_reference = response["reference"]
return response


def is_eligible(reference):
def is_eligible(reference) -> EligibilityState:
means_test_endpoint = "checker/api/v1/eligibility_check/"
response = cla_backend.post(f"{means_test_endpoint}{reference}/is_eligible/")
return response["is_eligible"]
response = cla_backend.post(f"{means_test_endpoint}{reference}/is_eligible/", {})
state = response["is_eligible"]
return getattr(EligibilityState, state.upper(), EligibilityState.UNKNOWN)


def get_means_test_payload(eligibility_data) -> dict:
Expand Down
Loading
Loading