forest_liana 6.0.0.pre.beta.3 → 6.0.3

Sign up to get free protection for your applications and to get access to all the features.
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