macaw_framework 1.2.4 → 1.2.6

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: 17a2ff330e9f546997fcbbcbbc8bc0e08af19c08980056999b60f3307aaf898d
4
- data.tar.gz: 2af901040509fcd7feecaa5bc9cc762967f9530cd6277b6fc0d4ac1fe5a3bb20
3
+ metadata.gz: 2353cd5ffe4a264a2eacab71fc2de65c5c4a6b2cd925666529e36b8bdde0ada6
4
+ data.tar.gz: 4855cf3e106380f6c6718058001cd6cbd888a6c0f1927f833da3bc97cf648785
5
5
  SHA512:
6
- metadata.gz: 07127b57e24d02a95a9bc444bc444bb3ce44d9f80c6fc9f08b5155c88a3355bd20ee93ae5831f028596b114a4cb200c31bd5247bff8ba043d65f844c426a6bd7
7
- data.tar.gz: 4ca1a0f63ea1feb4de1b712f63b786286b791bc9df255011b4ef7acf5a740076c65821b13311bf5c3741bfde945ba0f83dae8ad0073bb56dab6962295b1dc7b5
6
+ metadata.gz: 85f5262044b8b3029ad507d501655f72ccd022630b34fc72a5606fe993d223a7e83b245b2ee858adbedf1723717d2d050e1276badf2a353d89612d2c544253a4
7
+ data.tar.gz: 31b67715e3f2b2c792262d53f6488ae0b8df38c2c1f700fd5590b45927f49dc47fa8c84db884d2ec560b0e981582091b3f38a0f8af06bbddb26efd26f33e2e58
data/CHANGELOG.md CHANGED
@@ -128,3 +128,11 @@
128
128
 
129
129
  - Fixing small bug on lof during endpoint declaration
130
130
  - Disclosing security issue on session storage
131
+
132
+ ## [1.2.5]
133
+
134
+ - Improvements to cache usability
135
+
136
+ ## [1.2.6]
137
+
138
+ - Improving session strategy and fixing vulnerabilities on it.
data/README.md CHANGED
@@ -104,7 +104,7 @@ m.start!
104
104
  ### Caching: Improve performance by caching responses and configuring cache invalidation
105
105
 
106
106
  ```ruby
107
- m.get('/cached_data', cache: true) do |context|
107
+ m.get('/cached_data', cache: ["header_to_cache", "query_param_to_cache"]) do |context|
108
108
  # Retrieve data
109
109
  end
110
110
  ```
@@ -113,6 +113,15 @@ end
113
113
 
114
114
  ### Session management: Handle user sessions with server-side in-memory storage
115
115
 
116
+ Session will only be enabled if it's configurations exists in the `application.json` file.
117
+ The session mechanism works by recovering the Session ID from a client sent header. The default
118
+ header is `X-Session-ID`, but it can be changed in the `application.json` file.
119
+
120
+ This header will be sent back to the user on every response if Session is enabled. Also, the
121
+ session ID will be automatically generated and sent to a client if this client does not provide
122
+ a session id in the HTTP request. In the case of the client sending an ID of an expired session
123
+ the framework will return a new session with a new ID.
124
+
116
125
  ```ruby
117
126
  m.get('/login') do |context|
118
127
  # Authenticate user
@@ -129,8 +138,6 @@ m.get('/dashboard') do |context|
129
138
  end
130
139
  ```
131
140
 
132
- **Caution: This feature is vulnerable to IP spoofing and may disrupt sessions on devices sharing the same network (e.g., Wi-Fi).**
133
-
134
141
  ### Configuration: Customize various aspects of the framework through the application.json configuration file, such as rate limiting, SSL support, and Prometheus integration
135
142
 
136
143
  ```json
@@ -140,11 +147,7 @@ end
140
147
  "bind": "localhost",
141
148
  "threads": 200,
142
149
  "cache": {
143
- "cache_invalidation": 3600,
144
- "ignore_headers": [
145
- "header-to-be-ignored-from-caching-strategy",
146
- "another-header-to-be-ignored-from-caching-strategy"
147
- ]
150
+ "cache_invalidation": 3600
148
151
  },
149
152
  "prometheus": {
150
153
  "endpoint": "/metrics"
@@ -159,6 +162,10 @@ end
159
162
  "key_type": "EC",
160
163
  "cert_file_name": "path/to/cert/file/file.crt",
161
164
  "key_file_name": "path/to/cert/key/file.key"
165
+ },
166
+ "session": {
167
+ "secure_header": "X-Session-ID",
168
+ "invalidation_time": 3600
162
169
  }
163
170
  }
164
171
  }
@@ -6,7 +6,7 @@ module CacheAspect
6
6
  def call_endpoint(cache, *args)
7
7
  return super(*args) unless !cache[:cache].nil? && cache[:endpoints_to_cache]&.include?(args[0])
8
8
 
9
- cache_filtered_name = cache_name_filter(args[1], cache[:ignored_headers])
9
+ cache_filtered_name = cache_name_filter(args[1], cache[:cached_methods][args[0]])
10
10
 
11
11
  cache[:cache].mutex.synchronize do
12
12
  return cache[:cache].cache[cache_filtered_name][0] unless cache[:cache].cache[cache_filtered_name].nil?
@@ -19,9 +19,10 @@ module CacheAspect
19
19
 
20
20
  private
21
21
 
22
- def cache_name_filter(client_data, ignored_headers)
23
- filtered_headers = client_data[:headers].filter { |key, _value| !ignored_headers&.include?(key) }
24
- [{ body: client_data[:body], params: client_data[:params], headers: filtered_headers }].to_s.to_sym
22
+ def cache_name_filter(client_data, cached_methods_params)
23
+ filtered_headers = client_data[:headers]&.filter { |key, _value| cached_methods_params&.include?(key) }
24
+ filtered_params = client_data[:params]&.filter { |key, _value| cached_methods_params&.include?(key) }
25
+ [{ params: filtered_params, headers: filtered_headers }].to_s.to_sym
25
26
  end
26
27
 
27
28
  def should_cache_response?(status)
@@ -8,6 +8,7 @@ require_relative "../../utils/supported_ssl_versions"
8
8
  require_relative "../../aspects/prometheus_aspect"
9
9
  require_relative "../../aspects/logging_aspect"
10
10
  require_relative "../../aspects/cache_aspect"
11
+ require "securerandom"
11
12
 
12
13
  ##
13
14
  # Base module for Server classes. It contains
@@ -21,14 +22,14 @@ module ServerBase
21
22
 
22
23
  private
23
24
 
24
- def call_endpoint(name, client_data, client_ip)
25
+ def call_endpoint(name, client_data, session_id, _client_ip)
25
26
  @macaw.send(
26
27
  name.to_sym,
27
28
  {
28
29
  headers: client_data[:headers],
29
30
  body: client_data[:body],
30
31
  params: client_data[:params],
31
- client: @session[client_ip][0]
32
+ client: @session[session_id][0]
32
33
  }
33
34
  )
34
35
  end
@@ -42,12 +43,14 @@ module ServerBase
42
43
  raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
43
44
  raise TooManyRequestsError unless @rate_limit.nil? || @rate_limit.allow?(client.peeraddr[3])
44
45
 
45
- declare_client_session(client)
46
46
  client_data = get_client_data(body, headers, parameters)
47
+ session_id = declare_client_session(client_data[:headers], @macaw.secure_header) if @macaw.session
47
48
 
48
49
  @macaw_log&.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
49
50
  message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
50
- method_name, client_data, client.peeraddr[3])
51
+ method_name, client_data, session_id, client.peeraddr[3])
52
+ response_headers ||= {}
53
+ response_headers[@macaw.secure_header] = session_id if @macaw.session
51
54
  status ||= 200
52
55
  message ||= nil
53
56
  response_headers ||= nil
@@ -69,9 +72,11 @@ module ServerBase
69
72
  end
70
73
  end
71
74
 
72
- def declare_client_session(client)
73
- @session[client.peeraddr[3]] ||= [{}, Time.now]
74
- @session[client.peeraddr[3]] = [{}, Time.now] if @session[client.peeraddr[3]][0].nil?
75
+ def declare_client_session(headers, secure_header_name)
76
+ session_id = headers[secure_header_name] || SecureRandom.uuid
77
+ session_id = SecureRandom.uuid if @session[session_id].nil?
78
+ @session[session_id] ||= [{}, Time.now]
79
+ session_id
75
80
  end
76
81
 
77
82
  def set_rate_limiting
@@ -83,12 +88,6 @@ module ServerBase
83
88
  )
84
89
  end
85
90
 
86
- def set_cache_ignored_h
87
- return unless @macaw.config&.dig("macaw", "cache", "ignore_headers")
88
-
89
- @macaw.config["macaw"]["cache"]["ignore_headers"] || []
90
- end
91
-
92
91
  def set_ssl
93
92
  ssl_config = @macaw.config["macaw"]["ssl"] if @macaw.config&.dig("macaw", "ssl")
94
93
  ssl_config ||= nil
@@ -116,7 +115,7 @@ module ServerBase
116
115
  end
117
116
 
118
117
  def set_session
119
- @session = {}
118
+ @session ||= {}
120
119
  inv = if @macaw.config&.dig("macaw", "session", "invalidation_time")
121
120
  MemoryInvalidationMiddleware.new(@macaw.config["macaw"]["session"]["invalidation_time"])
122
121
  else
@@ -128,7 +127,7 @@ module ServerBase
128
127
  def set_features
129
128
  @is_shutting_down = false
130
129
  set_rate_limiting
131
- set_session
130
+ set_session if @macaw.session
132
131
  set_ssl
133
132
  end
134
133
  end
@@ -32,11 +32,13 @@ class ThreadServer
32
32
  @macaw_log = macaw.macaw_log
33
33
  @num_threads = macaw.threads
34
34
  @work_queue = Queue.new
35
- ignored_headers = set_cache_ignored_h
36
35
  set_features
37
36
  @rate_limit ||= nil
38
- ignored_headers ||= nil
39
- @cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
37
+ @cache = {
38
+ cache: cache,
39
+ endpoints_to_cache: endpoints_to_cache || [],
40
+ cached_methods: macaw.cached_methods
41
+ }
40
42
  @prometheus = prometheus
41
43
  @prometheus_middleware = prometheus_mw
42
44
  @workers = []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "1.2.4"
4
+ VERSION = "1.2.6"
5
5
  end
@@ -19,30 +19,16 @@ module MacawFramework
19
19
  # Class responsible for creating endpoints and
20
20
  # starting the web server.
21
21
  class Macaw
22
- ##
23
- # Array containing the routes defined in the application
24
- attr_reader :routes, :macaw_log, :config, :jobs
22
+ attr_reader :routes, :macaw_log, :config, :jobs, :cached_methods, :secure_header, :session
25
23
  attr_accessor :port, :bind, :threads
26
24
 
27
25
  ##
26
+ # Initialize Macaw Class
28
27
  # @param {Logger} custom_log
28
+ # @param {ThreadServer} server
29
+ # @param {String?} dir
29
30
  def initialize(custom_log: Logger.new($stdout), server: ThreadServer, dir: nil)
30
- begin
31
- @routes = []
32
- @macaw_log ||= custom_log
33
- @config = JSON.parse(File.read("application.json"))
34
- @port = @config["macaw"]["port"] || 8080
35
- @bind = @config["macaw"]["bind"] || "localhost"
36
- @threads = @config["macaw"]["threads"] || 200
37
- unless @config["macaw"]["cache"].nil?
38
- @cache = MemoryInvalidationMiddleware.new(@config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
39
- end
40
- @prometheus = Prometheus::Client::Registry.new if @config["macaw"]["prometheus"]
41
- @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
42
- @prometheus_middleware.configure_prometheus(@prometheus, @config, self) if @config["macaw"]["prometheus"]
43
- rescue StandardError => e
44
- @macaw_log&.warn(e.message)
45
- end
31
+ apply_options(custom_log)
46
32
  create_endpoint_public_files(dir)
47
33
  @port ||= 8080
48
34
  @bind ||= "localhost"
@@ -65,7 +51,7 @@ module MacawFramework
65
51
  # macaw.get("/hello") do |context|
66
52
  # return "Hello World!", 200, { "Content-Type" => "text/plain" }
67
53
  # end
68
- def get(path, cache: false, &block)
54
+ def get(path, cache: [], &block)
69
55
  map_new_endpoint("get", cache, path, &block)
70
56
  end
71
57
 
@@ -81,7 +67,7 @@ module MacawFramework
81
67
  # macaw.post("/hello") do |context|
82
68
  # return "Hello World!", 200, { "Content-Type" => "text/plain" }
83
69
  # end
84
- def post(path, cache: false, &block)
70
+ def post(path, cache: [], &block)
85
71
  map_new_endpoint("post", cache, path, &block)
86
72
  end
87
73
 
@@ -96,7 +82,7 @@ module MacawFramework
96
82
  # macaw.put("/hello") do |context|
97
83
  # return "Hello World!", 200, { "Content-Type" => "text/plain" }
98
84
  # end
99
- def put(path, cache: false, &block)
85
+ def put(path, cache: [], &block)
100
86
  map_new_endpoint("put", cache, path, &block)
101
87
  end
102
88
 
@@ -111,7 +97,7 @@ module MacawFramework
111
97
  # macaw.patch("/hello") do |context|
112
98
  # return "Hello World!", 200, { "Content-Type" => "text/plain" }
113
99
  # end
114
- def patch(path, cache: false, &block)
100
+ def patch(path, cache: [], &block)
115
101
  map_new_endpoint("patch", cache, path, &block)
116
102
  end
117
103
 
@@ -126,7 +112,7 @@ module MacawFramework
126
112
  # macaw.delete("/hello") do |context|
127
113
  # return "Hello World!", 200, { "Content-Type" => "text/plain" }
128
114
  # end
129
- def delete(path, cache: false, &block)
115
+ def delete(path, cache: [], &block)
130
116
  map_new_endpoint("delete", cache, path, &block)
131
117
  end
132
118
 
@@ -191,12 +177,36 @@ module MacawFramework
191
177
 
192
178
  private
193
179
 
180
+ def apply_options(custom_log)
181
+ @routes = []
182
+ @cached_methods = {}
183
+ @macaw_log ||= custom_log
184
+ @config = JSON.parse(File.read("application.json"))
185
+ @port = @config["macaw"]["port"] || 8080
186
+ @bind = @config["macaw"]["bind"] || "localhost"
187
+ @session = false
188
+ unless @config["macaw"]["session"].nil?
189
+ @session = true
190
+ @secure_header = @config["macaw"]["session"]["secure_header"] || "X-Session-ID"
191
+ end
192
+ @threads = @config["macaw"]["threads"] || 200
193
+ unless @config["macaw"]["cache"].nil?
194
+ @cache = MemoryInvalidationMiddleware.new(@config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
195
+ end
196
+ @prometheus = Prometheus::Client::Registry.new if @config["macaw"]["prometheus"]
197
+ @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
198
+ @prometheus_middleware.configure_prometheus(@prometheus, @config, self) if @config["macaw"]["prometheus"]
199
+ rescue StandardError => e
200
+ @macaw_log&.warn(e.message)
201
+ end
202
+
194
203
  def server_loop(server)
195
204
  server.run
196
205
  end
197
206
 
198
207
  def map_new_endpoint(prefix, cache, path, &block)
199
- @endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" if cache
208
+ @endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" unless cache.empty?
209
+ @cached_methods["#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}"] = cache unless cache.empty?
200
210
  path_clean = RequestDataFiltering.extract_path(path)
201
211
  slash = path[0] == "/" ? "" : "/"
202
212
  @macaw_log&.info("Defining #{prefix.upcase} endpoint at #{slash}#{path}")
@@ -2,6 +2,7 @@ module MacawFramework
2
2
  class Macaw
3
3
  @bind: String
4
4
  @cache: untyped
5
+ @cached_methods: Hash[String, Array[String]]
5
6
  @config: Hash[String, untyped]
6
7
  @cron_runner: CronRunner
7
8
  @endpoints_to_cache: Array[String]
@@ -9,8 +10,11 @@ module MacawFramework
9
10
 
10
11
  @prometheus: untyped
11
12
  @prometheus_middleware: untyped
13
+ @secure_header: String
12
14
  @server: untyped
13
15
 
16
+ @server_class: untyped
17
+ @session: bool
14
18
  @threads: Integer
15
19
 
16
20
  attr_accessor bind: String
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: macaw_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aria Diniz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-18 00:00:00.000000000 Z
11
+ date: 2024-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prometheus-client