govuk_personalisation 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7494e4a43a49be9a654d9b95ad2a2884575c3d46b8b6e5487b9c472575705ada
4
- data.tar.gz: 4321076a89efdcc1289d3dccb23eb7c26a87dd877dc63764d1d6da796f18c128
3
+ metadata.gz: '08b691342c4c22592162f9eb11e8c8f74b8fea75af5669568c71d15337166626'
4
+ data.tar.gz: 0e266ec1d1f91a7537ccef2c1a4398e90b4c0435546772d41877cfd34e9df092
5
5
  SHA512:
6
- metadata.gz: 3d772b13491114187dc74f38ceb5959721da4e9df7caa46384fea1e5b7e2f0bdc96486b9e11e1f07a40da625371bfd219f10024d801f417fe23da75ec32ce435
7
- data.tar.gz: 1e68f1eeb364957909271666d19a12af06c3f64f47744c9e34d7850a99282b67685cace40b997d5b56a4245b1b7501fc8d03e4c2eaec61b8eddd91957604783f
6
+ metadata.gz: 5f265633173eee3b60e436c781ddb4047d849471cc1760d88e8c3e497a79d8a0fd188a4c3908feb33fecf3b8c2b171dbbc1ea7f08ed837fae2d58cfc5658ae35
7
+ data.tar.gz: d90dc8a74720981e16d7d379939c08072806ead930d012bb40c1a57ac2e90ae3879a10d7fb0bfbe3d874d39c70312952d72c0c9eca452fd757906c253ecb402e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.6.0
2
+
3
+ - Add `GovukPersonalisation::Flash` and helper methods to the concern ([#9](https://github.com/alphagov/govuk_personalisation/pull/9))
4
+ - Ensure every method has RDoc ([#10](https://github.com/alphagov/govuk_personalisation/pull/10))
5
+ - Remove unused `GovukPersonalisation::Error` class ([#10](https://github.com/alphagov/govuk_personalisation/pull/10))
6
+ - BREAKING: Rename `GovukPersonalisation::AccountConcern` to `GovukPersonalisation::ControllerConcern` ([#11](https://github.com/alphagov/govuk_personalisation/pull/11))
7
+
1
8
  # 0.5.0
2
9
 
3
10
  - Rename header name constants ([#7](https://github.com/alphagov/govuk_personalisation/pull/7))
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # GovukPersonalisation
1
+ # GOV.UK Personalisation
2
2
 
3
- A gem to hold shared code which other GOV.UK apps will use to implement
3
+ A gem to hold shared code which other GOV.UK apps use to implement
4
4
  accounts-related functionality.
5
5
 
6
6
  ## Installation
@@ -17,13 +17,64 @@ And then execute:
17
17
  $ bundle install
18
18
  ```
19
19
 
20
- ## Technical documentation
20
+ ## Usage
21
21
 
22
- <!-- TODO -->...
22
+ ### Rails concern
23
23
 
24
- ### Testing
24
+ Include the concern into a controller:
25
25
 
26
- <!-- TODO -->...
26
+ ```ruby
27
+ include GovukPersonalisation::AccountConcern
28
+ ```
29
+
30
+ And it will add `before_action` methods to:
31
+
32
+ - fetch the account session identifier from the request headers, making it available as `account_session_header`
33
+ - set a `Vary` response header, to ensure responses for different users are cached differently by the CDN
34
+
35
+ The following functions are available:
36
+
37
+ - `logged_in?` - check if there is an account session header
38
+ - `set_account_session_header` - replace the current session value, and set the response header the CDN uses to update the user's cookie
39
+ - `logout!` - clear the session value, and set the response header the CDN uses to remove the user's cookie
40
+
41
+ When run in development mode (`RAILS_ENV=development`), a cookie on
42
+ `dev.gov.uk` is used instead of custom headers.
43
+
44
+ ### Test helpers
45
+
46
+ There are test helpers for both request and feature specs to log the
47
+ user in. Include the relevant helper:
48
+
49
+ ```ruby
50
+ include GovukPersonalisation::TestHelpers::Requests
51
+ ```
52
+
53
+ *or*
54
+
55
+ ```ruby
56
+ include GovukPersonalisation::TestHelpers::Features
57
+ ```
58
+
59
+ And then log the user in:
60
+
61
+ ```ruby
62
+ before do
63
+ mock_logged_in_session
64
+ end
65
+ ```
66
+
67
+ If you need a specific session identifier, for example to match
68
+ against it in WebMock stubs or with the [gds-api-adapters][] test
69
+ helpers, you can pass it as an argument:
70
+
71
+ ```ruby
72
+ before do
73
+ mock_logged_in_session("your-session-identifier-goes-here")
74
+ end
75
+ ```
76
+
77
+ [gds-api-adapters]: https://github.com/alphagov/gds-api-adapters
27
78
 
28
79
  ## License
29
80
 
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "govuk_personalisation/version"
4
- require "govuk_personalisation/account_concern"
4
+ require "govuk_personalisation/controller_concern"
5
+ require "govuk_personalisation/flash"
5
6
  require "govuk_personalisation/test_helpers/features"
6
7
  require "govuk_personalisation/test_helpers/requests"
7
8
 
8
- module GovukPersonalisation
9
- class Error < StandardError; end
10
- # Your code goes here...
11
- end
9
+ module GovukPersonalisation; end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module GovukPersonalisation
6
+ module ControllerConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ ACCOUNT_SESSION_INTERNAL_HEADER_NAME = "HTTP_GOVUK_ACCOUNT_SESSION"
10
+ ACCOUNT_SESSION_HEADER_NAME = "GOVUK-Account-Session"
11
+ ACCOUNT_END_SESSION_HEADER_NAME = "GOVUK-Account-End-Session"
12
+ ACCOUNT_SESSION_DEV_COOKIE_NAME = "govuk_account_session"
13
+
14
+ included do
15
+ before_action :fetch_account_session_header
16
+ before_action :set_account_vary_header
17
+ attr_accessor :account_session_header
18
+ attr_reader :account_flash
19
+ end
20
+
21
+ # Read the `GOVUK-Account-Session` request header and set the
22
+ # `@account_session_header` and `@account_flash` variables. Also
23
+ # sets a response header with an empty flash if there is a flash
24
+ # in the request.
25
+ #
26
+ # This is called as a `before_action`
27
+ #
28
+ # This should not be called after either of the
29
+ # `@govuk_account_session` or flash to return to the user have
30
+ # been changed, as those changes will be overwritten.
31
+ def fetch_account_session_header
32
+ session_with_flash =
33
+ if request.headers[ACCOUNT_SESSION_INTERNAL_HEADER_NAME]
34
+ request.headers[ACCOUNT_SESSION_INTERNAL_HEADER_NAME].presence
35
+ elsif Rails.env.development?
36
+ cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME]
37
+ end
38
+
39
+ @account_session_header, flash = GovukPersonalisation::Flash.decode_session(session_with_flash)
40
+ @account_flash = (flash || []).index_with { |_| true }
41
+ @new_account_flash = {}
42
+
43
+ set_account_session_header unless @account_flash.empty?
44
+ end
45
+
46
+ # Set the `Vary: GOVUK-Account-Session` response header.
47
+ #
48
+ # This is called as a `before_action`, to ensure that pages
49
+ # rendered using one user's session are not served to another by
50
+ # our CDN. You should only skip this action if you are certain
51
+ # that the response does not include any personalisation, or if
52
+ # you prevent caching in some other way (for example, with
53
+ # `Cache-Control: no-store`).
54
+ def set_account_vary_header
55
+ response.headers["Vary"] = [response.headers["Vary"], ACCOUNT_SESSION_HEADER_NAME].compact.join(", ")
56
+ end
57
+
58
+ # Check if the user has a session.
59
+ #
60
+ # This does not call account-api to verify that the session is
61
+ # valid, but an invalid session would not allow a user to access
62
+ # any personal data anyway.
63
+ #
64
+ # @return [true, false] whether the user has a session
65
+ def logged_in?
66
+ account_session_header.present?
67
+ end
68
+
69
+ # Set a new session header.
70
+ #
71
+ # This should be called after any API call to account-api which
72
+ # returns a new session value. This is called automatically after
73
+ # updating the flash with `account_flash_add` or
74
+ # `account_flash_keep`
75
+ #
76
+ # Calling this after calling `logout!` will not prevent the user
77
+ # from being logged out.
78
+ #
79
+ # @param govuk_account_session [String, nil] the new session identifier
80
+ def set_account_session_header(govuk_account_session = nil)
81
+ @account_session_header = govuk_account_session if govuk_account_session
82
+
83
+ session_with_flash = GovukPersonalisation::Flash.encode_session(@account_session_header, @new_account_flash.keys)
84
+
85
+ response.headers[ACCOUNT_SESSION_HEADER_NAME] = session_with_flash
86
+ if Rails.env.development?
87
+ cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME] = {
88
+ value: session_with_flash,
89
+ domain: "dev.gov.uk",
90
+ }
91
+ end
92
+ end
93
+
94
+ # Clear the `@account_session_header` and set the logout response
95
+ # header.
96
+ def logout!
97
+ response.headers[ACCOUNT_END_SESSION_HEADER_NAME] = "1"
98
+ @account_session_header = nil
99
+ if Rails.env.development?
100
+ cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME] = {
101
+ value: "",
102
+ domain: "dev.gov.uk",
103
+ expires: 1.second.ago,
104
+ }
105
+ end
106
+ end
107
+
108
+ # Add a message to the flash to return to the user. This does not
109
+ # change `account_flash`
110
+ #
111
+ # @param message [String] the message to add
112
+ #
113
+ # @return [true, false] whether the message is valid (and so has been added)
114
+ def account_flash_add(message)
115
+ return false unless GovukPersonalisation::Flash.valid_message? message
116
+
117
+ @new_account_flash[message] = true
118
+ set_account_session_header
119
+ true
120
+ end
121
+
122
+ # Copy all messages from the `account_flash` into the flash to
123
+ # return to the user.
124
+ def account_flash_keep
125
+ @new_account_flash = @account_flash.merge(@new_account_flash)
126
+ set_account_session_header
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string-literal: true
2
+
3
+ module GovukPersonalisation::Flash
4
+ SESSION_SEPARATOR = "$$"
5
+ MESSAGE_SEPARATOR = ","
6
+ MESSAGE_REGEX = /\A[a-zA-Z0-9._\-]+\z/.freeze
7
+
8
+ # Splits the session header into a session value (suitable for using
9
+ # in account-api calls) and flash messages.
10
+ #
11
+ # @param encoded_session [String] the value of the `GOVUK-Account-Session` header
12
+ #
13
+ # @return [Array(String, Array<String>), nil] the session value and the flash messages
14
+ def self.decode_session(encoded_session)
15
+ session_bits = encoded_session&.split(SESSION_SEPARATOR)
16
+ return if session_bits.blank?
17
+
18
+ if session_bits.length == 1
19
+ [session_bits[0], []]
20
+ else
21
+ [session_bits[0], session_bits[1].split(MESSAGE_SEPARATOR)]
22
+ end
23
+ end
24
+
25
+ # Encodes the session value and a list of flash messages into a
26
+ # session header which can be returned to the user.
27
+ #
28
+ # @param session [String] the session value
29
+ # @param flash [Array<String>] the flash messages, which must all be `valid_message?`
30
+ #
31
+ # @return [String] the encoded session header value
32
+ def self.encode_session(session, flash)
33
+ if flash.blank?
34
+ session
35
+ else
36
+ "#{session}#{SESSION_SEPARATOR}#{flash.join(MESSAGE_SEPARATOR)}"
37
+ end
38
+ end
39
+
40
+ # Check if a string is valid as a flash message.
41
+ #
42
+ # @param message [String, nil] the flash message
43
+ #
44
+ # @return [true, false] whether the message is valid or not.
45
+ def self.valid_message?(message)
46
+ return false if message.nil?
47
+
48
+ message.match? MESSAGE_REGEX
49
+ end
50
+ end
@@ -1,8 +1,13 @@
1
1
  module GovukPersonalisation
2
2
  module TestHelpers
3
3
  module Features
4
- def mock_logged_in_session(value = "placeholder")
5
- page.driver.header("GOVUK-Account-Session", value)
4
+ # Set the `GOVUK-Account-Session` request header in the page
5
+ # driver.
6
+ #
7
+ # @param session_id [String] the session identifier
8
+ # @param flash [Array<String>, nil] the flash messages
9
+ def mock_logged_in_session(session_id = "placeholder", flash = nil)
10
+ page.driver.header("GOVUK-Account-Session", GovukPersonalisation::Flash.encode_session(session_id, flash))
6
11
  end
7
12
  end
8
13
  end
@@ -1,8 +1,12 @@
1
1
  module GovukPersonalisation
2
2
  module TestHelpers
3
3
  module Requests
4
- def mock_logged_in_session(value = "placeholder")
5
- request.headers["GOVUK-Account-Session"] = value
4
+ # Set the `GOVUK-Account-Session` request header.
5
+ #
6
+ # @param session_id [String] the session identifier
7
+ # @param flash [Array<String>, nil] the flash messages
8
+ def mock_logged_in_session(session_id = "placeholder", flash = nil)
9
+ request.headers["GOVUK-Account-Session"] = GovukPersonalisation::Flash.encode_session(session_id, flash)
6
10
  end
7
11
  end
8
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GovukPersonalisation
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_personalisation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-09 00:00:00.000000000 Z
11
+ date: 2021-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -116,7 +116,8 @@ files:
116
116
  - bin/setup
117
117
  - govuk_personalisation.gemspec
118
118
  - lib/govuk_personalisation.rb
119
- - lib/govuk_personalisation/account_concern.rb
119
+ - lib/govuk_personalisation/controller_concern.rb
120
+ - lib/govuk_personalisation/flash.rb
120
121
  - lib/govuk_personalisation/test_helpers/features.rb
121
122
  - lib/govuk_personalisation/test_helpers/requests.rb
122
123
  - lib/govuk_personalisation/version.rb
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
-
5
- module GovukPersonalisation
6
- module AccountConcern
7
- extend ActiveSupport::Concern
8
-
9
- ACCOUNT_SESSION_INTERNAL_HEADER_NAME = "HTTP_GOVUK_ACCOUNT_SESSION"
10
- ACCOUNT_SESSION_HEADER_NAME = "GOVUK-Account-Session"
11
- ACCOUNT_END_SESSION_HEADER_NAME = "GOVUK-Account-End-Session"
12
- ACCOUNT_SESSION_DEV_COOKIE_NAME = "govuk_account_session"
13
-
14
- included do
15
- before_action :fetch_account_session_header
16
- before_action :set_account_vary_header
17
- attr_accessor :account_session_header
18
- end
19
-
20
- def logged_in?
21
- account_session_header.present?
22
- end
23
-
24
- def fetch_account_session_header
25
- @account_session_header =
26
- if request.headers[ACCOUNT_SESSION_INTERNAL_HEADER_NAME]
27
- request.headers[ACCOUNT_SESSION_INTERNAL_HEADER_NAME].presence
28
- elsif Rails.env.development?
29
- cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME]
30
- end
31
- end
32
-
33
- def set_account_vary_header
34
- response.headers["Vary"] = [response.headers["Vary"], ACCOUNT_SESSION_HEADER_NAME].compact.join(", ")
35
- end
36
-
37
- def set_account_session_header(govuk_account_session = nil)
38
- @account_session_header = govuk_account_session if govuk_account_session
39
- response.headers[ACCOUNT_SESSION_HEADER_NAME] = @account_session_header
40
- if Rails.env.development?
41
- cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME] = {
42
- value: @account_session_header,
43
- domain: "dev.gov.uk",
44
- }
45
- end
46
- end
47
-
48
- def logout!
49
- response.headers[ACCOUNT_END_SESSION_HEADER_NAME] = "1"
50
- @account_session_header = nil
51
- if Rails.env.development?
52
- cookies[ACCOUNT_SESSION_DEV_COOKIE_NAME] = {
53
- value: "",
54
- domain: "dev.gov.uk",
55
- expires: 1.second.ago,
56
- }
57
- end
58
- end
59
- end
60
- end