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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e1e2f7e68df52d6adccbb4381d18c7363159ad2
4
- data.tar.gz: c1a3afce3057dc8f6c2770bf585b000f031089e4
3
+ metadata.gz: df754c6c7de93be5e155ea18ca2a236e737ff9ed
4
+ data.tar.gz: fb4fb5714e04982a5db0e474491c153cbbf28b47
5
5
  SHA512:
6
- metadata.gz: c449b27faa19a9eabd32f0fc2317cb63292a075d9d8d99d1176362fada86ab22d0b035df2e4cdf36f3bb660e506a29f41c01f39943777ca6e420c42a2d350b26
7
- data.tar.gz: b6bfc2f00d21e33bc4e6d4b9b15c877593f0897af572c46c72973492015ab6ac12cf5e4f6fb5717e41efda041e1b8fcdf7222919dea7a59d64cd988f122d422d
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
- @user_class = ForestLiana.user_class_name.constantize rescue nil
6
-
7
- user = check_user
8
- token = encode_token(user) if user
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
- if token
11
- render json: { token: token }, serializer: nil
12
- else
13
- if @error_message
14
- render serializer: nil, json: JSONAPI::Serializer.serialize_errors(
15
- [{ detail: @error_message }]), status: :unauthorized
16
- elsif !has_internal_authentication? && ForestLiana.allowed_users.empty?
17
- render serializer: nil, json: JSONAPI::Serializer.serialize_errors(
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
- user = GoogleAuthorizedUserGetter.new(rendering_id, forest_token).perform()
33
- token = encode_token(user) if user
34
-
35
- if token
36
- render json: { token: token }, serializer: nil
37
- else
38
- if @error_message
39
- render serializer: nil, json: JSONAPI::Serializer.serialize_errors(
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 check_user
50
- if has_internal_authentication?
51
- # NOTICE: Use the ForestUser table for authentication.
52
- user = @user_class.find_by(email: params['email'])
53
- user if !user.blank? && authenticate_internal_user(user['password_digest'])
54
- else
55
- # NOTICE: Query Forest server for authentication.
56
- fetch_allowed_users
57
-
58
- ForestLiana.allowed_users.find do |allowed_user|
59
- allowed_user['email'] == params['email'] &&
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
- def fetch_allowed_users
66
- AllowedUsersGetter.new(params['renderingId']).perform()
67
- end
56
+ # TODO: Add ip whitelist retrieving when it exists.
68
57
 
69
- def has_internal_authentication?
70
- @user_class && defined? @user_class
71
- end
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
- def authenticate_internal_user(password_digest)
74
- BCrypt::Password.new(password_digest).is_password?(params['password'])
75
- end
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
- def encode_token(user)
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
- JWT.encode({
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 perform_request(query_parameters = nil)
4
- http = Net::HTTP.new(@uri.host, @uri.port)
5
- http.use_ssl = true if forest_api_url.start_with?('https')
6
-
7
- http.start do |client|
8
- path = get_path(query_parameters)
9
- request = Net::HTTP::Get.new(path)
10
- request['Content-Type'] = 'application/json'
11
- request['forest-secret-key'] = ForestLiana.env_secret
12
- request['forest-token'] = @forest_token if @forest_token
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
- handle_service_response(response)
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
- def forest_api_url
20
- ENV['FOREST_URL'] || 'https://api.forestadmin.com';
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 get_path(query_parameters)
24
- route = @uri.path
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 < ForestApiRequester
2
+ class PermissionsGetter
3
3
  def initialize(rendering_id)
4
- @uri = URI.parse("#{forest_api_url}/liana/v2/permissions")
4
+ @route = "/liana/v2/permissions"
5
5
  @rendering_id = rendering_id
6
6
  end
7
7
 
8
8
  def perform
9
- perform_request({ 'renderingId' => @rendering_id })
10
- rescue => exception
11
- FOREST_LOGGER.error "Cannot retrieve the permissions for the project you\'re trying to unlock. Forest API seems to be down right now."
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
- def handle_service_response(response)
19
- if response.is_a?(Net::HTTPOK)
20
- JSON.parse(response.body)
21
- elsif response.is_a?(Net::HTTPNotFound) || response.is_a?(Net::HTTPUnprocessableEntity)
22
- FOREST_LOGGER.error "Cannot retrieve the permissions from the Forest server. Can you check that you properly set up the forest_env_secret?"
23
- nil
24
- else
25
- FOREST_LOGGER.error "Cannot retrieve the data from the Forest server. An error occured in Forest API."
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