gloo 3.7.0 → 3.8.0
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 +4 -4
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +6 -0
- data/lib/gloo/objs/security/csrf_token.rb +72 -0
- data/lib/gloo/objs/web_svr/svr.rb +32 -5
- data/lib/gloo/web_svr/embedded_renderer.rb +8 -0
- data/lib/gloo/web_svr/handler.rb +3 -0
- data/lib/gloo/web_svr/request.rb +8 -2
- data/lib/gloo/web_svr/request_params.rb +23 -0
- data/lib/gloo/web_svr/response.rb +4 -1
- data/lib/gloo/web_svr/session.rb +54 -6
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c8d357cda5602195adcfddc50c58705d34670b6ac01848eb8a1d66243571356
|
4
|
+
data.tar.gz: 86687c8e18f4843dc9bca97111e140c26750479fce8619341a902284dde5a573
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: baa186fd096e79a6152b7c287e23048a4b2fa67af5aa5db1c4e860918f42477b75e52fabbae8309506adfa32a77f5616bc2c4f4b22b6972142cb3977c9c965c9
|
7
|
+
data.tar.gz: 8467cf3db96cd931cad4dc06910a4933c65284b25675a3f884e17d5639cb8b3d9b50ff8bd1c171d329f318317390e715a9a5af62dceed681509fe336d28c3439
|
data/lib/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.8.0
|
data/lib/VERSION_NOTES
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/gloo/web_svr/handler.rb
CHANGED
data/lib/gloo/web_svr/request.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/gloo/web_svr/session.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
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.
|
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-
|
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
|