diff --git a/backend/api/tests/test_applications_api.py b/backend/api/tests/test_applications_api.py index 74a2397..3f1444c 100644 --- a/backend/api/tests/test_applications_api.py +++ b/backend/api/tests/test_applications_api.py @@ -20,7 +20,7 @@ def _build_create_payload(questionnaire): "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "test-token", } @@ -87,16 +87,16 @@ def test_application_create_requires_privacy_consent( questionnaire, monkeypatch, ): - """Reject creation unless privacy consent is explicitly acknowledged.""" + """Reject creation unless collection notice consent is explicitly acknowledged.""" monkeypatch.setattr(application_serialisers, "verify_turnstile_token", lambda *_args, **_kwargs: True) payload = _build_create_payload(questionnaire) - payload["privacy_consent_agreed"] = False + payload["collection_notice_agreed"] = False api_client.force_authenticate(user=user) response = api_client.post("/api/applications", payload, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST - assert "privacy_consent_agreed" in response.data + assert "collection_notice_agreed" in response.data @pytest.mark.django_db diff --git a/backend/applications/serialisers.py b/backend/applications/serialisers.py index 5265c5d..0246e03 100644 --- a/backend/applications/serialisers.py +++ b/backend/applications/serialisers.py @@ -118,7 +118,7 @@ class ApplicationSerialiser(JsonSchemaSerialiserMixin, serializers.ModelSerializ required=False, read_only=True, ) - privacy_consent_agreed = serializers.BooleanField( + collection_notice_agreed = serializers.BooleanField( required=False, write_only=True, ) @@ -151,7 +151,7 @@ class Meta: "questionnaire_name", "questionnaire_version", "questionnaire_sort_order", - "privacy_consent_agreed", + "collection_notice_agreed", "turnstile_token", "status", "created_at", @@ -194,9 +194,9 @@ def get_fields(self, *args, **kwargs): # Questionnaire version is required when creating (to confirm data integrity) fields["questionnaire_version"].required = isPost fields["questionnaire_version"].read_only = not isPost - # Privacy consent acknowledgement is required when creating for auditability. - fields["privacy_consent_agreed"].required = isPost - fields["privacy_consent_agreed"].read_only = not isPost + # Collection notice acknowledgement is required when creating for auditability. + fields["collection_notice_agreed"].required = isPost + fields["collection_notice_agreed"].read_only = not isPost # Turnstile verification token is required during create and final submit PATCH. fields["turnstile_token"].required = isPost or isPatch fields["turnstile_token"].read_only = not (isPost or isPatch) @@ -346,7 +346,7 @@ def validate(self, attrs): questionnaire_id = questionnaire_data.get("id") questionnaire_code = questionnaire_data.get("code") questionnaire_version = questionnaire_data.get("version") - privacy_consent_agreed = attrs.get("privacy_consent_agreed") + collection_notice_agreed = attrs.get("collection_notice_agreed") # Ensure all integrity fields are present. if ( @@ -359,11 +359,11 @@ def validate(self, attrs): "Process slug, questionnaire id, code and version are required." ) - # Creation is allowed only when explicit consent is acknowledged. - if privacy_consent_agreed is not True: + # Creation is allowed only when explicit collection notice consent is acknowledged. + if collection_notice_agreed is not True: raise exceptions.ValidationError( { - "privacy_consent_agreed": "This field must be true to create an application." + "collection_notice_agreed": "This field must be true to create an application." } ) diff --git a/backend/applications/tests/test_serialisers.py b/backend/applications/tests/test_serialisers.py index 77b2e31..9a7bf71 100644 --- a/backend/applications/tests/test_serialisers.py +++ b/backend/applications/tests/test_serialisers.py @@ -181,7 +181,7 @@ def setUp(self): @patch("applications.serialisers.verify_turnstile_token") def test_create_requires_privacy_consent(self, mock_verify): - """ApplicationSerialiser requires privacy_consent_agreed.""" + """ApplicationSerialiser requires collection_notice_agreed.""" mock_verify.return_value = True from django.test import RequestFactory @@ -196,7 +196,7 @@ def test_create_requires_privacy_consent(self, mock_verify): "questionnaire_id": self.questionnaire.id, "questionnaire_code": self.questionnaire.code, "questionnaire_version": self.questionnaire.version, - "privacy_consent_agreed": False, # False + "collection_notice_agreed": False, # False "turnstile_token": "test-token", } @@ -206,7 +206,7 @@ def test_create_requires_privacy_consent(self, mock_verify): ) self.assertFalse(serializer.is_valid()) - self.assertIn("privacy_consent_agreed", serializer.errors or {}) + self.assertIn("collection_notice_agreed", serializer.errors or {}) @patch("applications.serialisers.verify_turnstile_token") def test_create_validates_questionnaire_exists(self, mock_verify): @@ -225,7 +225,7 @@ def test_create_validates_questionnaire_exists(self, mock_verify): "questionnaire_id": 99999, # Non-existent "questionnaire_code": "new-app", "questionnaire_version": 1, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "test-token", } diff --git a/backend/applications/tests/test_turnstile.py b/backend/applications/tests/test_turnstile.py index 79fcc5a..8c3bb69 100644 --- a/backend/applications/tests/test_turnstile.py +++ b/backend/applications/tests/test_turnstile.py @@ -78,7 +78,7 @@ def _build_payload(self): "questionnaire_id": self.questionnaire.id, "questionnaire_code": self.questionnaire.code, "questionnaire_version": self.questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "test-token", } diff --git a/backend/e2e/conftest.py b/backend/e2e/conftest.py index 1b90585..e9746ce 100644 --- a/backend/e2e/conftest.py +++ b/backend/e2e/conftest.py @@ -464,7 +464,7 @@ def draft_application(authenticated_request_context_factory, e2e_users): "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-turnstile-token", }), headers={ diff --git a/backend/e2e/tests/test_my_applications_workflows.py b/backend/e2e/tests/test_my_applications_workflows.py index 9becb53..be26aa7 100644 --- a/backend/e2e/tests/test_my_applications_workflows.py +++ b/backend/e2e/tests/test_my_applications_workflows.py @@ -39,7 +39,7 @@ def multiple_applications_fixture(authenticated_request_context_factory, e2e_use "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-turnstile-token", }), headers={ @@ -58,7 +58,7 @@ def multiple_applications_fixture(authenticated_request_context_factory, e2e_use "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-turnstile-token", }), headers={ @@ -105,7 +105,7 @@ def draft_application_for_discard(authenticated_request_context_factory, e2e_use "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-turnstile-token", }), headers={ diff --git a/backend/e2e/tests/test_new_application_page.py b/backend/e2e/tests/test_new_application_page.py index 19418d6..50925c0 100644 --- a/backend/e2e/tests/test_new_application_page.py +++ b/backend/e2e/tests/test_new_application_page.py @@ -71,7 +71,7 @@ def test_new_application_requires_privacy_consent_before_creation( "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": False, + "collection_notice_agreed": False, "turnstile_token": "e2e-token", }), headers={ @@ -85,7 +85,7 @@ def test_new_application_requires_privacy_consent_before_creation( request_context.dispose() assert status == 400 - assert "privacy_consent_agreed" in payload + assert "collection_notice_agreed" in payload @pytest.mark.e2e @@ -109,7 +109,7 @@ def test_new_application_requires_turnstile_token( "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, }), headers={ str(auth_context["csrf_header"]): str(auth_context["csrf_token"]), @@ -146,7 +146,7 @@ def test_new_application_creation_succeeds_with_valid_payload( "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-token", }), headers={ diff --git a/backend/e2e/tests/test_user_end_to_end_flow.py b/backend/e2e/tests/test_user_end_to_end_flow.py index 81d26e2..ecb0be3 100644 --- a/backend/e2e/tests/test_user_end_to_end_flow.py +++ b/backend/e2e/tests/test_user_end_to_end_flow.py @@ -46,7 +46,7 @@ def draft_application(authenticated_request_context_factory, e2e_users): "questionnaire_id": questionnaire.id, "questionnaire_code": questionnaire.code, "questionnaire_version": questionnaire.version, - "privacy_consent_agreed": True, + "collection_notice_agreed": True, "turnstile_token": "e2e-turnstile-token", }), headers={ diff --git a/frontend/src/components/layout/form/FormLayout.tsx b/frontend/src/components/layout/form/FormLayout.tsx index 197142f..854ffa6 100644 --- a/frontend/src/components/layout/form/FormLayout.tsx +++ b/frontend/src/components/layout/form/FormLayout.tsx @@ -223,7 +223,7 @@ export const FormLayout = () => { showSnackbar( <> DBCA will collect, use and disclose your personal information in
- accordance with applicable privacy laws and DBCA's{" "} + accordance with applicable privacy laws and its{" "} Privacy Policy . diff --git a/frontend/src/components/layout/main/NewApplication.tsx b/frontend/src/components/layout/main/NewApplication.tsx index ecf3223..52de54c 100644 --- a/frontend/src/components/layout/main/NewApplication.tsx +++ b/frontend/src/components/layout/main/NewApplication.tsx @@ -1,4 +1,5 @@ import CreateOutlinedIcon from '@mui/icons-material/CreateOutlined'; +import LaunchIcon from '@mui/icons-material/Launch'; import LinkOutlinedIcon from '@mui/icons-material/LinkOutlined'; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; @@ -6,6 +7,7 @@ import Card from "@mui/material/Card"; import Checkbox from "@mui/material/Checkbox"; import FormControlLabel from "@mui/material/FormControlLabel"; import IconButton from '@mui/material/IconButton'; +import Link from '@mui/material/Link'; import MuiLink from '@mui/material/Link'; import Stack from "@mui/material/Stack"; import Tab from "@mui/material/Tab"; @@ -26,7 +28,6 @@ import type { IAuthorisationProcess, IQuestionnaireData } from "../../../context import { openNewTab } from '../../../context/Utils'; import { EmptyStateComponent } from "./EmptyState"; import { LoadingState } from "./LoadingState"; -import { PrivacyContent } from './PrivacyPolicy'; // ============================================================================ // Utility Functions & Interfaces @@ -43,7 +44,6 @@ const generateQuestionnaireHash = (questionnaire: IQuestionnaireData): string => }; - interface IProcessGroup { process: IAuthorisationProcess; questionnaires: IQuestionnaireData[]; @@ -73,15 +73,115 @@ const buildProcessGroups = ( // Application Flow Functions // ============================================================================ +/** + * Displays the S717 collection notice content for application consent flow. + * + * The content structure mirrors the source notice so reviewers can validate wording, + * bullet points, and links before final legal sign-off. + */ +const CollectionNoticeContent = () => { + return ( + <> + + The Department of Biodiversity, Conservation and Attractions (DBCA) collects personal information to: + + + + + + The personal information collected may include names, contact details, organisational affiliation, role details and other information necessary to assess applications and issue lawful authority for activities. + + + + DBCA may share this information: + + + + + + You are required to provide this information where it is necessary to enable DBCA to assess applications and submissions and to perform its statutory functions under the BC Act, the AW Act, the CALM Act, the CALM Regulations and to support DPIRD's statutory functions under the FRMA Act. + + + + If you choose not to provide the required personal information, DBCA may be unable to assess your application or submission, issue an approval or authorisation, or progress the matter further. + + + + DBCA will handle all personal information in accordance with the PRIS Act and DBCA's Privacy Policy. + + + + For further details on how DBCA manages your personal information, please refer to DBCA's Privacy Policy. + + + + If you have any questions about how your personal information will be handled, or if you would like to access or correct your personal information, please contact DBCA at email privacy@dbca.wa.gov.au. + + + ); +} + const createNewApplication = async ({ questionnaire, - privacyConsentAgreed, + collectionNoticeAgreed, turnstileToken, navigate, showSnackbar, }: { questionnaire: IQuestionnaireData; - privacyConsentAgreed: boolean; + collectionNoticeAgreed: boolean; turnstileToken: string; navigate: NavigateFunction; showSnackbar: (message: React.ReactNode, severity?: AlertColor) => void; @@ -91,7 +191,7 @@ const createNewApplication = async ({ questionnaireId: questionnaire.id, questionnaireCode: questionnaire.code, questionnaireVersion: questionnaire.version, - privacyConsentAgreed, + collectionNoticeAgreed, turnstileToken, }).catch((error: AxiosError) => { showSnackbar( @@ -112,13 +212,13 @@ const createNewApplication = async ({ } /** - * Wraps the privacy notice with dialog-specific acknowledgement controls. + * Wraps the collection notice disclaimer with dialog-specific acknowledgement controls. * * The content stays reusable for standalone pages, while this component owns * the acceptance state required only for the application creation flow. * Renders a Turnstile verification widget and gates checkbox interaction on successful verification. */ -const PrivacyConsentDialogContent = ({ +const CollectionNoticeConsentDialogContent = ({ onAgree, onDecline, }: { @@ -194,7 +294,7 @@ const PrivacyConsentDialogContent = ({ return ( <> - + {/* Turnstile verification widget container with loading spinner */} @@ -209,7 +309,7 @@ const PrivacyConsentDialogContent = ({ )} - {/* Privacy acknowledgement checkbox is disabled until verification succeeds */} + {/* Collection notice acknowledgement checkbox is disabled until verification succeeds */} )} - label="I acknowledge that DBCA will collect, use and disclose my personal information in accordance with applicable privacy laws and DBCA's Privacy Policy." + label="I acknowledge the above information and that DBCA will handle my personal information in accordance with applicable privacy laws and its Privacy Policy." /> @@ -279,13 +379,13 @@ const startApplication = async ({ } /** - * Opens the PRIS consent window and gates application creation on acceptance. + * Opens the collection notice consent window and gates application creation on acceptance. */ - const showPrivacyConsentDialog = () => { + const showCollectionNoticeConsentDialog = () => { showDialog({ title: "Collection Notice Disclaimer", content: ( - { hideDialog(); setInProgress(false); @@ -293,7 +393,7 @@ const startApplication = async ({ onAgree={async (turnstileToken: string) => { await createNewApplication({ questionnaire, - privacyConsentAgreed: true, + collectionNoticeAgreed: true, turnstileToken, navigate, showSnackbar, @@ -331,7 +431,7 @@ const startApplication = async ({ color="warning" onClick={async () => { hideDialog(); - showPrivacyConsentDialog(); + showCollectionNoticeConsentDialog(); }} >Confirm @@ -340,7 +440,7 @@ const startApplication = async ({ }); } else { - showPrivacyConsentDialog(); + showCollectionNoticeConsentDialog(); } } diff --git a/frontend/src/components/layout/main/PrivacyPolicy.tsx b/frontend/src/components/layout/main/PrivacyPolicy.tsx index e143271..6090653 100644 --- a/frontend/src/components/layout/main/PrivacyPolicy.tsx +++ b/frontend/src/components/layout/main/PrivacyPolicy.tsx @@ -1,6 +1,7 @@ import Box from "@mui/material/Box"; import Link from "@mui/material/Link"; import Typography from "@mui/material/Typography"; +import LaunchIcon from "@mui/icons-material/Launch"; /** * Displays the S717 privacy collection notice content. @@ -12,65 +13,75 @@ export const PrivacyContent = () => { return ( <> - The Department of Biodiversity, Conservation and Attractions (DBCA) collects personal information in order to: + The Department of Biodiversity, Conservation and Attractions (DBCA) collects personal information to: -
    +
    • - Receive, assess and manage animal ethics submissions and approvals in accordance with section 8 of the Animal Welfare Act 2002 (WA) + receive, assess and manage animal ethics submissions and approvals in accordance with section 8 of the Animal Welfare Act 2002 (WA) (AW Act);
    • - Assess and determine applications made under sections 40 and 45 of the Biodiversity Conservation Act 2016 (WA) + assess and determine applications made under sections 40 and 45 of the Biodiversity Conservation Act 2016 (WA) (BC Act);
    • - Administer, monitor and enforce authorisations, permits and approvals issued by DBCA + assess and determine applications made under regulation 89 of the Conservation and Land Management Regulations 2002 (WA) (CALM Regulations);
    • - Communicate with applicants, nominees, researchers, licence holders and authorised representatives regarding applications, approvals, compliance matters or related enquiries + administer, monitor and enforce other authorisations, permits and approvals that it issues;
    • - Meet DBCA's statutory obligations for record-keeping, reporting, audit and regulatory compliance. + communicate with applicants, nominees, researchers, licence holders and authorised representatives regarding applications, approvals, compliance matters or related enquiries; and + +
    • +
    • + + meet its statutory obligations for record-keeping, reporting, audit and regulatory compliance.
    - The personal information collected may include names, contact details, organisational affiliation, role details and other information necessary to assess applications and administer approvals. + The personal information collected may include names, contact details, organisational affiliation, role details and other information necessary to assess applications and issue lawful authority for activities. DBCA may share this information: -
      +
        +
      • + + internally within DBCA for assessment, decision-making, compliance, audit and operational purposes; + +
      • - Internally within DBCA for assessment, decision-making, compliance, audit and operational purposes + with relevant advisory bodies, committees or experts (including the Animal Ethics Committee) for the purpose of evaluating applications and submissions;
      • - With relevant advisory bodies, committees or experts (including the Animal Ethics Committee) for the purpose of evaluating applications and submissions + with the Department of Primary Industries and Regional Development (DPIRD) for the purpose of assessing and determining exemptions under section 7 of the Fish Resources Management Act 1994 (WA) (FRMA Act), including in some cases the application of biodiversity conservation conditions for the purposes of section 7(2)(b) of the BC Act; and
      • - With other Western Australian public sector agencies or oversight bodies where required or authorised under the Privacy and Responsible Information Sharing Act 2024 (WA), the Biodiversity Conservation Act 2016 (WA), the Animal Welfare Act 2002 (WA), or other written law. + with other Western Australian public sector agencies or oversight bodies where required or authorised under the Privacy and Responsible Information Sharing Act 2024 (WA) (PRIS Act), the BC Act, the AW Act, the Conservation and Land Management Act 1984 (WA) (CALM Act), or any other written law.
      - You are required to provide this information where it is necessary to enable DBCA to assess applications and submissions and to perform its statutory functions under the Biodiversity Conservation Act 2016 (WA) and the Animal Welfare Act 2002 (WA). + You are required to provide this information where it is necessary to enable DBCA to assess applications and submissions and to perform its statutory functions under the BC Act, the AW Act, the CALM Act, the CALM Regulations and to support DPIRD's statutory functions under the FRMA Act. @@ -78,15 +89,15 @@ export const PrivacyContent = () => { - DBCA will handle all personal information in accordance with the Privacy and Responsible Information Sharing Act 2024 (WA) and DBCA's Privacy Policy. + DBCA will handle all personal information in accordance with the PRIS Act and DBCA's Privacy Policy. - For further details on how DBCA manages your personal information, please refer to DBCA's Privacy Policy. + For further details on how DBCA manages your personal information, please refer to DBCA's Privacy Policy. - If you have any questions about how your personal information will be handled, or if you would like to access or correct your personal information, please contact DBCA on (08) 9219 9004 or email privacy@dbca.wa.gov.au. + If you have any questions about how your personal information will be handled, or if you would like to access or correct your personal information, please contact DBCA at email privacy@dbca.wa.gov.au. ); @@ -97,7 +108,7 @@ export const PrivacyContent = () => { */ export const PrivacyPolicy = () => { return ( - + Privacy Policy diff --git a/frontend/src/context/ApiManager.tsx b/frontend/src/context/ApiManager.tsx index 2659a4a..7fe572e 100644 --- a/frontend/src/context/ApiManager.tsx +++ b/frontend/src/context/ApiManager.tsx @@ -53,14 +53,14 @@ export class ApiManager { questionnaireId, questionnaireCode, questionnaireVersion, - privacyConsentAgreed, + collectionNoticeAgreed, turnstileToken, }: { processSlug: string; questionnaireId: number; questionnaireCode: string; questionnaireVersion: number; - privacyConsentAgreed: boolean; + collectionNoticeAgreed: boolean; turnstileToken: string; }): Promise { const requestConfig = ApiManager.getRequestConfig(); @@ -69,7 +69,7 @@ export class ApiManager { questionnaire_id: questionnaireId, questionnaire_code: questionnaireCode, questionnaire_version: questionnaireVersion, - privacy_consent_agreed: privacyConsentAgreed, + collection_notice_agreed: collectionNoticeAgreed, turnstile_token: turnstileToken, }, requestConfig); diff --git a/frontend/src/test/unit/context/api-manager.test.ts b/frontend/src/test/unit/context/api-manager.test.ts index b46d4c9..1c7ea30 100644 --- a/frontend/src/test/unit/context/api-manager.test.ts +++ b/frontend/src/test/unit/context/api-manager.test.ts @@ -37,7 +37,7 @@ describe("ApiManager", () => { questionnaireId: 5, questionnaireCode: "new", questionnaireVersion: 2, - privacyConsentAgreed: true, + collectionNoticeAgreed: true, turnstileToken: "ts-token", }); @@ -48,7 +48,7 @@ describe("ApiManager", () => { questionnaire_id: 5, questionnaire_code: "new", questionnaire_version: 2, - privacy_consent_agreed: true, + collection_notice_agreed: true, turnstile_token: "ts-token", }); expect(config.baseURL).toBe("/api");