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 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