macaw_framework 1.2.4 → 1.2.6

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