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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +12 -2
  3. data/app/controllers/forest_liana/authentication_controller.rb +5 -21
  4. data/app/serializers/forest_liana/stripe_invoice_serializer.rb +5 -5
  5. data/app/services/forest_liana/authentication.rb +0 -2
  6. data/app/services/forest_liana/authorization_getter.rb +23 -21
  7. data/app/services/forest_liana/oidc_client_manager.rb +1 -1
  8. data/app/services/forest_liana/resource_creator.rb +1 -1
  9. data/app/services/forest_liana/resource_updater.rb +3 -3
  10. data/app/services/forest_liana/schema_utils.rb +8 -3
  11. data/app/services/forest_liana/stripe_invoice_getter.rb +1 -1
  12. data/app/services/forest_liana/stripe_invoices_getter.rb +1 -1
  13. data/app/services/forest_liana/stripe_source_getter.rb +1 -1
  14. data/app/services/forest_liana/stripe_sources_getter.rb +1 -1
  15. data/config/initializers/error-messages.rb +3 -0
  16. data/config/initializers/errors.rb +21 -2
  17. data/config/routes.rb +0 -4
  18. data/lib/forest_liana/bootstrapper.rb +12 -5
  19. data/lib/forest_liana/version.rb +1 -1
  20. data/lib/generators/forest_liana/install_generator.rb +13 -5
  21. data/spec/requests/actions_controller_spec.rb +49 -1
  22. data/spec/requests/authentications_spec.rb +3 -17
  23. data/test/routing/route_test.rb +0 -12
  24. data/test/services/forest_liana/resources_getter_test.rb +3 -3
  25. metadata +119 -154
  26. data/app/controllers/forest_liana/sessions_controller.rb +0 -95
  27. data/app/serializers/forest_liana/session_serializer.rb +0 -33
  28. data/app/services/forest_liana/login_handler.rb +0 -99
  29. data/app/services/forest_liana/two_factor_registration_confirmer.rb +0 -36
  30. data/app/services/forest_liana/user_secret_creator.rb +0 -26
  31. data/spec/requests/sessions_spec.rb +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d299c9968621d0592da4e25c67969073237e6cf0eac69d0d898e6db6027ad14
4
- data.tar.gz: bbb59dfd951086e62df9829124fcf57ef0b71d40550597abeaff3de07450c946
3
+ metadata.gz: 741046b8057ebd5d86f157a30718f97eaee9a4a07ad17cdd5ce45ddeae32cb5c
4
+ data.tar.gz: '0857804c6e7272e3178139a7d3967840f8d8f87c56b73a9ca122faf4270db18d'
5
5
  SHA512:
6
- metadata.gz: 9b3dc3c43d682e085dd95066648471f779bd03da28b935eb645532e04da42ab75651d679291d03118355cba35379655878fa9a3a36a2fb945fbdb1ac35099748
7
- data.tar.gz: 2bd81a3eb3aa40c9129b1afd5fc86004eb10bbe6115f422537f82ffbeec0d94be77c542f68ed5494688464f1857bee5b691187a95d86a45357ca6d3e07b7c400
6
+ metadata.gz: 8130addad197c5995881a9f5af072ee2a3e1099c177ae8a14f945448a06ef95363957054aa6c44e6980d03edfe3f7210aa34c8237f21dd08c2e7faaa4464c9c5
7
+ data.tar.gz: 649e455781ed81a414c96476ab9e2156b68bc04132a7bdd28fc46bf2106b1f0ebadf673059985f02f186c47aa91b67c27099f8799e3f5433769749521586d37d
@@ -55,10 +55,20 @@ module ForestLiana
55
55
  # Apply result on fields (transform the object back to an array), preserve order.
56
56
  fields = action.fields.map do |field|
57
57
  updated_field = result[field[:field]]
58
+
58
59
  # Reset `value` when not present in `enums` (which means `enums` has changed).
59
- if updated_field[:enums].is_a?(Array) && !updated_field[:enums].include?(updated_field[:value])
60
- updated_field[:value] = nil
60
+ if updated_field[:enums].is_a?(Array)
61
+ # `value` can be an array if the type of fields is `[x]`
62
+ if updated_field[:type].is_a?(Array) && updated_field[:value].is_a?(Array) && !(updated_field[:value] - updated_field[:enums]).empty?
63
+ updated_field[:value] = nil
64
+ end
65
+
66
+ # `value` can be any other value
67
+ if !updated_field[:type].is_a?(Array) && !updated_field[:enums].include?(updated_field[:value])
68
+ updated_field[:value] = nil
69
+ end
61
70
  end
71
+
62
72
  updated_field
63
73
  end
64
74
 
@@ -17,7 +17,7 @@ module ForestLiana
17
17
  end
18
18
 
19
19
  def get_callback_url
20
- URI.join(ForestLiana.application_url, "/forest/#{CALLBACK_AUTHENTICATION_ROUTE}").to_s
20
+ File.join(ForestLiana.application_url, "/forest/#{CALLBACK_AUTHENTICATION_ROUTE}").to_s
21
21
  rescue => error
22
22
  raise "application_url is not valid or not defined" if error.is_a?(ArgumentError)
23
23
  end
@@ -61,33 +61,17 @@ module ForestLiana
61
61
  callback_url,
62
62
  params,
63
63
  )
64
-
65
- response.set_cookie(
66
- 'forest_session_token',
67
- {
68
- value: token,
69
- httponly: true,
70
- secure: true,
71
- expires: ForestLiana::Token.expiration_in_days,
72
- samesite: 'none',
73
- path: '/'
74
- },
75
- )
76
64
 
77
65
  response_body = {
66
+ token: token,
78
67
  tokenData: JWT.decode(token, ForestLiana.auth_secret, true, { algorithm: 'HS256' })[0]
79
68
  }
80
69
 
81
- # The token is sent decoded, because we don't want to share the whole, signed token
82
- # that is used to authenticate people
83
- # but the token itself contains interesting values, such as its expiration date
84
- response_body[:token] = token if !ForestLiana.application_url.start_with?('https://')
85
-
86
70
  render json: response_body, status: 200
87
71
 
88
72
  rescue => error
89
- render json: { errors: [{ status: 500, detail: error.message }] },
90
- status: :internal_server_error, serializer: nil
73
+ render json: { errors: [{ status: error.try(:error_code) || 500, detail: error.try(:message) }] },
74
+ status: error.try(:status) || :internal_server_error, serializer: nil
91
75
  end
92
76
  end
93
77
 
@@ -104,7 +88,7 @@ module ForestLiana
104
88
  httponly: true,
105
89
  secure: true,
106
90
  expires: Time.at(0),
107
- samesite: 'none',
91
+ same_site: :None,
108
92
  path: '/'
109
93
  },
110
94
  )
@@ -3,20 +3,20 @@ module ForestLiana
3
3
  include JSONAPI::Serializer
4
4
 
5
5
  attribute :amount_due
6
+ attribute :amount_paid
7
+ attribute :amount_remaining
8
+ attribute :application_fee_amount
6
9
  attribute :attempt_count
7
10
  attribute :attempted
8
- attribute :closed
9
11
  attribute :currency
10
- attribute :date
11
- attribute :forgiven
12
+ attribute :due_date
12
13
  attribute :paid
13
14
  attribute :period_end
14
15
  attribute :period_start
16
+ attribute :status
15
17
  attribute :subtotal
16
18
  attribute :total
17
- attribute :application_fee
18
19
  attribute :tax
19
- attribute :tax_percent
20
20
 
21
21
  has_one :customer
22
22
 
@@ -26,9 +26,7 @@ module ForestLiana
26
26
 
27
27
  user = ForestLiana::AuthorizationGetter.authenticate(
28
28
  rendering_id,
29
- true,
30
29
  { :forest_token => access_token_instance.instance_variable_get(:@access_token) },
31
- nil,
32
30
  )
33
31
 
34
32
  return ForestLiana::Token.create_token(user, rendering_id)
@@ -1,23 +1,12 @@
1
1
  module ForestLiana
2
2
  class AuthorizationGetter
3
- def self.authenticate(rendering_id, use_google_authentication, auth_data, two_factor_registration)
3
+ def self.authenticate(rendering_id, auth_data)
4
4
  begin
5
5
  route = "/liana/v2/renderings/#{rendering_id.to_s}/authorization"
6
-
7
- if !use_google_authentication.nil?
8
- headers = { 'forest-token' => auth_data[:forest_token] }
9
- elsif !auth_data[:email].nil?
10
- headers = { 'email' => auth_data[:email], 'password' => auth_data[:password] }
11
- end
12
-
13
- query_parameters = {}
14
-
15
- unless two_factor_registration.nil?
16
- query_parameters['two-factor-registration'] = true
17
- end
6
+ headers = { 'forest-token' => auth_data[:forest_token] }
18
7
 
19
8
  response = ForestLiana::ForestApiRequester
20
- .get(route, query: query_parameters, headers: headers)
9
+ .get(route, query: {}, headers: headers)
21
10
 
22
11
  if response.code.to_i == 200
23
12
  body = JSON.parse(response.body, :symbolize_names => false)
@@ -25,15 +14,28 @@ module ForestLiana
25
14
  user['id'] = body['data']['id']
26
15
  user
27
16
  else
28
- unless use_google_authentication.nil?
29
- raise "Cannot authorize the user using this google account. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
30
- else
31
- raise "Cannot authorize the user using this email/password. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
32
- end
17
+ raise generate_authentication_error response
33
18
  end
34
- rescue
35
- raise ForestLiana::Errors::HTTP401Error
36
19
  end
37
20
  end
21
+
22
+ private
23
+ def self.generate_authentication_error(error)
24
+ case error[:message]
25
+ when ForestLiana::MESSAGES[:SERVER_TRANSACTION][:SECRET_AND_RENDERINGID_INCONSISTENT]
26
+ return ForestLiana::Errors::InconsistentSecretAndRenderingError.new()
27
+ when ForestLiana::MESSAGES[:SERVER_TRANSACTION][:SECRET_NOT_FOUND]
28
+ return ForestLiana::Errors::SecretNotFoundError.new()
29
+ else
30
+ end
31
+
32
+ serverError = error[:jse_cause][:response][:body][:errors][0] || nil
33
+
34
+ if !serverError.nil? && serverError[:name] == ForestLiana::MESSAGES[:SERVER_TRANSACTION][:names][:TWO_FACTOR_AUTHENTICATION_REQUIRED]
35
+ return ForestLiana::Errors::TwoFactorAuthenticationRequiredError.new()
36
+ end
37
+
38
+ return StandardError.new(error)
39
+ end
38
40
  end
39
41
  end
@@ -15,7 +15,7 @@ module ForestLiana
15
15
  registration_endpoint: configuration['registration_endpoint']
16
16
  })
17
17
  else
18
- client_credentials['client_id'] = ForestLiana.forest_client_id
18
+ client_credentials = { 'client_id' => ForestLiana.forest_client_id }
19
19
  end
20
20
 
21
21
  client_data = { :client_id => client_credentials['client_id'], :issuer => configuration['issuer'] }
@@ -53,7 +53,7 @@ module ForestLiana
53
53
  end
54
54
 
55
55
  def has_strong_parameter
56
- Rails::VERSION::MAJOR > 5 || @resource.instance_method(:update_attributes!).arity == 1
56
+ Rails::VERSION::MAJOR > 5 || @resource.instance_method(:update!).arity == 1
57
57
  end
58
58
  end
59
59
  end
@@ -14,9 +14,9 @@ module ForestLiana
14
14
  @record = @resource.find(@params[:id])
15
15
 
16
16
  if has_strong_parameter
17
- @record.update_attributes(resource_params)
17
+ @record.update(resource_params)
18
18
  else
19
- @record.update_attributes(resource_params, without_protection: true)
19
+ @record.update(resource_params, without_protection: true)
20
20
  end
21
21
  rescue ActiveRecord::StatementInvalid => exception
22
22
  # NOTICE: SQL request cannot be executed properly
@@ -33,7 +33,7 @@ module ForestLiana
33
33
  end
34
34
 
35
35
  def has_strong_parameter
36
- Rails::VERSION::MAJOR > 5 || @resource.instance_method(:update_attributes!).arity == 1
36
+ Rails::VERSION::MAJOR > 5 || @resource.instance_method(:update!).arity == 1
37
37
  end
38
38
  end
39
39
  end
@@ -2,9 +2,14 @@ module ForestLiana
2
2
  class SchemaUtils
3
3
 
4
4
  def self.associations(active_record_class)
5
- active_record_class
6
- .reflect_on_all_associations
7
- .select { |association| !polymorphic?(association) && !is_active_type?(association.klass) }
5
+ active_record_class.reflect_on_all_associations.select do |association|
6
+ begin
7
+ !polymorphic?(association) && !is_active_type?(association.klass)
8
+ rescue
9
+ FOREST_LOGGER.warn "Unknown association #{association.name} on class #{active_record_class.name}"
10
+ false
11
+ end
12
+ end
8
13
  end
9
14
 
10
15
  def self.one_associations(active_record_class)
@@ -11,7 +11,7 @@ module ForestLiana
11
11
  query = {}
12
12
  @record = ::Stripe::Invoice.retrieve(@params[:invoice_id])
13
13
 
14
- @record.date = Time.at(@record.date).to_datetime
14
+ @record.due_date = Time.at(@record.due_date).to_datetime unless @record.due_date.nil?
15
15
  @record.period_start = Time.at(@record.period_start).to_datetime
16
16
  @record.period_end = Time.at(@record.period_end).to_datetime
17
17
  @record.subtotal /= 100.00
@@ -32,7 +32,7 @@ module ForestLiana
32
32
  end
33
33
 
34
34
  @records = @invoices.data.map do |d|
35
- d.date = Time.at(d.date).to_datetime
35
+ d.date = Time.at(d.created).to_datetime
36
36
  d.period_start = Time.at(d.period_start).to_datetime
37
37
  d.period_end = Time.at(d.period_end).to_datetime
38
38
  d.subtotal /= 100.00
@@ -12,7 +12,7 @@ module ForestLiana
12
12
  customer = resource[field]
13
13
 
14
14
  @record = ::Stripe::Customer
15
- .retrieve(customer)
15
+ .retrieve({ id: customer, expand: ['sources'] })
16
16
  .sources.retrieve(@params[:objectId])
17
17
 
18
18
  query = {}
@@ -32,7 +32,7 @@ module ForestLiana
32
32
 
33
33
  def fetch_bank_accounts(customer, params)
34
34
  begin
35
- @cards = ::Stripe::Customer.retrieve(customer).sources.list(params)
35
+ @cards = ::Stripe::Customer.retrieve({ id: customer, expand: ['sources'] }).sources.list(params)
36
36
  if @cards.blank?
37
37
  @records = []
38
38
  return
@@ -15,6 +15,9 @@ module ForestLiana
15
15
  INVALID_RENDERING_ID: "The parameter renderingId is not valid",
16
16
  REGISTRATION_FAILED: "The registration to the authentication API failed, response: ",
17
17
  OIDC_CONFIGURATION_RETRIEVAL_FAILED: "Failed to retrieve the provider's configuration.",
18
+ names: {
19
+ TWO_FACTOR_AUTHENTICATION_REQUIRED: 'TwoFactorAuthenticationRequiredForbiddenError',
20
+ }
18
21
  }
19
22
  }
20
23
  end
@@ -14,12 +14,13 @@ module ForestLiana
14
14
  end
15
15
 
16
16
  class ExpectedError < StandardError
17
- attr_reader :error_code, :status, :message
17
+ attr_reader :error_code, :status, :message, :name
18
18
 
19
- def initialize(error_code, status, message)
19
+ def initialize(error_code, status, message, name = nil)
20
20
  @error_code = error_code
21
21
  @status = status
22
22
  @message = message
23
+ @name = name
23
24
  end
24
25
 
25
26
  def display_error
@@ -45,6 +46,24 @@ module ForestLiana
45
46
  end
46
47
  end
47
48
 
49
+ class InconsistentSecretAndRenderingError < ExpectedError
50
+ def initialize(message=ForestLiana::MESSAGES[:SERVER_TRANSACTION][:SECRET_AND_RENDERINGID_INCONSISTENT])
51
+ super(500, :internal_server_error, message, 'InconsistentSecretAndRenderingError')
52
+ end
53
+ end
54
+
55
+ class SecretNotFoundError < ExpectedError
56
+ def initialize(message=ForestLiana::MESSAGES[:SERVER_TRANSACTION][:SECRET_NOT_FOUND])
57
+ super(500, :internal_server_error, message, 'SecretNotFoundError')
58
+ end
59
+ end
60
+
61
+ class TwoFactorAuthenticationRequiredError < ExpectedError
62
+ def initialize(message='Two factor authentication required')
63
+ super(403, :forbidden, message, 'TwoFactorAuthenticationRequiredError')
64
+ end
65
+ end
66
+
48
67
  class ExceptionHelper
49
68
  def self.recursively_print(error, margin: '', is_error: false)
50
69
  logger = is_error ?
data/config/routes.rb CHANGED
@@ -9,10 +9,6 @@ ForestLiana::Engine.routes.draw do
9
9
  get 'authentication/callback' => 'authentication#authentication_callback'
10
10
  post 'authentication/logout' => 'authentication#logout'
11
11
 
12
- # Session
13
- post 'sessions' => 'sessions#create_with_password'
14
- post 'sessions-google' => 'sessions#create_with_google'
15
-
16
12
  # Associations
17
13
  get ':collection/:id/relationships/:association_name' => 'associations#index'
18
14
  get ':collection/:id/relationships/:association_name/count' => 'associations#count'
@@ -18,6 +18,13 @@ module ForestLiana
18
18
  ForestLiana.auth_secret = ForestLiana.auth_key
19
19
  end
20
20
 
21
+ unless Rails.application.config.action_controller.perform_caching || Rails.env.test? || ForestLiana.forest_client_id
22
+ FOREST_LOGGER.error "You need to enable caching on your environment to use Forest Admin.\n" \
23
+ "For a development environment, run: `rails dev:cache`\n" \
24
+ "Or setup a static forest_client_id by following this part of the documentation:\n" \
25
+ "https://docs.forestadmin.com/documentation/how-tos/maintain/upgrade-notes-rails/upgrade-to-v6#setup-a-static-clientid"
26
+ end
27
+
21
28
  fetch_models
22
29
  check_integrations_setup
23
30
  namespace_duplicated_models
@@ -430,19 +437,19 @@ module ForestLiana
430
437
  fields: [
431
438
  { field: :id, type: 'String', is_filterable: false },
432
439
  { field: :amount_due, type: 'Number', is_filterable: false },
440
+ { field: :amount_paid, type: 'Number', is_filterable: false },
441
+ { field: :amount_remaining, type: 'Number', is_filterable: false },
442
+ { field: :application_fee_amount, type: 'Number', is_filterable: false },
433
443
  { field: :attempt_count, type: 'Number', is_filterable: false },
434
444
  { field: :attempted, type: 'Boolean', is_filterable: false },
435
- { field: :closed, type: 'Boolean', is_filterable: false },
436
445
  { field: :currency, type: 'String', is_filterable: false },
437
- { field: :date, type: 'Date', is_filterable: false },
438
- { field: :forgiven, type: 'Boolean', is_filterable: false },
446
+ { field: :due_date, type: 'Date', is_filterable: false },
439
447
  { field: :period_start, type: 'Date', is_filterable: false },
440
448
  { field: :period_end, type: 'Date', is_filterable: false },
449
+ { field: :status, type: 'String', enums: ['draft', 'open', 'paid', 'uncollectible', 'void'], is_filterable: false },
441
450
  { field: :subtotal, type: 'Number', is_filterable: false },
442
451
  { field: :total, type: 'Number', is_filterable: false },
443
- { field: :application_fee, type: 'Number', is_filterable: false },
444
452
  { field: :tax, type: 'Number', is_filterable: false },
445
- { field: :tax_percent, type: 'Number', is_filterable: false },
446
453
  {
447
454
  field: :customer,
448
455
  type: 'String',
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "6.0.0-beta.4"
2
+ VERSION = "6.0.4"
3
3
  end
@@ -5,6 +5,7 @@ module ForestLiana
5
5
  desc 'Forest Rails Liana installation generator'
6
6
 
7
7
  argument :env_secret, type: :string, required: true, desc: 'required', banner: 'env_secret'
8
+ argument :application_url, type: :string, required: false, desc: 'optional', banner: 'application_url', default: 'http://localhost:3000'
8
9
 
9
10
  def install
10
11
  if ForestLiana.env_secret.present?
@@ -27,35 +28,42 @@ module ForestLiana
27
28
  if File.exist? 'config/secrets.yml'
28
29
  inject_into_file 'config/secrets.yml', after: "development:\n" do
29
30
  " forest_env_secret: #{env_secret}\n" +
30
- " forest_auth_secret: #{auth_secret}\n"
31
+ " forest_auth_secret: #{auth_secret}\n" +
32
+ " forest_application_url: #{application_url}\n"
31
33
  end
32
34
 
33
35
  inject_into_file 'config/secrets.yml', after: "staging:\n", force: true do
34
36
  " forest_env_secret: <%= ENV[\"FOREST_ENV_SECRET\"] %>\n" +
35
- " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n"
37
+ " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n" +
38
+ " forest_application_url: <%= ENV[\"FOREST_APPLICATION_URL\"] %>\n"
36
39
  end
37
40
 
38
41
  inject_into_file 'config/secrets.yml', after: "production:\n", force: true do
39
42
  " forest_env_secret: <%= ENV[\"FOREST_ENV_SECRET\"] %>\n" +
40
- " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n"
43
+ " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n" +
44
+ " forest_application_url: <%= ENV[\"FOREST_APPLICATION_URL\"] %>\n"
41
45
  end
42
46
  else
43
47
  create_file 'config/secrets.yml' do
44
48
  "development:\n" +
45
49
  " forest_env_secret: #{env_secret}\n" +
46
50
  " forest_auth_secret: #{auth_secret}\n" +
51
+ " forest_application_url: #{application_url}\n" +
47
52
  "staging:\n" +
48
53
  " forest_env_secret: <%= ENV[\"FOREST_ENV_SECRET\"] %>\n" +
49
54
  " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n" +
55
+ " forest_application_url: <%= ENV[\"FOREST_APPLICATION_URL\"] %>\n" +
50
56
  "production:\n" +
51
57
  " forest_env_secret: <%= ENV[\"FOREST_ENV_SECRET\"] %>\n" +
52
- " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n"
58
+ " forest_auth_secret: <%= ENV[\"FOREST_AUTH_SECRET\"] %>\n" +
59
+ " forest_application_url: <%= ENV[\"FOREST_APPLICATION_URL\"] %>\n"
53
60
  end
54
61
  end
55
62
 
56
63
  initializer 'forest_liana.rb' do
57
64
  "ForestLiana.env_secret = Rails.application.secrets.forest_env_secret" +
58
- "\nForestLiana.auth_secret = Rails.application.secrets.forest_auth_secret"
65
+ "\nForestLiana.auth_secret = Rails.application.secrets.forest_auth_secret" +
66
+ "\nForestLiana.application_url = Rails.application.secrets.forest_application_url"
59
67
  end
60
68
  end
61
69
  end