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.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/actions_controller.rb +12 -2
- data/app/controllers/forest_liana/authentication_controller.rb +5 -21
- data/app/serializers/forest_liana/stripe_invoice_serializer.rb +5 -5
- data/app/services/forest_liana/authentication.rb +0 -2
- data/app/services/forest_liana/authorization_getter.rb +23 -21
- data/app/services/forest_liana/oidc_client_manager.rb +9 -5
- data/app/services/forest_liana/resource_creator.rb +1 -1
- data/app/services/forest_liana/resource_updater.rb +3 -3
- data/app/services/forest_liana/schema_utils.rb +8 -3
- data/app/services/forest_liana/stripe_invoice_getter.rb +1 -1
- data/app/services/forest_liana/stripe_invoices_getter.rb +1 -1
- data/app/services/forest_liana/stripe_source_getter.rb +1 -1
- data/app/services/forest_liana/stripe_sources_getter.rb +1 -1
- data/config/initializers/error-messages.rb +3 -0
- data/config/initializers/errors.rb +21 -2
- data/config/routes.rb +0 -4
- data/lib/forest_liana.rb +1 -0
- data/lib/forest_liana/bootstrapper.rb +12 -5
- data/lib/forest_liana/version.rb +1 -1
- data/lib/generators/forest_liana/install_generator.rb +13 -5
- data/spec/requests/actions_controller_spec.rb +49 -1
- data/spec/requests/authentications_spec.rb +7 -20
- data/test/routing/route_test.rb +0 -12
- data/test/services/forest_liana/resources_getter_test.rb +1 -1
- metadata +119 -154
- data/app/controllers/forest_liana/sessions_controller.rb +0 -95
- data/app/serializers/forest_liana/session_serializer.rb +0 -33
- data/app/services/forest_liana/login_handler.rb +0 -99
- data/app/services/forest_liana/two_factor_registration_confirmer.rb +0 -36
- data/app/services/forest_liana/user_secret_creator.rb +0 -26
- 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
|