forest_liana 2.11.13 → 2.12.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 +4 -4
- data/app/controllers/forest_liana/sessions_controller.rb +58 -83
- data/app/services/forest_liana/authorization_getter.rb +46 -0
- data/app/services/forest_liana/forest_api_requester.rb +30 -22
- data/app/services/forest_liana/login_handler.rb +113 -0
- data/app/services/forest_liana/permissions_getter.rb +14 -18
- data/app/services/forest_liana/two_factor_registration_confirmer.rb +36 -0
- data/app/services/forest_liana/user_secret_creator.rb +26 -0
- data/config/initializers/errors.rb +69 -0
- data/lib/forest_liana/version.rb +1 -1
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +973 -62283
- metadata +48 -5
- data/app/services/forest_liana/allowed_users_getter.rb +0 -31
- data/app/services/forest_liana/google_authorized_user_getter.rb +0 -28
- data/app/services/forest_liana/users_getter.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df754c6c7de93be5e155ea18ca2a236e737ff9ed
|
4
|
+
data.tar.gz: fb4fb5714e04982a5db0e474491c153cbbf28b47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b9dd0d21d2a2001d980b6aee0d8f519731a331d559d58e394e5d8723033a5e17c1327da5ba8e1db83352189d3b4917c8d85abbe46b2ae5f54e8d23a140f34eb
|
7
|
+
data.tar.gz: 9f10c5cca47362935d125593bb107eeea8d0119bf46ac275d3d096a134f8410ca7fca16e08ea01962ab7b9f9b6059f9f0c465e19e2f7c5e91e23ca1da72f48c9
|
@@ -2,107 +2,82 @@ module ForestLiana
|
|
2
2
|
class SessionsController < ForestLiana::BaseController
|
3
3
|
def create_with_password
|
4
4
|
@error_message = nil
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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']
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
[{ detail: 'Forest cannot retrieve any users for the project ' \
|
19
|
-
'you\'re trying to unlock.' }]), status: :unauthorized
|
20
|
-
else
|
21
|
-
head :unauthorized
|
22
|
-
end
|
23
|
-
end
|
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
|
+
)
|
24
20
|
end
|
25
21
|
|
26
22
|
def create_with_google
|
27
23
|
@error_message = nil
|
28
24
|
|
29
|
-
rendering_id = params['renderingId']
|
30
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']
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
[{ detail: @error_message }]), status: :unauthorized
|
41
|
-
else
|
42
|
-
head :unauthorized
|
43
|
-
end
|
44
|
-
end
|
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
|
+
)
|
45
39
|
end
|
46
40
|
|
47
41
|
private
|
48
42
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
BCrypt::Password.new(allowed_user['password']) == params['password']
|
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 Errors::HTTP401Error
|
61
54
|
end
|
62
|
-
end
|
63
|
-
end
|
64
55
|
|
65
|
-
|
66
|
-
AllowedUsersGetter.new(params['renderingId']).perform()
|
67
|
-
end
|
56
|
+
# TODO: Add ip whitelist retrieving when it exists.
|
68
57
|
|
69
|
-
|
70
|
-
|
71
|
-
|
58
|
+
reponse_data = LoginHandler.new(
|
59
|
+
rendering_id,
|
60
|
+
auth_data,
|
61
|
+
use_google_authentication,
|
62
|
+
two_factor_registration,
|
63
|
+
project_id,
|
64
|
+
two_factor_token
|
65
|
+
).perform
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
67
|
+
rescue ForestLiana::Errors::ExpectedError => error
|
68
|
+
error.display_error
|
69
|
+
error_data = JSONAPI::Serializer.serialize_errors([{
|
70
|
+
status: error.error_code,
|
71
|
+
detail: error.message
|
72
|
+
}])
|
73
|
+
render(serializer: nil, json: error_data, status: error.status)
|
74
|
+
rescue StandardError => error
|
75
|
+
FOREST_LOGGER.error error
|
76
|
+
FOREST_LOGGER.error error.backtrace.join("\n")
|
76
77
|
|
77
|
-
|
78
|
-
if ForestLiana.auth_secret.nil?
|
79
|
-
@error_message = "Your Forest auth key seems to be missing. Can " \
|
80
|
-
"you check that you properly set a Forest auth key in the " \
|
81
|
-
"forest_liana initializer?"
|
82
|
-
FOREST_LOGGER.error @error_message
|
83
|
-
nil
|
78
|
+
render(serializer: nil, json: nil, status: :internal_server_error)
|
84
79
|
else
|
85
|
-
|
86
|
-
exp: Time.now.to_i + 2.weeks.to_i,
|
87
|
-
data: {
|
88
|
-
id: user['id'],
|
89
|
-
type: 'users',
|
90
|
-
data: {
|
91
|
-
email: user['email'],
|
92
|
-
first_name: user['first_name'],
|
93
|
-
last_name: user['last_name'],
|
94
|
-
teams: user['teams']
|
95
|
-
},
|
96
|
-
relationships: {
|
97
|
-
renderings: {
|
98
|
-
data: [{
|
99
|
-
type: 'renderings',
|
100
|
-
id: params['renderingId']
|
101
|
-
}]
|
102
|
-
}
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}, ForestLiana.auth_secret, 'HS256')
|
80
|
+
render(json: reponse_data, serializer: nil)
|
106
81
|
end
|
107
82
|
end
|
108
83
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class AuthorizationGetter
|
3
|
+
def initialize(rendering_id, use_google_authentication, auth_data, two_factor_registration)
|
4
|
+
@rendering_id = rendering_id
|
5
|
+
@use_google_authentication = use_google_authentication
|
6
|
+
@auth_data = auth_data
|
7
|
+
@two_factor_registration = two_factor_registration
|
8
|
+
|
9
|
+
@route = "/liana/v2/renderings/#{rendering_id}"
|
10
|
+
@route += use_google_authentication ? "/google-authorization" : "/authorization"
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform
|
14
|
+
begin
|
15
|
+
if @use_google_authentication
|
16
|
+
headers = { 'forest-token': @auth_data[:forest_token] }
|
17
|
+
else
|
18
|
+
headers = { email: @auth_data[:email], password: @auth_data[:password] }
|
19
|
+
end
|
20
|
+
|
21
|
+
query_parameters = { 'renderingId' => @rendering_id }
|
22
|
+
|
23
|
+
if @two_factor_registration
|
24
|
+
query_parameters['two-factor-registration'] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
response = ForestApiRequester.get(@route, query: query_parameters, headers: headers)
|
28
|
+
|
29
|
+
if response.is_a?(Net::HTTPOK)
|
30
|
+
body = JSON.parse(response.body)
|
31
|
+
user = body['data']['attributes']
|
32
|
+
user['id'] = body['data']['id']
|
33
|
+
user
|
34
|
+
else
|
35
|
+
if @use_google_authentication
|
36
|
+
raise "Cannot authorize the user using this google account. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
|
37
|
+
else
|
38
|
+
raise "Cannot authorize the user using this email/password. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
raise Errors::HTTP401Error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,33 +1,41 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
1
3
|
module ForestLiana
|
2
4
|
class ForestApiRequester
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
response = client.request(request)
|
5
|
+
def self.get(route, query: nil, headers: {})
|
6
|
+
begin
|
7
|
+
HTTParty.get("#{forest_api_url}#{route}", {
|
8
|
+
headers: base_headers.merge(headers),
|
9
|
+
query: query,
|
10
|
+
}).response
|
11
|
+
rescue
|
12
|
+
raise 'Cannot reach Forest API, it seems to be down right now.'
|
13
|
+
end
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
def self.post(route, body: nil, query: nil, headers: {})
|
17
|
+
begin
|
18
|
+
HTTParty.post("#{forest_api_url}#{route}", {
|
19
|
+
headers: base_headers.merge(headers),
|
20
|
+
query: query,
|
21
|
+
body: body.to_json,
|
22
|
+
}).response
|
23
|
+
rescue
|
24
|
+
raise 'Cannot reach Forest API, it seems to be down right now.'
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
19
|
-
|
20
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.base_headers
|
31
|
+
{
|
32
|
+
'Content-Type' => 'application/json',
|
33
|
+
'forest-secret-key' => ForestLiana.env_secret,
|
34
|
+
}
|
21
35
|
end
|
22
36
|
|
23
|
-
def
|
24
|
-
|
25
|
-
unless query_parameters.nil?
|
26
|
-
query = query_parameters
|
27
|
-
.collect { |parameter, value| "#{parameter}=#{CGI::escape(value.to_s)}" }.join('&')
|
28
|
-
route += "?#{query}"
|
29
|
-
end
|
30
|
-
route
|
37
|
+
def self.forest_api_url
|
38
|
+
ENV['FOREST_URL'] || 'https://api.forestadmin.com'
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
@@ -0,0 +1,113 @@
|
|
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 = AuthorizationGetter.new(@rendering_id, @use_google_authentication, @auth_data, @two_factor_registration).perform
|
23
|
+
|
24
|
+
if user['two_factor_authentication_enabled']
|
25
|
+
if !@two_factor_token.nil?
|
26
|
+
if is_two_factor_token_valid(user, @two_factor_token)
|
27
|
+
TwoFactorRegistrationConfirmer.new(@project_id, @use_google_authentication, @auth_data)
|
28
|
+
.perform
|
29
|
+
|
30
|
+
return { 'token' => create_token(user, @rendering_id) }
|
31
|
+
else
|
32
|
+
raise Errors::HTTP401Error.new('Your token is invalid, please try again.')
|
33
|
+
end
|
34
|
+
else
|
35
|
+
return get_two_factor_response(user)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return { token: create_token(user, @rendering_id) }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def check_two_factor_secret_salt
|
45
|
+
if ENV['FOREST_2FA_SECRET_SALT'].nil?
|
46
|
+
FOREST_LOGGER.error 'Cannot use the two factor authentication because the environment variable "FOREST_2FA_SECRET_SALT" is not set.'
|
47
|
+
FOREST_LOGGER.error 'You can generate it using this command: `$ openssl rand -hex 10`'
|
48
|
+
|
49
|
+
raise Errors::HTTP401Error.new('Invalid 2FA configuration, please ask more information to your admin')
|
50
|
+
end
|
51
|
+
|
52
|
+
if ENV['FOREST_2FA_SECRET_SALT'].length != 20
|
53
|
+
FOREST_LOGGER.error 'The FOREST_2FA_SECRET_SALT environment variable must be 20 characters long.'
|
54
|
+
FOREST_LOGGER.error 'You can generate it using this command: `$ openssl rand -hex 10`'
|
55
|
+
|
56
|
+
raise Errors::HTTP401Error.new('Invalid 2FA configuration, please ask more information to your admin')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_two_factor_response(user)
|
61
|
+
check_two_factor_secret_salt
|
62
|
+
|
63
|
+
return { 'twoFactorAuthenticationEnabled' => true } if user['two_factor_authentication_active']
|
64
|
+
|
65
|
+
two_factor_authentication_secret = user['two_factor_authentication_secret']
|
66
|
+
user_secret = UserSecretCreator
|
67
|
+
.new(two_factor_authentication_secret, ENV['FOREST_2FA_SECRET_SALT'])
|
68
|
+
.perform
|
69
|
+
|
70
|
+
{
|
71
|
+
twoFactorAuthenticationEnabled: true,
|
72
|
+
userSecret: user_secret,
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_two_factor_token_valid(user, two_factor_token)
|
77
|
+
check_two_factor_secret_salt
|
78
|
+
|
79
|
+
two_factor_authentication_secret = user['two_factor_authentication_secret']
|
80
|
+
|
81
|
+
user_secret = UserSecretCreator
|
82
|
+
.new(two_factor_authentication_secret, ENV['FOREST_2FA_SECRET_SALT'])
|
83
|
+
.perform
|
84
|
+
|
85
|
+
totp = ROTP::TOTP.new(user_secret)
|
86
|
+
totp.verify(two_factor_token)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_token(user, rendering_id)
|
90
|
+
JWT.encode({
|
91
|
+
exp: Time.now.to_i + 2.weeks.to_i,
|
92
|
+
data: {
|
93
|
+
id: user['id'],
|
94
|
+
type: 'users',
|
95
|
+
data: {
|
96
|
+
email: user['email'],
|
97
|
+
first_name: user['first_name'],
|
98
|
+
last_name: user['last_name'],
|
99
|
+
teams: user['teams']
|
100
|
+
},
|
101
|
+
relationships: {
|
102
|
+
renderings: {
|
103
|
+
data: [{
|
104
|
+
type: 'renderings',
|
105
|
+
id: rendering_id
|
106
|
+
}]
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}, ForestLiana.auth_secret, 'HS256')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -1,28 +1,24 @@
|
|
1
1
|
module ForestLiana
|
2
|
-
class PermissionsGetter
|
2
|
+
class PermissionsGetter
|
3
3
|
def initialize(rendering_id)
|
4
|
-
@
|
4
|
+
@route = "/liana/v2/permissions"
|
5
5
|
@rendering_id = rendering_id
|
6
6
|
end
|
7
7
|
|
8
8
|
def perform
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
FOREST_LOGGER.error exception
|
13
|
-
nil
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
9
|
+
begin
|
10
|
+
query_parameters = { 'renderingId' => @rendering_id }
|
11
|
+
response = ForestApiRequester.get(@route, query: query_parameters)
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
FOREST_LOGGER.error
|
13
|
+
if response.is_a?(Net::HTTPOK)
|
14
|
+
JSON.parse(response.body)
|
15
|
+
else
|
16
|
+
raise "Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
|
17
|
+
end
|
18
|
+
rescue => exception
|
19
|
+
FOREST_LOGGER.error 'Cannot retrieve the permissions from the Forest server.'
|
20
|
+
FOREST_LOGGER.error 'Which was caused by:'
|
21
|
+
Errors::ExceptionHelper.recursively_print(exception, margin: ' ', is_error: true)
|
26
22
|
nil
|
27
23
|
end
|
28
24
|
end
|