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)