model_driven_api 3.12.2 → 3.13.1

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
  SHA256:
3
- metadata.gz: 64d6dc96e02bf63e76a9948d41e1f7203433f9be2f3bb0a2abe16f19179351ad
4
- data.tar.gz: 47bf8c07a5e6381bcabb15a150faae8bd08ae02000db9798c091b2a916e628df
3
+ metadata.gz: 9f71f26da0653883d6bcf4aa2382a9d6d9d51104e0f5f183f79abdf6da8cf242
4
+ data.tar.gz: 1cb5c008dfe9bbc44dbec5ec2bcf19670cd2c469239b0416b3a27d3ee4387387
5
5
  SHA512:
6
- metadata.gz: 6d7039ac2e39aa75800db5a1ecdabd121a7465c8ffdc966ab88dac7961e53bd00eff37c0b8794b5d6ecca68f5a8d9eed3e4a3a8dd9d800a084301e12a7e321b0
7
- data.tar.gz: 37566ee9050bfe09de0f3d784d0c6b4b6aebb39ec365ceb8c7f59e045b4865d3bf66518ebd1a393c38a8a04db575a2bd3ee2c106df84551f17e79c2b27c33780
6
+ metadata.gz: 4407253720ea30dbd1c209fe9997461564c28035ab24b7e97bb60ccdfb1614000f8405adc94f97666d06d1799a11a5aee5f5c2e498d105a8c2c88f56c32bc007
7
+ data.tar.gz: 734ca5f9bc6745ebcd0ef1ef382048d3d35205e1a692bdcc6fa41ed6c2480653e590bc25bf76ad29b32b9aae27392d065ecbb45294a9da8de87e8be402114ade
@@ -0,0 +1,28 @@
1
+ {
2
+ "ignored_warnings": [
3
+ {
4
+ "warning_type": "Remote Code Execution",
5
+ "warning_code": 24,
6
+ "fingerprint": "3a0e243d6afeb081594823f1d9102c5c6f9235211b4d2bcf3121eb57e91b65b9",
7
+ "check_name": "UnsafeReflection",
8
+ "message": "Unsafe reflection method `constantize` called on parameter value",
9
+ "file": "app/controllers/api/v2/application_controller.rb",
10
+ "line": 146,
11
+ "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/",
12
+ "code": "\"Endpoints::#{params[:ctrl].to_s.classify}\".constantize",
13
+ "render_path": null,
14
+ "location": {
15
+ "type": "method",
16
+ "class": "Api::V2::ApplicationController",
17
+ "method": "public_custom_action?"
18
+ },
19
+ "user_input": "params[:ctrl].to_s.classify",
20
+ "confidence": "High",
21
+ "cwe_id": [
22
+ 470
23
+ ],
24
+ "note": "False positive (host Odoo #5884). The class name is always prefixed with Endpoints:: and ActiveSupport's constantize never falls back to a top-level constant (Endpoints::String raises NameError), so only an existing Endpoints::* class can be loaded; nothing is instantiated here. Guarded by the host's test/integration/api_reflection_test.rb."
25
+ }
26
+ ],
27
+ "brakeman_version": "8.1.0"
28
+ }
@@ -4,17 +4,26 @@ module Api
4
4
 
5
5
  # Resolve model from params, controller_path, or controller_name.
6
6
  # Returns nil when no class can be resolved (info/utility controllers use this path).
7
- # Raises NotFound when a class is resolved but is not an ActiveRecord model (and not TestApi).
7
+ # Raises NotFound when a class is resolved but is not a concrete ActiveRecord model (and not TestApi).
8
+ #
9
+ # The names come from the request path, so a resolved class is only ever inspected, never
10
+ # instantiated: checking a model with `.new` used to run the constructor of any class the
11
+ # path named (GET /api/v2/tempfiles created a file on the server).
8
12
  def self.resolve(params, controller_path, controller_name)
9
- model = (params[:ctrl].classify.constantize rescue
10
- params[:path].split("/").first.classify.constantize rescue
11
- controller_path.classify.constantize rescue
12
- controller_name.classify.constantize rescue
13
- nil)
14
- if model && model != TestApi
15
- raise NotFound unless (model.new.is_a?(ActiveRecord::Base) rescue false)
16
- end
13
+ model = [
14
+ params[:ctrl],
15
+ params[:path].to_s.split("/").first,
16
+ controller_path,
17
+ controller_name
18
+ ].lazy.filter_map { |name| name.to_s.classify.safe_constantize }.first
19
+ raise NotFound if model && model != TestApi && !model?(model)
20
+
17
21
  model
18
22
  end
23
+
24
+ def self.model?(klass)
25
+ klass.is_a?(Class) && klass < ActiveRecord::Base && !klass.abstract_class?
26
+ end
27
+ private_class_method :model?
19
28
  end
20
29
  end
@@ -1,21 +1,44 @@
1
1
  class JsonWebToken
2
2
  class << self
3
3
  def encode(payload, expiry = ENV.fetch('SESSION_TIMEOUT_IN_MINUTES', 31).to_i.minutes.from_now.to_i)
4
- result = ::JWT.encode(payload.merge(exp: expiry), ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])
4
+ result = ::JWT.encode(payload.merge(exp: expiry), signing_key)
5
5
  # Store the created token into the DB for later checks if is invalid
6
6
  # In a public environment management, without login, it has no interest, so I don't pollute the DB
7
7
  UsedToken.find_or_create_by(token: result, user_id: payload[:user_id]) if ENV["ALLOW_MULTISESSIONS"] == "false"
8
8
  result
9
9
  end
10
-
10
+
11
11
  def decode(token)
12
- # Check if the passed token is present and valid into the UsedToken
12
+ # Check if the passed token is present and valid into the UsedToken
13
13
  raise "Token is invalidated by new login" if !UsedToken.exists?(token: token, is_valid: true) && ENV["ALLOW_MULTISESSIONS"] == "false"
14
- body = ::JWT.decode(token, ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])[0]
14
+ body = verified_payload(token)
15
15
  Rails.logger.debug "JsonWebToken: decode -> body -> #{body}"
16
16
  ::HashWithIndifferentAccess.new body
17
17
  # rescue
18
18
  # nil
19
19
  end
20
+
21
+ private
22
+
23
+ def signing_key
24
+ ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"]
25
+ end
26
+
27
+ # After a SECRET_KEY_BASE rotation, a token signed with the previous key is still accepted until
28
+ # SECRET_KEY_BASE_PREVIOUS_EXPIRES_AT (ISO 8601), so app users never log in again: every authenticated
29
+ # response renews the token with the current key. The previous key may be public, so it is never
30
+ # trusted without an expiry, and only a bad signature falls back to it (an expired token stays expired).
31
+ def verified_payload(token)
32
+ ::JWT.decode(token, signing_key)[0]
33
+ rescue ::JWT::VerificationError
34
+ previous = previous_signing_key or raise
35
+ ::JWT.decode(token, previous)[0]
36
+ end
37
+
38
+ def previous_signing_key
39
+ key = ENV["SECRET_KEY_BASE_PREVIOUS"].presence or return
40
+ expires_at = Time.iso8601(ENV["SECRET_KEY_BASE_PREVIOUS_EXPIRES_AT"].to_s) rescue nil
41
+ key if expires_at && Time.current < expires_at
42
+ end
20
43
  end
21
- end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.12.2".freeze
2
+ VERSION = "3.13.1".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.12.2
4
+ version: 3.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -192,6 +192,7 @@ files:
192
192
  - app/models/endpoints/test_api.rb
193
193
  - app/models/test_api.rb
194
194
  - app/models/used_token.rb
195
+ - config/brakeman.ignore
195
196
  - config/initializers/after_initialize_for_model_driven_api.rb
196
197
  - config/initializers/auto_include_json.rb
197
198
  - config/initializers/cors_api_thecore.rb