forest_liana 2.11.13 → 2.12.0
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/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
|