gloo 3.7.0 → 3.8.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
  SHA256:
3
- metadata.gz: c1dfa24d10974fad30c699b9935d765525ceda2eef22c642be9f30fd8f717ce7
4
- data.tar.gz: c631fafd6862b14c03994767ad520cc95380868ea9a1c60a68f0da67399d9fab
3
+ metadata.gz: 0c8d357cda5602195adcfddc50c58705d34670b6ac01848eb8a1d66243571356
4
+ data.tar.gz: 86687c8e18f4843dc9bca97111e140c26750479fce8619341a902284dde5a573
5
5
  SHA512:
6
- metadata.gz: a020b85943d3fa84e2940dc37a4bf61d541a89e55de775b0a22fd001b7e57de84a852748d0084e92bf1490e5f561761b62cbea9c94cbfa57e422632bf77134c5
7
- data.tar.gz: 04ca71cc0e8c3c483d31155799876e336d372d2bee8f1a923f429c3aee0c2902528af4ebe289d3a6e5709274205ca7d5feaaf164e1f8187c48ce6a5c4315a847
6
+ metadata.gz: baa186fd096e79a6152b7c287e23048a4b2fa67af5aa5db1c4e860918f42477b75e52fabbae8309506adfa32a77f5616bc2c4f4b22b6972142cb3977c9c965c9
7
+ data.tar.gz: 8467cf3db96cd931cad4dc06910a4933c65284b25675a3f884e17d5639cb8b3d9b50ff8bd1c171d329f318317390e715a9a5af62dceed681509fe336d28c3439
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 3.7.0
1
+ 3.8.0
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,9 @@
1
+ 3.8.0 - 2025.01.23
2
+ - Fixes issue with sessions
3
+ - Adds authenticity token tag and supporting session id and validation
4
+
5
+
6
+
1
7
  3.7.0 - 2025.01.09
2
8
  - Adds File message to get SHA256 hash
3
9
  - Asset Fingerprinting
@@ -0,0 +1,72 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2025 Eric Crane. All rights reserved.
3
+ #
4
+ # Helper class to generate and verify a csrf token.
5
+ #
6
+ require 'securerandom'
7
+ require 'base64'
8
+ require 'active_support/security_utils'
9
+
10
+ module Gloo
11
+ module Objs
12
+ class CsrfToken
13
+
14
+ TOKEN_LENGTH = 32
15
+ AUTHENTICITY_TOKEN = 'authenticity_token'.freeze
16
+
17
+ #
18
+ # Generate a random token
19
+ #
20
+ def self.generate_csrf_token
21
+ SecureRandom.base64( TOKEN_LENGTH )
22
+ end
23
+
24
+ #
25
+ # Generate a masked token.
26
+ #
27
+ def self.mask_token( base_token )
28
+ one_time_pad = SecureRandom.random_bytes( base_token.bytesize )
29
+ masked_token = one_time_pad.bytes.zip( base_token.bytes ).map { |a, b| a ^ b }.pack('C*')
30
+ return Base64.urlsafe_encode64( one_time_pad + masked_token ) # Encode the result
31
+ end
32
+
33
+ #
34
+ # Unmask a masked token.
35
+ #
36
+ def self.unmask_token( masked_token )
37
+ decoded = Base64.urlsafe_decode64( masked_token )
38
+ one_time_pad, masked_token = decoded[0...decoded.length / 2], decoded[decoded.length / 2..]
39
+ return one_time_pad.bytes.zip( masked_token.bytes ).map { |a, b| (a ^ b).chr }.join
40
+ end
41
+
42
+ #
43
+ # Compare two tokens.
44
+ # Use ActiveSupport::SecurityUtils.secure_compare to avoid timing attacks.
45
+ #
46
+ def self.compare_tokens( token1, token2 )
47
+ return ActiveSupport::SecurityUtils.secure_compare( token1, token2 )
48
+ end
49
+
50
+ #
51
+ # Return a hidden field with the masked csrf token.
52
+ #
53
+ def self.get_csrf_token_hidden_field( base_token )
54
+ form_token = mask_token( base_token )
55
+
56
+ return "<input type='hidden' name='#{AUTHENTICITY_TOKEN}' value='#{form_token}' />"
57
+ end
58
+
59
+ #
60
+ # Validate a masked csrf token that came from a form submit.
61
+ #
62
+ def self.valid_csrf_token?( base_token, masked_token )
63
+ return false unless base_token && masked_token
64
+
65
+ unmasked_token = unmask_token( masked_token )
66
+
67
+ return compare_tokens( base_token, unmasked_token )
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -51,7 +51,8 @@ module Gloo
51
51
  ELAPSED = 'elapsed'.freeze
52
52
  DB = 'db'.freeze
53
53
  PAGE = 'page'.freeze
54
-
54
+ CURRENT_PAGE = 'current_page'.freeze
55
+
55
56
  # Container with pages in the web app.
56
57
  PAGES = 'pages'.freeze
57
58
 
@@ -303,7 +304,7 @@ module Gloo
303
304
  # Important to do this after the response is sent
304
305
  # to avoid holding on to data that is no longer needed.
305
306
  #
306
- def clear_session_data
307
+ def reset_session_data
307
308
  session_container.children.each do |session_var|
308
309
  session_var.value = ''
309
310
  end
@@ -407,6 +408,7 @@ module Gloo
407
408
  def self.messages
408
409
  return super + [ 'start', 'stop',
409
410
  'list_routes', 'list_assets',
411
+ 'add_session_to_response', 'clear_session_data',
410
412
  'list_asset_img', 'list_asset_css', 'list_asset_js' ]
411
413
  end
412
414
 
@@ -494,6 +496,22 @@ module Gloo
494
496
  end
495
497
  end
496
498
 
499
+ #
500
+ # Add the session data to the response.
501
+ # This will be done for the current (next) request only.
502
+ #
503
+ def msg_add_session_to_response
504
+ @session.add_session_to_response if @session
505
+ end
506
+
507
+ #
508
+ # Clear out the session data, and remove it from the response.
509
+ #
510
+ def msg_clear_session_data
511
+ reset_session_data
512
+ @session.clear_session_data if @session
513
+ end
514
+
497
515
 
498
516
  # ---------------------------------------------------------------------
499
517
  # Start and Stop Events
@@ -533,7 +551,7 @@ module Gloo
533
551
  @engine.log.info "Stopping web server…"
534
552
 
535
553
  # Last chance to clear out session data.
536
- clear_session_data
554
+ reset_session_data
537
555
 
538
556
  @web_server.stop
539
557
  @web_server = nil
@@ -570,13 +588,18 @@ module Gloo
570
588
 
571
589
  #
572
590
  # Run the on request script if there is one.
591
+ # Set thee current page object so the app knows
592
+ # which page is being requested.
573
593
  #
574
- def run_on_request
594
+ def run_on_request current_page
595
+ for_page = find_child CURRENT_PAGE
596
+ alias_value = current_page.pn
597
+ for_page.set_value( alias_value ) if for_page
575
598
  o = find_child ON_REQUEST
576
599
  return unless o
577
600
  o = Gloo::Objs::Alias.resolve_alias( @engine, o )
578
601
 
579
- Gloo::Exec::Dispatch.message( @engine, 'run', o )
602
+ Gloo::Exec::Dispatch.message( @engine, 'run', o, CURRENT_PAGE => current_page )
580
603
  end
581
604
 
582
605
  #
@@ -595,6 +618,10 @@ module Gloo
595
618
  # This is done before the on_request event is fired.
596
619
  #
597
620
  def set_request_data( request )
621
+ # Clear out the redirect if there is one since this is the start of
622
+ # a new request.
623
+ @redirect = nil
624
+
598
625
  data = find_child RESQUEST_DATA
599
626
  return unless data
600
627
  data = Gloo::Objs::Alias.resolve_alias( @engine, data )
@@ -72,6 +72,14 @@ module Gloo
72
72
  return "<link rel='stylesheet' media='all' href='#{published_name}' />"
73
73
  end
74
74
 
75
+ #
76
+ # Embed a hidden field with the autenticity token.
77
+ #
78
+ def autenticity_token_tag
79
+ session_id = @engine.running_app.obj&.session&.get_session_id
80
+ return Gloo::Objs::CsrfToken.get_csrf_token_hidden_field( session_id )
81
+ end
82
+
75
83
 
76
84
  # ---------------------------------------------------------------------
77
85
  # Obj Helper Functions
@@ -46,6 +46,9 @@ module Gloo
46
46
  request.request_params.log_id_keys
47
47
 
48
48
  if page
49
+ # Run the on_request script with the found page.
50
+ @server_obj.run_on_request( page )
51
+
49
52
  if page.is_a? Gloo::Objs::FileHandle
50
53
  result = handle_file page
51
54
  else
@@ -59,9 +59,15 @@ module Gloo
59
59
 
60
60
  # Run the on_request script if there is one.
61
61
  @handler.server_obj.set_request_data self
62
- @handler.server_obj.run_on_request
63
62
 
64
- result, page_obj = @handler.handle self
63
+ # Check authenticity token if it's given.
64
+ if @request_params.check_authenticity_token( @engine )
65
+ result, page_obj = @handler.handle self
66
+ else
67
+ # Render the error page.
68
+ result = @handler.server_error_result
69
+ end
70
+
65
71
  finish_timer
66
72
 
67
73
  # Run the on_response script if there is one.
@@ -56,6 +56,29 @@ module Gloo
56
56
  end
57
57
 
58
58
 
59
+ # ---------------------------------------------------------------------
60
+ # Authenticity Token checking
61
+ # ---------------------------------------------------------------------
62
+
63
+ #
64
+ # Check the authenticity token if it is present.
65
+ # Returns true if it is present and valid, and
66
+ # also if it is not present.
67
+ # Returns false if it is present but not valid.
68
+ #
69
+ def check_authenticity_token engine
70
+ auth_token = @query_params[ Gloo::Objs::CsrfToken::AUTHENTICITY_TOKEN ]
71
+ if auth_token
72
+ session_id = engine.running_app.obj&.session&.get_session_id
73
+ return false unless session_id
74
+
75
+ return Gloo::Objs::CsrfToken.valid_csrf_token?( session_id, auth_token )
76
+ end
77
+
78
+ return true
79
+ end
80
+
81
+
59
82
  # ---------------------------------------------------------------------
60
83
  # Helper functions
61
84
  # ---------------------------------------------------------------------
@@ -122,9 +122,12 @@ module Gloo
122
122
  headers[ 'Location' ] = @location
123
123
  end
124
124
 
125
- session = @engine&.running_app&.obj&.session
125
+ session = @engine&.running_app&.obj&.session
126
126
  headers = session.add_session_for_response( headers ) if session
127
127
 
128
+ # Clear out session data after the response is prepared.
129
+ @engine&.running_app&.obj&.reset_session_data
130
+
128
131
  return headers
129
132
  end
130
133
 
@@ -15,6 +15,7 @@ module Gloo
15
15
  class Session
16
16
 
17
17
  SESSION_CONTAINER = 'session'.freeze
18
+ SESSION_ID_NAME = 'session_id'.freeze
18
19
 
19
20
 
20
21
  # ---------------------------------------------------------------------
@@ -29,6 +30,8 @@ module Gloo
29
30
  @log = @engine.log
30
31
 
31
32
  @server_obj = server_obj
33
+ @include_in_response = false
34
+ @clearing_session = false
32
35
  end
33
36
 
34
37
 
@@ -52,8 +55,12 @@ module Gloo
52
55
  data = decode_decrypt( data )
53
56
  return unless data
54
57
 
58
+ @session_id = data[ SESSION_ID_NAME ]
59
+
55
60
  data.each do |key, value|
56
- @server_obj.set_session_var( key, value )
61
+ unless key == SESSION_ID_NAME
62
+ @server_obj.set_session_var( key, value )
63
+ end
57
64
  end
58
65
  end
59
66
  end
@@ -64,18 +71,62 @@ module Gloo
64
71
 
65
72
 
66
73
  # ---------------------------------------------------------------------
67
- # Get Session Data for Response
74
+ # Set Session Data for Response
68
75
  # ---------------------------------------------------------------------
69
76
 
77
+ #
78
+ # Temporarily set the flag to add the session data to the response.
79
+ # Once this is done, the flag will be cleared and it will not
80
+ # be added to the next request unless specifically set.
81
+ #
82
+ def add_session_to_response
83
+ @include_in_response = true
84
+ end
85
+
86
+ def init_session_id
87
+ @session_id = Gloo::Objs::CsrfToken.generate_csrf_token
88
+ return @session_id
89
+ end
90
+
91
+ #
92
+ # Initialize the session id and add it to the data.
93
+ # Use the current session ID if it is there.
94
+ #
95
+ def get_session_id
96
+ if @clearing_session
97
+ @clearing_session = false
98
+ return nil
99
+ end
100
+
101
+ init_session_id if @session_id.blank?
102
+
103
+ return @session_id
104
+ end
105
+
106
+ #
107
+ # Clear out the session Id.
108
+ # Set the flag to add the session data to the response.
109
+ #
110
+ def clear_session_data
111
+ @session_id = nil
112
+ @clearing_session = true
113
+ add_session_to_response
114
+ end
115
+
70
116
  #
71
117
  # If there is session data, encrypt and add it to the response.
72
118
  # Once done, clear out the session data.
73
119
  #
74
120
  def add_session_for_response( headers )
75
121
  # Are we using sessions?
76
- if @server_obj.use_session?
122
+ if @server_obj.use_session? && @include_in_response
123
+ # Reset the flag because we are adding to the session data now
124
+ @include_in_response = false
125
+
77
126
  # Build and add encrypted session data
78
127
  data = @server_obj.get_session_data
128
+ data[ SESSION_ID_NAME ] = get_session_id
129
+
79
130
  unless data.empty?
80
131
  data = encrypt_encode( data )
81
132
  session_hash = {
@@ -90,9 +141,6 @@ module Gloo
90
141
 
91
142
  Rack::Utils.set_cookie_header!( headers, session_name, session_hash )
92
143
  end
93
-
94
- # Clear out session data
95
- @server_obj.clear_session_data
96
144
  end
97
145
 
98
146
  return headers
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.0
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-09 00:00:00.000000000 Z
11
+ date: 2025-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -462,6 +462,7 @@ files:
462
462
  - lib/gloo/objs/ror/erb.rb
463
463
  - lib/gloo/objs/ror/eval.rb
464
464
  - lib/gloo/objs/security/cipher.rb
465
+ - lib/gloo/objs/security/csrf_token.rb
465
466
  - lib/gloo/objs/security/password.rb
466
467
  - lib/gloo/objs/system/file_handle.rb
467
468
  - lib/gloo/objs/system/ssh_exec.rb