forest_liana 6.0.0.pre.beta.4 → 6.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 +1 -1
- 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/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 +3 -17
- data/test/routing/route_test.rb +0 -12
- data/test/services/forest_liana/resources_getter_test.rb +3 -3
- 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
|