diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 00000000..fa8df175 --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,3 @@ +--- +ignore: + - CVE-2023-26141 diff --git a/.env.example b/.env.example index be9a6481..29d37d5c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ PORT=3000 GOVUK_NOTIFY_API_KEY= -GOVUK_NOTIFY_APPLICATION_SUBMITTED_TEMPLATE_ID= GOVUK_NOTIFY_GENERIC_EMAIL_TEMPLATE_ID= AZURE_CLIENT_ID= AZURE_CLIENT_SECRET= diff --git a/.rubocop.yml b/.rubocop.yml index ca1c59ef..9e23adcb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -100,3 +100,7 @@ RSpec/NestedGroups: RSpec/DescribeClass: Exclude: - 'spec/jobs/schedule_job_spec.rb' + +RSpec/AnyInstance: + Exclude: + - 'spec/requests/submission_spec.rb' diff --git a/README.md b/README.md index 0a5a7008..d52d576e 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,30 @@ When the versions are updated on the `main` branch run `asdf install` again to u installation. Use `asdf plugin update --all` to update plugins and get access to newer versions of tools. -## Setting up the app in development +Create your `.env` from the `.env.example` template +* GOVUK_NOTIFY_API_KEY, using the citest api key +* GOVUK_NOTIFY_GENERIC_EMAIL_TEMPLATE_ID, template id used by mail-notify +* AZURE_CLIENT_ID, to access application platform +* AZURE_CLIENT_SECRET +* AZURE_TENANT_ID +* REDIS_URL, redis url for sidekiq +* LOCAL_USER_EMAIL, your education.gov.uk email address to access the system_admin section + +### Manual development setup 1. Run `bundle install` to install the gem dependencies 2. Run `yarn` to install node dependencies 3. Run `bin/rails db:setup` to set up the database development and test schemas -4. Run `bundle exec rails server` to launch the app on -5. Run `./bin/webpack-dev-server` in a separate shell for faster compilation of assets +4. Run `bundle exec foreman start -f Procfile.dev` to launch the app on + + +### Docker based development setup +1. Run `tilt up -- --local-app` to launch the app on +You can also run `tilt up` that way you will be building the image definied by the Dockerfile -## Setup using Tilt -To have a development environment setup quickly with all the dependencies you can use [Tilt](https://tilt.dev/). -1. Create your local `.env` file from `.env.example` -2. Update your local `.env` with relevant information -3. Add your education.gov.uk email address to `LOCAL_USER_EMAIL` in `.env` -4. Run `tilt up` to start the server +This option will start the application and run the `db/seed.rb` file. -For development convenience you should use `tilt up -- --local-app` ## Running specs @@ -66,6 +73,34 @@ Run the full test suite with: bundle exec rake ``` + +## Platform + +You need to request `digitalauth.education.gov.uk` account before being able to access a deployed +instance. +Once your account active you need to request your temporary access token at +[portal.azure.com](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/azurerbac) + +The following environment are available on the platform: +* qa +* review, deployed on demand by adding the `deploy` label on a PR +* staging +* production + + +### Environment variables + +When adding / removing or editing along side the code changes you will need to update the all the +available environments. +Run the following command `make edit-app-secrets` + + +### SSH access + +Access a deploy with the command `make ssh`. + + + ## Architectural Decision Record See the [docs/adr](docs/adr) directory for a list of the Architectural Decision @@ -82,8 +117,4 @@ adr new "Title of ADR" ### Contingency This service does not offer any out of hours SLAs and there will be not on call shift. -Any incidents observed should follow [the incident reporting guidance](https://tech-docs.teacherservices.cloud/operating-a-service/incident-playbook.html) - -### Hosting - -TODO +Any incidents observed should follow [the incident reporting guidance](https://tech-docs.teacherservices.cloud/operating-a-service/incident-playbook.html) diff --git a/app/controllers/submission_controller.rb b/app/controllers/submission_controller.rb index 3d678603..e88d3822 100644 --- a/app/controllers/submission_controller.rb +++ b/app/controllers/submission_controller.rb @@ -16,7 +16,7 @@ def show end def create - service = SubmitForm.call(current_form) + service = SubmitForm.call(current_form, request.remote_ip) if service.success? update_session(service) redirect_to(submission_path) diff --git a/app/models/applicant.rb b/app/models/applicant.rb index d51194e8..71f64c79 100644 --- a/app/models/applicant.rb +++ b/app/models/applicant.rb @@ -9,6 +9,7 @@ # email_address :text # family_name :text # given_name :text +# ip_address :string # middle_name :string # nationality :text # passport_number :text diff --git a/app/models/reports/home_office.rb b/app/models/reports/home_office.rb index 92406a17..e54f2db8 100644 --- a/app/models/reports/home_office.rb +++ b/app/models/reports/home_office.rb @@ -27,8 +27,8 @@ def rows application.applicant.date_of_birth, application.applicant.nationality, application.applicant.passport_number, - application.visa_type, - application.date_of_entry, + nil, + nil, ] end end diff --git a/app/services/submit_form.rb b/app/services/submit_form.rb index 3718df2a..84f9a20e 100644 --- a/app/services/submit_form.rb +++ b/app/services/submit_form.rb @@ -7,11 +7,12 @@ def self.call(...) service end - def initialize(form) + def initialize(form, ip_address) @form = form + @ip_address = ip_address @success = false end - attr_reader :form, :application + attr_reader :form, :ip_address, :application delegate :errors, to: :form @@ -57,6 +58,7 @@ def create_school def create_applicant(school) Applicant.create!( + ip_address: ip_address, given_name: form.given_name, middle_name: form.middle_name, family_name: form.family_name, diff --git a/app/views/pages/privacy.html.erb b/app/views/pages/privacy.html.erb index 1e49acee..4599e6e2 100644 --- a/app/views/pages/privacy.html.erb +++ b/app/views/pages/privacy.html.erb @@ -30,6 +30,7 @@
  • nationality
  • passport number
  • gender
  • +
  • Ip address
  • @@ -103,7 +104,7 @@

    Last updated

    - We may need to update this privacy notice periodically so we recommend that you revisit this information from time to time. This version was last updated on 14 August 2023. + We may need to update this privacy notice periodically so we recommend that you revisit this information from time to time. This version was last updated on 15 September 2023.

    diff --git a/app/views/system_admin/applicants/show.html.erb b/app/views/system_admin/applicants/show.html.erb index a00002d3..e3b8fa6e 100644 --- a/app/views/system_admin/applicants/show.html.erb +++ b/app/views/system_admin/applicants/show.html.erb @@ -70,6 +70,10 @@ end %>

    Personal details

    <%= govuk_summary_list(actions: false) do |summary_list| + summary_list.with_row do |row| + row.with_key(text: 'Ip address') + row.with_value(text: @applicant.ip_address) + end summary_list.with_row do |row| row.with_key(text: 'Given name') row.with_value(text: @applicant.given_name) diff --git a/db/migrate/20230915100841_add_applicant_ip_address.rb b/db/migrate/20230915100841_add_applicant_ip_address.rb new file mode 100644 index 00000000..22e8a4d2 --- /dev/null +++ b/db/migrate/20230915100841_add_applicant_ip_address.rb @@ -0,0 +1,5 @@ +class AddApplicantIpAddress < ActiveRecord::Migration[7.0] + def change + add_column :applicants, :ip_address, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index b8fe4a09..2873ecf2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_06_030846) do +ActiveRecord::Schema[7.0].define(version: 2023_09_15_100841) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "plpgsql" @@ -48,6 +48,7 @@ t.bigint "school_id" t.string "middle_name" t.boolean "student_loan" + t.string "ip_address" t.index ["school_id"], name: "index_applicants_on_school_id" end diff --git a/spec/factories/applicants.rb b/spec/factories/applicants.rb index 8d79193c..7ea24ddb 100644 --- a/spec/factories/applicants.rb +++ b/spec/factories/applicants.rb @@ -7,6 +7,7 @@ # email_address :text # family_name :text # given_name :text +# ip_address :string # middle_name :string # nationality :text # passport_number :text diff --git a/spec/models/reports/home_office_spec.rb b/spec/models/reports/home_office_spec.rb index eb3a18df..88405791 100644 --- a/spec/models/reports/home_office_spec.rb +++ b/spec/models/reports/home_office_spec.rb @@ -57,8 +57,8 @@ module Reports application.applicant.date_of_birth, application.applicant.nationality, application.applicant.passport_number, - application.visa_type, - application.date_of_entry, + nil, + nil, ].join(",")) end diff --git a/spec/requests/submission_spec.rb b/spec/requests/submission_spec.rb index 0b485f5e..0e512db4 100644 --- a/spec/requests/submission_spec.rb +++ b/spec/requests/submission_spec.rb @@ -8,4 +8,21 @@ expect(response).to have_http_status(:found) end end + + describe "POST /summary" do + let(:service) { SubmitForm.new(form, remote_ip) } + let(:form) { build(:form) } + let(:remote_ip) { "127.0.0.1" } + + before do + allow_any_instance_of(SubmissionController).to receive(:check_service_open!).and_return(true) + allow_any_instance_of(SubmissionController).to receive(:current_form).and_return(form) + allow(SubmitForm).to receive(:call).and_return(service) + end + + it "records ip address" do + post "/summary" + expect(SubmitForm).to have_received(:call).with(form, remote_ip) + end + end end diff --git a/spec/services/submit_form_spec.rb b/spec/services/submit_form_spec.rb index 1cf0cabb..3c110afe 100644 --- a/spec/services/submit_form_spec.rb +++ b/spec/services/submit_form_spec.rb @@ -1,9 +1,10 @@ require "rails_helper" RSpec.describe SubmitForm do - subject(:service) { described_class.new(form) } + subject(:service) { described_class.new(form, remote_ip) } let(:form) { build(:form, :complete, :eligible) } + let(:remote_ip) { "204.65.54.6" } describe "valid?" do context "returns true when form complete and eligible" do @@ -47,6 +48,89 @@ it { expect { service.submit_form! }.to change(Application, :count).by(1) } it { expect { service.submit_form! }.to change(Form, :count).from(1).to(0) } + context "school attributes" do + before do + allow(School).to receive(:create!).and_return(school) + + service.submit_form! + end + + let(:school) { build(:school) } + let(:expected_school_data) do + { + name: form.school_name, + headteacher_name: form.school_headteacher_name, + address_attributes: { + address_line_1: form.school_address_line_1, + address_line_2: form.school_address_line_2, + city: form.school_city, + postcode: form.school_postcode, + }, + } + end + + it { expect(School).to have_received(:create!).with(expected_school_data) } + end + + context "applicants attributes" do + before do + allow(Applicant).to receive(:create!).and_return(applicant) + + service.submit_form! + end + + let(:applicant) { build(:applicant) } + let(:expected_applicant_data) do + { + ip_address: service.ip_address, + given_name: form.given_name, + middle_name: form.middle_name, + family_name: form.family_name, + email_address: form.email_address, + phone_number: form.phone_number, + date_of_birth: form.date_of_birth, + sex: form.sex, + passport_number: form.passport_number, + nationality: form.nationality, + student_loan: form.student_loan, + address_attributes: { + address_line_1: form.address_line_1, + address_line_2: form.address_line_2, + city: form.city, + postcode: form.postcode, + }, + school: kind_of(School), + } + end + + it { expect(Applicant).to have_received(:create!).with(expected_applicant_data) } + end + + context "applications attributes" do + before do + allow(Application).to receive(:create!).and_return(application) + + service.submit_form! + end + + let(:application) { build(:application) } + let(:expected_application_data) do + { + applicant: kind_of(Applicant), + application_date: Date.current.to_s, + application_route: form.application_route, + application_progress: kind_of(ApplicationProgress), + date_of_entry: form.date_of_entry, + start_date: form.start_date, + subject: SubjectStep.new(form).answer.formatted_value, + urn: kind_of(String), + visa_type: form.visa_type, + } + end + + it { expect(Application).to have_received(:create!).with(expected_application_data) } + end + context "applicant email" do before do allow(Urn).to receive(:generate).and_return(urn)