forest_liana 6.0.0.pre.beta.3 → 6.0.3

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +12 -2
  3. data/app/controllers/forest_liana/authentication_controller.rb +5 -21
  4. data/app/serializers/forest_liana/stripe_invoice_serializer.rb +5 -5
  5. data/app/services/forest_liana/authentication.rb +0 -2
  6. data/app/services/forest_liana/authorization_getter.rb +23 -21
  7. data/app/services/forest_liana/oidc_client_manager.rb +9 -5
  8. data/app/services/forest_liana/resource_creator.rb +1 -1
  9. data/app/services/forest_liana/resource_updater.rb +3 -3
  10. data/app/services/forest_liana/schema_utils.rb +8 -3
  11. data/app/services/forest_liana/stripe_invoice_getter.rb +1 -1
  12. data/app/services/forest_liana/stripe_invoices_getter.rb +1 -1
  13. data/app/services/forest_liana/stripe_source_getter.rb +1 -1
  14. data/app/services/forest_liana/stripe_sources_getter.rb +1 -1
  15. data/config/initializers/error-messages.rb +3 -0
  16. data/config/initializers/errors.rb +21 -2
  17. data/config/routes.rb +0 -4
  18. data/lib/forest_liana.rb +1 -0
  19. data/lib/forest_liana/bootstrapper.rb +12 -5
  20. data/lib/forest_liana/version.rb +1 -1
  21. data/lib/generators/forest_liana/install_generator.rb +13 -5
  22. data/spec/requests/actions_controller_spec.rb +49 -1
  23. data/spec/requests/authentications_spec.rb +7 -20
  24. data/test/routing/route_test.rb +0 -12
  25. data/test/services/forest_liana/resources_getter_test.rb +1 -1
  26. metadata +119 -154
  27. data/app/controllers/forest_liana/sessions_controller.rb +0 -95
  28. data/app/serializers/forest_liana/session_serializer.rb +0 -33
  29. data/app/services/forest_liana/login_handler.rb +0 -99
  30. data/app/services/forest_liana/two_factor_registration_confirmer.rb +0 -36
  31. data/app/services/forest_liana/user_secret_creator.rb +0 -26
  32. data/spec/requests/sessions_spec.rb +0 -53
@@ -1,95 +0,0 @@
1
- module ForestLiana
2
- class SessionsController < ForestLiana::BaseController
3
- def create_with_password
4
- @error_message = nil
5
- rendering_id = params['renderingId']
6
- project_id = params['projectId']
7
- email = params['email']
8
- password = params['password']
9
- two_factor_token = params['token']
10
- two_factor_registration = params['twoFactorRegistration']
11
-
12
- process_login(
13
- use_google_authentication: false,
14
- rendering_id: rendering_id,
15
- project_id: project_id,
16
- auth_data: { email: email, password: password },
17
- two_factor_registration: two_factor_registration,
18
- two_factor_token: two_factor_token,
19
- )
20
- end
21
-
22
- def create_with_google
23
- @error_message = nil
24
-
25
- forest_token = params['forestToken']
26
- rendering_id = params['renderingId']
27
- project_id = params['projectId']
28
- two_factor_token = params['token']
29
- two_factor_registration = params['twoFactorRegistration']
30
-
31
- process_login(
32
- use_google_authentication: true,
33
- rendering_id: rendering_id,
34
- project_id: project_id,
35
- auth_data: { forest_token: forest_token },
36
- two_factor_registration: two_factor_registration,
37
- two_factor_token: two_factor_token,
38
- )
39
- end
40
-
41
- private
42
-
43
- def process_login(
44
- use_google_authentication:,
45
- rendering_id:,
46
- project_id:,
47
- auth_data:,
48
- two_factor_registration:,
49
- two_factor_token:
50
- )
51
- begin
52
- if two_factor_registration && two_factor_token.nil?
53
- raise ForestLiana::Errors::HTTP401Error
54
- end
55
-
56
- # NOTICE: The IP Whitelist is retrieved on any request if it was not retrieved yet, or when
57
- # an IP is rejected, to ensure the IP is still rejected (meaning the configuration
58
- # on the projects has not changed). To handle the last case, which is rejecting an
59
- # IP which was not initaliy rejected, we need periodically refresh the whitelist.
60
- # This is done here on the login of any user.
61
- ForestLiana::IpWhitelist.retrieve
62
-
63
- reponse_data = ForestLiana::LoginHandler.new(
64
- rendering_id,
65
- auth_data,
66
- use_google_authentication,
67
- two_factor_registration,
68
- project_id,
69
- two_factor_token
70
- ).perform
71
-
72
- rescue ForestLiana::Errors::ExpectedError => error
73
- error.display_error
74
- error_data = JSONAPI::Serializer.serialize_errors([{
75
- status: error.error_code,
76
- detail: error.message
77
- }])
78
- render(serializer: nil, json: error_data, status: error.status)
79
- rescue StandardError => error
80
- FOREST_LOGGER.error error
81
- FOREST_LOGGER.error error.backtrace.join("\n")
82
-
83
- render(serializer: nil, json: nil, status: :internal_server_error)
84
- else
85
- # NOTICE: Set a cookie to ensure secure authentication using export feature.
86
- # NOTICE: The token is empty at first authentication step if the 2FA option is active.
87
- if reponse_data[:token]
88
- response.set_cookie("forest_session_token", { value: reponse_data[:token], expires: (ForestLiana::Token.expiration_in_days) })
89
- end
90
-
91
- render(json: reponse_data, serializer: nil)
92
- end
93
- end
94
- end
95
- end
@@ -1,33 +0,0 @@
1
- module ForestLiana
2
- class SessionSerializer
3
- include JSONAPI::Serializer
4
-
5
- attribute :first_name
6
- attribute :last_name
7
- attribute :email
8
-
9
- def type
10
- 'users'
11
- end
12
-
13
- def format_name(attribute_name)
14
- attribute_name.to_s
15
- end
16
-
17
- def unformat_name(attribute_name)
18
- attribute_name.to_s.underscore
19
- end
20
-
21
- def self_link
22
- nil
23
- end
24
-
25
- def relationship_self_link(attribute_name)
26
- nil
27
- end
28
-
29
- def relationship_related_link(attribute_name)
30
- nil
31
- end
32
- end
33
- end
@@ -1,99 +0,0 @@
1
- require 'rotp'
2
-
3
- module ForestLiana
4
- class LoginHandler
5
- def initialize(
6
- rendering_id,
7
- auth_data,
8
- use_google_authentication,
9
- two_factor_registration,
10
- project_id,
11
- two_factor_token
12
- )
13
- @rendering_id = rendering_id
14
- @auth_data = auth_data
15
- @use_google_authentication = use_google_authentication
16
- @two_factor_registration = two_factor_registration
17
- @project_id = project_id
18
- @two_factor_token = two_factor_token
19
- end
20
-
21
- def perform
22
- user = ForestLiana::AuthorizationGetter.authenticate(
23
- @rendering_id,
24
- @use_google_authentication,
25
- @auth_data,
26
- @two_factor_registration
27
- )
28
-
29
- if user['two_factor_authentication_enabled']
30
- if !@two_factor_token.nil?
31
- if is_two_factor_token_valid(user, @two_factor_token)
32
- ForestLiana::TwoFactorRegistrationConfirmer
33
- .new(@project_id, @use_google_authentication, @auth_data)
34
- .perform
35
-
36
- return { 'token' => create_token(user, @rendering_id) }
37
- else
38
- raise ForestLiana::Errors::HTTP401Error.new('Your token is invalid, please try again.')
39
- end
40
- else
41
- return get_two_factor_response(user)
42
- end
43
- end
44
-
45
- return { token: create_token(user, @rendering_id) }
46
- end
47
-
48
- private
49
-
50
- def check_two_factor_secret_salt
51
- if ENV['FOREST_2FA_SECRET_SALT'].nil?
52
- FOREST_LOGGER.error 'Cannot use the two factor authentication because the environment variable "FOREST_2FA_SECRET_SALT" is not set.'
53
- FOREST_LOGGER.error 'You can generate it using this command: `$ openssl rand -hex 10`'
54
-
55
- raise Errors::HTTP401Error.new('Invalid 2FA configuration, please ask more information to your admin')
56
- end
57
-
58
- if ENV['FOREST_2FA_SECRET_SALT'].length != 20
59
- FOREST_LOGGER.error 'The FOREST_2FA_SECRET_SALT environment variable must be 20 characters long.'
60
- FOREST_LOGGER.error 'You can generate it using this command: `$ openssl rand -hex 10`'
61
-
62
- raise ForestLiana::Errors::HTTP401Error.new('Invalid 2FA configuration, please ask more information to your admin')
63
- end
64
- end
65
-
66
- def get_two_factor_response(user)
67
- check_two_factor_secret_salt
68
-
69
- return { 'twoFactorAuthenticationEnabled' => true } if user['two_factor_authentication_active']
70
-
71
- two_factor_authentication_secret = user['two_factor_authentication_secret']
72
- user_secret = ForestLiana::UserSecretCreator
73
- .new(two_factor_authentication_secret, ENV['FOREST_2FA_SECRET_SALT'])
74
- .perform
75
-
76
- {
77
- twoFactorAuthenticationEnabled: true,
78
- userSecret: user_secret,
79
- }
80
- end
81
-
82
- def is_two_factor_token_valid(user, two_factor_token)
83
- check_two_factor_secret_salt
84
-
85
- two_factor_authentication_secret = user['two_factor_authentication_secret']
86
-
87
- user_secret = ForestLiana::UserSecretCreator
88
- .new(two_factor_authentication_secret, ENV['FOREST_2FA_SECRET_SALT'])
89
- .perform
90
-
91
- totp = ROTP::TOTP.new(user_secret)
92
- totp.verify(two_factor_token)
93
- end
94
-
95
- def create_token(user, rendering_id)
96
- ForestLiana::Token.create_token(user, rendering_id)
97
- end
98
- end
99
- end
@@ -1,36 +0,0 @@
1
- module ForestLiana
2
- class TwoFactorRegistrationConfirmer
3
- def initialize(
4
- project_id,
5
- use_google_authentication,
6
- auth_data
7
- )
8
- @project_id = project_id
9
- @use_google_authentication = use_google_authentication
10
- @auth_data = auth_data
11
- end
12
-
13
- def perform
14
- begin
15
- body_data = { 'useGoogleAuthentication' => @use_google_authentication }
16
-
17
- if @use_google_authentication
18
- body_data['forestToken'] = @auth_data[:forest_token]
19
- else
20
- body_data['email'] = @auth_data[:email]
21
- end
22
-
23
- response = ForestLiana::ForestApiRequester.post(
24
- "/liana/v2/projects/#{@project_id}/two-factor-registration-confirm",
25
- body: body_data,
26
- )
27
-
28
- unless response.is_a?(Net::HTTPOK)
29
- raise "Cannot retrieve the data from the Forest server. Forest API returned an #{ForestLiana::Errors::HTTPErrorHelper.format(response)}"
30
- end
31
- rescue
32
- raise ForestLiana::Errors::HTTP401Error
33
- end
34
- end
35
- end
36
- end
@@ -1,26 +0,0 @@
1
- require 'base32'
2
-
3
- module ForestLiana
4
- # NOTICE: This service combines the 2FA secret stored on the forest server to the local secret
5
- # salt. This guarantees that only the owner of the server and the concerned end user can
6
- # know the final key.
7
- # This is done by using a bitwise exclusive or operation, which guarantees the key to stay
8
- # unique, so it is impossible for two users to have the same key.
9
- class UserSecretCreator
10
- def initialize(two_factor_authentication_secret, two_factor_secret_salt)
11
- @two_factor_authentication_secret = two_factor_authentication_secret
12
- @two_factor_secret_salt = two_factor_secret_salt
13
- end
14
-
15
- def perform
16
- hash = (@two_factor_authentication_secret.to_i(16) ^ @two_factor_secret_salt.to_i(16)).to_s(16)
17
- bin_hash = hex_to_bin(hash)
18
-
19
- Base32.encode(bin_hash).tr('=', '')
20
- end
21
-
22
- def hex_to_bin(hex_string)
23
- hex_string.scan(/../).map { |x| x.hex.chr }.join
24
- end
25
- end
26
- end
@@ -1,53 +0,0 @@
1
- require 'rails_helper'
2
- require 'openid_connect'
3
- require 'json'
4
-
5
- RSpec.describe "Authentications", type: :request do
6
- before() do
7
- allow(ForestLiana::IpWhitelist).to receive(:retrieve) { true }
8
- allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
9
- allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
10
-
11
- body = '{"data":{"id":"654","type":"users","attributes":{"email":"user@email.com","first_name":"FirstName","last_name":"LastName","teams":["Operations"]}},"relationships":{"renderings":{"data":[{"id":1,"type":"renderings"}]}}}'
12
- allow(ForestLiana::ForestApiRequester).to receive(:get).with(
13
- "/liana/v2/renderings/42/authorization", { :headers => { "forest-token" => "google-access-token" }, :query=> {} }
14
- ).and_return(
15
- instance_double(HTTParty::Response, :body => body, :code => 200)
16
- )
17
- end
18
-
19
- after() do
20
- Rails.cache.delete(URI.join(ForestLiana.application_url, ForestLiana::Engine.routes.url_helpers.authentication_callback_path).to_s)
21
- end
22
-
23
- describe "POST /forest/sessions-google" do
24
- before() do
25
- post ForestLiana::Engine.routes.url_helpers.sessions_google_path, params: '{ "renderingId": "42", "forestToken": "google-access-token" }', headers: {
26
- 'Accept' => 'application/json',
27
- 'Content-Type' => 'application/json',
28
- }
29
- end
30
-
31
- it "should respond with a 200 code" do
32
- expect(response).to have_http_status(200)
33
- end
34
-
35
- it "should return a valid authentication token" do
36
- response_body = JSON.parse(response.body, :symbolize_names => true)
37
- expect(response_body).to have_key(:token)
38
-
39
- token = response_body[:token]
40
- decoded = JWT.decode(token, ForestLiana.auth_secret, true, { algorithm: 'HS256' })[0]
41
-
42
- expected_token_data = {
43
- "id" => '654',
44
- "email" => 'user@email.com',
45
- "first_name" => 'FirstName',
46
- "last_name" => 'LastName',
47
- "rendering_id" => "42",
48
- "team" => 'Operations'
49
- }
50
- expect(decoded).to include(expected_token_data);
51
- end
52
- end
53
- end