ru.Bee 2.7.19 → 3.0.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/app/models/user.rb +2 -1
- data/lib/db/create_users.rb +1 -0
- data/lib/db/test.db +0 -0
- data/lib/inits/system.rb +1 -0
- data/lib/rubee/autoload.rb +36 -9
- data/lib/rubee/cli/db.rb +32 -2
- data/lib/rubee/cli/project.rb +1 -0
- data/lib/rubee/cli/server.rb +2 -2
- data/lib/rubee/cli/version.rb +1 -1
- data/lib/rubee/controllers/base_controller.rb +2 -0
- data/lib/rubee/controllers/extensions/auth_tokenable.rb +8 -0
- data/lib/rubee/controllers/extensions/authorizable.rb +43 -0
- data/lib/rubee/internal_middlewares/cookie_samesite_middleware.rb +36 -0
- data/lib/rubee/patches/io_ready.rb +11 -0
- data/lib/rubee/support/time.rb +66 -0
- data/lib/rubee.rb +2 -2
- data/lib/tests/async/thread_async_test.rb +2 -0
- data/lib/tests/configuration_test.rb +7 -0
- data/lib/tests/controllers/auth_tokenable_test.rb +3 -5
- data/lib/tests/controllers/authorizable_test.rb +85 -0
- data/lib/tests/example_models/user.rb +4 -1
- data/lib/tests/models/user_model_test.rb +8 -1
- data/lib/tests/support/time_extension_test.rb +210 -0
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a38b359191859d1d0d22a355a81a3b86c760c5ab3ba33f1ac13059f14c6b69b
|
|
4
|
+
data.tar.gz: f0c8763134b4ad81e0db61b9926bb0f8e292ad4c0e328cbd1ae2960cf9155f3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e69bfd2ff4cae80bf4a2821feeb396f23da0dbdf3fd74f9405dd4e89feb0d2ba938d138372d140a921322916d70097766efd65a6cdf654ca6a469dcde29b1263
|
|
7
|
+
data.tar.gz: 2f2cbf3d544b9c89322e2f79d7a10bd53d3aadb5d592dbc890f482a87efd021b1276c77ab7e2828a342443637cd21bfa9ea25db79778a46993284a4e8df8144e
|
data/lib/app/models/user.rb
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
# If you remove or modify it, make sure all changes are inlined
|
|
3
3
|
# with AuthTokenMiddleware and AuthTokenable modules
|
|
4
4
|
class User < Rubee::SequelObject
|
|
5
|
-
attr_accessor :id, :email, :password, :created, :updated
|
|
5
|
+
attr_accessor :id, :email, :role, :password, :created, :updated
|
|
6
|
+
ROLES = { admin: 1, user: 0 }.freeze
|
|
6
7
|
end
|
data/lib/db/create_users.rb
CHANGED
data/lib/db/test.db
CHANGED
|
Binary file
|
data/lib/inits/system.rb
CHANGED
data/lib/rubee/autoload.rb
CHANGED
|
@@ -4,19 +4,34 @@ module Rubee
|
|
|
4
4
|
class << self
|
|
5
5
|
def call(black_list = [], **options)
|
|
6
6
|
load_whitelisted(options[:white_list_dirs]) && return if options[:white_list_dirs]
|
|
7
|
-
#
|
|
7
|
+
# Autoload all rbs
|
|
8
8
|
root_directory = File.join(Rubee::ROOT_PATH, '/lib')
|
|
9
|
+
|
|
10
|
+
load_middlewares(root_directory, black_list)
|
|
11
|
+
|
|
9
12
|
priority_order_require(root_directory, black_list)
|
|
10
13
|
|
|
11
14
|
load_inits(root_directory, black_list)
|
|
12
|
-
#
|
|
15
|
+
# Ensure sequel object is connected
|
|
13
16
|
Rubee::SequelObject.reconnect!
|
|
14
|
-
Dir.glob(File.join(Rubee::APP_ROOT, '**', '*.rb'))
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Dir.glob(File.join(Rubee::APP_ROOT, '**', '*.rb'))
|
|
18
|
+
.sort_by { |file| file.include?('/models/') ? 0 : 1 }
|
|
19
|
+
.each do |file|
|
|
20
|
+
base_name = File.basename(file)
|
|
21
|
+
unless base_name.end_with?('_test.rb') || (black_list + BLACKLIST).include?(base_name)
|
|
22
|
+
require_relative file
|
|
23
|
+
end
|
|
19
24
|
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_middlewares(root_directory, black_list)
|
|
28
|
+
# rubee middlewares
|
|
29
|
+
Dir[File.join(Rubee::ROOT_PATH, '/lib', '/rubee/internal_middlewares/**', '*.rb')].each do |file|
|
|
30
|
+
require_relative file unless black_list.include?("#{file}.rb")
|
|
31
|
+
end
|
|
32
|
+
# project middlewares
|
|
33
|
+
Dir[File.join(root_directory, 'middlewares/**', '*.rb')].each do |file|
|
|
34
|
+
require_relative file unless black_list.include?("#{file}.rb")
|
|
20
35
|
end
|
|
21
36
|
end
|
|
22
37
|
|
|
@@ -45,6 +60,17 @@ module Rubee
|
|
|
45
60
|
end
|
|
46
61
|
end
|
|
47
62
|
|
|
63
|
+
def load_envs!(prefix = ENV['RACK_ENV'], root_directory)
|
|
64
|
+
env_file_name = "#{root_directory}/.#{prefix}.env"
|
|
65
|
+
File.foreach(env_file_name) do |line|
|
|
66
|
+
line = line.strip
|
|
67
|
+
next if line.empty? || line.start_with?('#')
|
|
68
|
+
key, value = line.split('=', 2)
|
|
69
|
+
ENV[key] = value
|
|
70
|
+
end
|
|
71
|
+
rescue => _
|
|
72
|
+
end
|
|
73
|
+
|
|
48
74
|
def priority_order_require(root_directory, black_list)
|
|
49
75
|
# rubee pub sub
|
|
50
76
|
Dir[File.join(root_directory, 'rubee/pubsub/**', '*.rb')].each do |file|
|
|
@@ -59,10 +85,11 @@ module Rubee
|
|
|
59
85
|
require_relative file unless black_list.include?("#{file}.rb")
|
|
60
86
|
end
|
|
61
87
|
load_support(root_directory, black_list)
|
|
88
|
+
|
|
89
|
+
load_envs!(ENV['RACK_ENV'], root_directory)
|
|
62
90
|
# app config and routes
|
|
63
91
|
unless black_list.include?('base_configuration.rb')
|
|
64
|
-
require_relative File.join(Rubee::APP_ROOT, Rubee::LIB,
|
|
65
|
-
'config/base_configuration')
|
|
92
|
+
require_relative File.join(Rubee::APP_ROOT, Rubee::LIB, 'config/base_configuration')
|
|
66
93
|
end
|
|
67
94
|
require_relative File.join(Rubee::APP_ROOT, Rubee::LIB, 'config/routes') unless black_list.include?('routes.rb')
|
|
68
95
|
# rubee extensions
|
data/lib/rubee/cli/db.rb
CHANGED
|
@@ -38,9 +38,39 @@ module Rubee
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def drop_tables(_argv)
|
|
41
|
-
|
|
41
|
+
adapter = Rubee::SequelObject::DB.adapter_scheme.to_s
|
|
42
|
+
tables = Rubee::SequelObject::DB.tables
|
|
43
|
+
|
|
44
|
+
case adapter
|
|
45
|
+
when /postgres/
|
|
46
|
+
tables.each { |table| Rubee::SequelObject::DB.drop_table(table, cascade: true) }
|
|
47
|
+
when /mysql/
|
|
48
|
+
Rubee::SequelObject::DB.run("SET FOREIGN_KEY_CHECKS = 0")
|
|
49
|
+
tables.each { |table| Rubee::SequelObject::DB.drop_table(table) }
|
|
50
|
+
Rubee::SequelObject::DB.run("SET FOREIGN_KEY_CHECKS = 1")
|
|
51
|
+
when /sqlite/
|
|
52
|
+
Rubee::SequelObject::DB.run("PRAGMA foreign_keys = OFF")
|
|
53
|
+
tables.each { |table| Rubee::SequelObject::DB.drop_table(table) }
|
|
54
|
+
Rubee::SequelObject::DB.run("PRAGMA foreign_keys = ON")
|
|
55
|
+
else
|
|
56
|
+
# Fallback — try topological drop order by retrying failures
|
|
57
|
+
remaining = tables.dup
|
|
58
|
+
max_attempts = tables.size
|
|
59
|
+
attempts = 0
|
|
60
|
+
while remaining.any? && attempts < max_attempts
|
|
61
|
+
failed = []
|
|
62
|
+
remaining.each do |table|
|
|
63
|
+
Rubee::SequelObject::DB.drop_table(table)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
failed << table
|
|
66
|
+
end
|
|
67
|
+
remaining = failed
|
|
68
|
+
attempts += 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
42
72
|
color_puts("These tables have been dropped for the #{ENV['RACK_ENV']} env:", color: :cyan)
|
|
43
|
-
color_puts(
|
|
73
|
+
color_puts(tables, color: :gray)
|
|
44
74
|
end
|
|
45
75
|
|
|
46
76
|
def truncate_tables(_argv)
|
data/lib/rubee/cli/project.rb
CHANGED
data/lib/rubee/cli/server.rb
CHANGED
|
@@ -46,8 +46,8 @@ LOGO
|
|
|
46
46
|
|
|
47
47
|
print_logo
|
|
48
48
|
color_puts("Starting takeoff of ruBee server on port #{port} in dev mode...", color: :yellow)
|
|
49
|
-
|
|
50
|
-
command = "rerun -- #{jit_prefix(jit)}rackup --port #{port} #{rackup_file}"
|
|
49
|
+
patch_path = File.join(__dir__, '../patches/io_ready.rb')
|
|
50
|
+
command = "ruby -r '#{patch_path}' #{`which rerun`.strip} -- #{jit_prefix(jit)}rackup --port #{port} #{rackup_file}"
|
|
51
51
|
color_puts(command, color: :gray)
|
|
52
52
|
exec(command)
|
|
53
53
|
end
|
data/lib/rubee/cli/version.rb
CHANGED
|
@@ -12,6 +12,14 @@ module Rubee
|
|
|
12
12
|
base.extend(ClassMethods)
|
|
13
13
|
|
|
14
14
|
base.attach('Rubee::AuthTokenMiddleware')
|
|
15
|
+
base.extend(SubclassHook)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module SubclassHook
|
|
19
|
+
def inherited(subclass)
|
|
20
|
+
super
|
|
21
|
+
subclass.attach('Rubee::AuthTokenMiddleware')
|
|
22
|
+
end
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
module InstanceMethods
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Rubee
|
|
2
|
+
module Authorizable
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend ClassMethods
|
|
5
|
+
base.send :include, InstanceMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module InstanceMethods
|
|
9
|
+
def authorized?(role, model: nil, role_field: :role)
|
|
10
|
+
return false unless authentificated?
|
|
11
|
+
user = authentificated_user(user_model: model)
|
|
12
|
+
return false unless user
|
|
13
|
+
return false unless model.const_get(:ROLES).keys.map(&:to_sym).include?(role.to_sym)
|
|
14
|
+
|
|
15
|
+
model.const_get(:ROLES).key(user.send(role_field)).to_sym == role.to_sym
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
def authorize(model: nil, response_hash: nil, role_field: :role, **roles)
|
|
21
|
+
@__authorizations ||= {}
|
|
22
|
+
stringified_model_name = model.to_s
|
|
23
|
+
@__authorizations[stringified_model_name] ||= {}
|
|
24
|
+
@__authorizations[stringified_model_name].merge! roles
|
|
25
|
+
model_klass = Object.const_get stringified_model_name.capitalize
|
|
26
|
+
|
|
27
|
+
@__authorizations[stringified_model_name].each do |role, methods|
|
|
28
|
+
methods = methods.is_a?(Array) ? methods : [methods]
|
|
29
|
+
methods.each do |method|
|
|
30
|
+
around method, ->(controller, &original_action) do
|
|
31
|
+
if controller.send(:authorized?, role, model: model_klass, role_field:)
|
|
32
|
+
original_action.call
|
|
33
|
+
else
|
|
34
|
+
response_object_hash = response_hash || { object: { error: 'Unauthorized' }, type: :json, status: 403 }
|
|
35
|
+
controller.response_with(**response_object_hash)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Rubee
|
|
2
|
+
class CookieSamesiteMiddleware
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@app = app
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def call(env)
|
|
8
|
+
status, headers, body = @app.call(env)
|
|
9
|
+
|
|
10
|
+
cookie_header = headers["Set-Cookie"] || headers["set-cookie"]
|
|
11
|
+
header_key = headers["Set-Cookie"] ? "Set-Cookie" : "set-cookie"
|
|
12
|
+
|
|
13
|
+
if cookie_header
|
|
14
|
+
headers[header_key] = patch_cookies(cookie_header)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
[status, headers, body]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def patch_cookies(cookies)
|
|
23
|
+
is_dev = ENV['RACK_ENV'] == 'development' || ENV['RACK_ENV'].nil?
|
|
24
|
+
|
|
25
|
+
cookies.split("\n").map do |cookie|
|
|
26
|
+
cookie = cookie.gsub(/;\s*secure/i, "") if is_dev
|
|
27
|
+
|
|
28
|
+
unless cookie.downcase.include?("samesite")
|
|
29
|
+
cookie += "; SameSite=Lax"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
cookie
|
|
33
|
+
end.join("\n")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
class Time
|
|
2
|
+
def days_seconds
|
|
3
|
+
hour * 3600 + min * 60 + sec
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def beginning_of_day
|
|
7
|
+
Time.new(year, month, day, 0, 0, 0)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def end_of_day
|
|
11
|
+
Time.new(year, month, day, 23, 59, 59)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def at(hr, mn, sc)
|
|
15
|
+
Time.new(year, month, day, hr, mn, sc)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def all_day
|
|
19
|
+
beginning_of_day..end_of_day
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_days(days)
|
|
23
|
+
self + days * 86_400
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def subtract_days(days)
|
|
27
|
+
self - days * 86_400
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def closest_future_working_day
|
|
31
|
+
target_day = self
|
|
32
|
+
target_day = target_day.add_days(1) while target_day.wday == 6 || target_day.wday == 0
|
|
33
|
+
target_day
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_current_time
|
|
37
|
+
at(Time.now.hour, Time.now.min, Time.now.sec)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
def today
|
|
42
|
+
Time.now
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tomorrow
|
|
46
|
+
today.add_days(1)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def yesterday
|
|
50
|
+
today.subtract_days(1)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def beginning_of_today
|
|
54
|
+
new(Time.now.year, Time.now.month, Time.now.day, 0, 0, 0)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def end_of_today
|
|
58
|
+
new(Time.now.year, Time.now.month, Time.now.day, 23, 59, 59)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def start_of_today
|
|
62
|
+
new(Time.now.year, Time.now.month, Time.now.day, 0, 0, 0)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
data/lib/rubee.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Rubee
|
|
|
20
20
|
RUBEE_SUPPORT = { "Rubee::Support::Hash" => Hash, "Rubee::Support::String" => String }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
VERSION = '
|
|
23
|
+
VERSION = '3.0.0'
|
|
24
24
|
|
|
25
25
|
require_relative 'rubee/router'
|
|
26
26
|
require_relative 'rubee/logger'
|
|
@@ -66,7 +66,7 @@ module Rubee
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def middlewares
|
|
69
|
-
Rubee::Configuration.middlewares
|
|
69
|
+
([Rubee::CookieSamesiteMiddleware] + Rubee::Configuration.middlewares).uniq
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
private
|
|
@@ -20,4 +20,11 @@ describe 'Configuration' do
|
|
|
20
20
|
_("apples".plural?).must_equal(true)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
describe 'when ENV["RACK_ENV"] eq test' do
|
|
25
|
+
it 'laods env var to ENV' do
|
|
26
|
+
_(ENV['RACK_ENV']).must_equal('test')
|
|
27
|
+
_(ENV['SUPER_PASSWORD']).must_equal('test_password')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
23
30
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require_relative '../test_helper'
|
|
2
2
|
|
|
3
3
|
class TestController < Rubee::BaseController
|
|
4
|
-
include(Rubee::AuthTokenable)
|
|
5
4
|
auth_methods(:show)
|
|
6
5
|
def show
|
|
7
6
|
response_with(type: :json, object: { ok: :ok })
|
|
@@ -27,7 +26,6 @@ class TestController < Rubee::BaseController
|
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
class TesttwoController < Rubee::BaseController
|
|
30
|
-
include(Rubee::AuthTokenable)
|
|
31
29
|
auth_methods(:show)
|
|
32
30
|
def show
|
|
33
31
|
response_with(type: :json, object: { ok: :ok })
|
|
@@ -71,13 +69,13 @@ class AuthTokenableTest < Minitest::Test
|
|
|
71
69
|
Client.create(name: '9o@example.com', digest_password: '123456')
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
def
|
|
72
|
+
def test_test_controller_auth_tokenable
|
|
75
73
|
get('/test/show')
|
|
76
74
|
|
|
77
75
|
assert_equal(last_response.status, 401)
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
def
|
|
78
|
+
def test_test_controller_tokenable_authenticated
|
|
81
79
|
post('/test/login', { email: '9oU8S@example.com', password: '123456' })
|
|
82
80
|
rack_mock_session.cookie_jar["jwt"] = last_response.cookies["jwt"].value.last
|
|
83
81
|
get('/test/show')
|
|
@@ -91,7 +89,7 @@ class AuthTokenableTest < Minitest::Test
|
|
|
91
89
|
assert_equal(last_response.status, 401)
|
|
92
90
|
end
|
|
93
91
|
|
|
94
|
-
def
|
|
92
|
+
def test_test_controller_authenticated_custom_model
|
|
95
93
|
post('/testtwo/login', { name: '9o@example.com', digest_password: '123456' })
|
|
96
94
|
rack_mock_session.cookie_jar["jwt"] = last_response.cookies["jwt"].value.last
|
|
97
95
|
get('/testtwo/show')
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require_relative '../test_helper'
|
|
2
|
+
|
|
3
|
+
class AuthzController < Rubee::BaseController
|
|
4
|
+
auth_methods(:admin_only, :user_only)
|
|
5
|
+
authorize(admin: [:admin_only], model: :user, role_field: :role)
|
|
6
|
+
|
|
7
|
+
# POST /authz/login
|
|
8
|
+
def login
|
|
9
|
+
if authentificate!
|
|
10
|
+
response_with(type: :json, object: { ok: :ok }, headers: @token_header)
|
|
11
|
+
else
|
|
12
|
+
response_with(type: :json, object: { error: 'user unauthenticated' }, status: :unauthenticated)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# GET /authz/admin_only
|
|
17
|
+
def admin_only
|
|
18
|
+
response_with(type: :json, object: { ok: :admin_access })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# GET /authz/user_only
|
|
22
|
+
def user_only
|
|
23
|
+
response_with(type: :json, object: { ok: :user_access })
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class AuthorizableTest < Minitest::Test
|
|
28
|
+
include Rack::Test::Methods
|
|
29
|
+
|
|
30
|
+
def app
|
|
31
|
+
Rubee::Application.instance
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def setup
|
|
35
|
+
User.destroy_all
|
|
36
|
+
Rubee::Autoload.call
|
|
37
|
+
Rubee::Router.draw do |route|
|
|
38
|
+
route.post('/authz/login', to: 'authz#login')
|
|
39
|
+
route.get('/authz/admin_only', to: 'authz#admin_only')
|
|
40
|
+
route.get('/authz/user_only', to: 'authz#user_only')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
User.create(email: 'admin@example.com', password: '123456', role: 1)
|
|
44
|
+
User.create(email: 'user@example.com', password: '123456', role: 0)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def login_as(email)
|
|
48
|
+
post('/authz/login', { email:, password: '123456' })
|
|
49
|
+
rack_mock_session.cookie_jar["jwt"] = last_response.cookies["jwt"].value.last
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_admin_only_unauthenticated
|
|
53
|
+
get('/authz/admin_only')
|
|
54
|
+
|
|
55
|
+
assert_equal(403, last_response.status)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_admin_only_as_admin
|
|
59
|
+
login_as('admin@example.com')
|
|
60
|
+
|
|
61
|
+
get('/authz/admin_only')
|
|
62
|
+
assert_equal(200, last_response.status)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_admin_only_as_user_forbidden
|
|
66
|
+
login_as('user@example.com')
|
|
67
|
+
|
|
68
|
+
get('/authz/admin_only')
|
|
69
|
+
assert_equal(403, last_response.status)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_user_only_as_user
|
|
73
|
+
login_as('user@example.com')
|
|
74
|
+
|
|
75
|
+
get('/authz/user_only')
|
|
76
|
+
assert_equal(200, last_response.status)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_user_only_as_admin_forbidden
|
|
80
|
+
login_as('admin@example.com')
|
|
81
|
+
|
|
82
|
+
get('/authz/user_only')
|
|
83
|
+
assert_equal(200, last_response.status)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
class User < Rubee::SequelObject
|
|
2
|
-
attr_accessor :id, :email, :password, :created, :updated
|
|
2
|
+
attr_accessor :id, :email, :password, :role, :created, :updated
|
|
3
3
|
|
|
4
4
|
owns_many :accounts, cascade: true
|
|
5
5
|
owns_one :address, cascade: true
|
|
6
|
+
|
|
7
|
+
# Mandatory hash const required for authorizable
|
|
8
|
+
ROLES = { admin: 1, user: 0 }.freeze
|
|
6
9
|
end
|
|
@@ -169,6 +169,7 @@ describe 'User model' do
|
|
|
169
169
|
|
|
170
170
|
describe 'when there are related recrods' do
|
|
171
171
|
it 'does not delete the record' do
|
|
172
|
+
skip 'Account is not defined' unless respond_to?(:Account)
|
|
172
173
|
user = User.new(email: 'ok-test@test.com', password: '123')
|
|
173
174
|
user.save
|
|
174
175
|
Account.new(user_id: user.id, addres: 'test').save
|
|
@@ -184,6 +185,7 @@ describe 'User model' do
|
|
|
184
185
|
|
|
185
186
|
describe 'when there are related recrods but passed cascade=true' do
|
|
186
187
|
it 'deletes the record' do
|
|
188
|
+
skip 'Account is not defined' unless respond_to?(:Account)
|
|
187
189
|
user = User.new(email: 'ok-test@test.com', password: '123')
|
|
188
190
|
user.save
|
|
189
191
|
Account.new(user_id: user.id, addres: 'test').save
|
|
@@ -209,6 +211,8 @@ describe 'User model' do
|
|
|
209
211
|
|
|
210
212
|
describe 'when there is no record' do
|
|
211
213
|
it 'returns nil' do
|
|
214
|
+
User.destroy_all
|
|
215
|
+
|
|
212
216
|
assert_nil User.find(1)
|
|
213
217
|
end
|
|
214
218
|
end
|
|
@@ -290,6 +294,7 @@ describe 'User model' do
|
|
|
290
294
|
|
|
291
295
|
describe 'when there are records' do
|
|
292
296
|
it 'returns ordered records' do
|
|
297
|
+
User.destroy_all(cascade: true)
|
|
293
298
|
user = User.new(email: 'abc@test.com', password: '123')
|
|
294
299
|
user2 = User.new(email: 'defg@test.com', password: '123')
|
|
295
300
|
user.save
|
|
@@ -306,6 +311,7 @@ describe 'User model' do
|
|
|
306
311
|
|
|
307
312
|
describe 'when there are associated account records' do
|
|
308
313
|
it 'returns all records' do
|
|
314
|
+
skip 'Account is not defined' unless respond_to?(:Account)
|
|
309
315
|
user = User.new(email: 'ok-test@test.com', password: '123')
|
|
310
316
|
user.save
|
|
311
317
|
account = Account.new(user_id: user.id, addres: 'test')
|
|
@@ -321,7 +327,7 @@ describe 'User model' do
|
|
|
321
327
|
end
|
|
322
328
|
describe 'when there is one associated account' do
|
|
323
329
|
it 'cannot add more than one address' do
|
|
324
|
-
skip
|
|
330
|
+
skip 'Address is not defined' unless respond_to?(:Address)
|
|
325
331
|
user = User.new(email: 'bleh@example.com', password: '123')
|
|
326
332
|
user.save
|
|
327
333
|
|
|
@@ -335,6 +341,7 @@ zip: '555555')
|
|
|
335
341
|
end
|
|
336
342
|
|
|
337
343
|
it 'returns the single address' do
|
|
344
|
+
skip 'Address is not defined' unless respond_to?(:Address)
|
|
338
345
|
user = User.new(email: 'bleh@example.com', password: '123')
|
|
339
346
|
user.save
|
|
340
347
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
require_relative '../test_helper'
|
|
2
|
+
|
|
3
|
+
class TimeExtensionsTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@time = Time.new(2026, 6, 15, 14, 30, 45) # Sunday
|
|
6
|
+
@weekday = Time.new(2026, 6, 16, 9, 0, 0) # Monday
|
|
7
|
+
@saturday = Time.new(2026, 6, 20, 9, 0, 0) # Saturday
|
|
8
|
+
@sunday = Time.new(2026, 6, 21, 9, 0, 0) # Sunday
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# ---------- days_seconds ----------
|
|
12
|
+
|
|
13
|
+
def test_days_seconds
|
|
14
|
+
t = Time.new(2026, 6, 15, 1, 2, 3)
|
|
15
|
+
assert_equal 1 * 3600 + 2 * 60 + 3, t.days_seconds
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_days_seconds_midnight
|
|
19
|
+
t = Time.new(2026, 6, 15, 0, 0, 0)
|
|
20
|
+
assert_equal 0, t.days_seconds
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_days_seconds_end_of_day
|
|
24
|
+
t = Time.new(2026, 6, 15, 23, 59, 59)
|
|
25
|
+
assert_equal 23 * 3600 + 59 * 60 + 59, t.days_seconds
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ---------- beginning_of_day ----------
|
|
29
|
+
|
|
30
|
+
def test_beginning_of_day
|
|
31
|
+
result = @time.beginning_of_day
|
|
32
|
+
assert_equal Time.new(2026, 6, 15, 0, 0, 0), result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_beginning_of_day_preserves_date
|
|
36
|
+
assert_equal @time.year, @time.beginning_of_day.year
|
|
37
|
+
assert_equal @time.month, @time.beginning_of_day.month
|
|
38
|
+
assert_equal @time.day, @time.beginning_of_day.day
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# ---------- end_of_day ----------
|
|
42
|
+
|
|
43
|
+
def test_end_of_day
|
|
44
|
+
result = @time.end_of_day
|
|
45
|
+
assert_equal Time.new(2026, 6, 15, 23, 59, 59), result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_end_of_day_preserves_date
|
|
49
|
+
assert_equal @time.year, @time.end_of_day.year
|
|
50
|
+
assert_equal @time.month, @time.end_of_day.month
|
|
51
|
+
assert_equal @time.day, @time.end_of_day.day
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ---------- at ----------
|
|
55
|
+
|
|
56
|
+
def test_at
|
|
57
|
+
result = @time.at(10, 20, 30)
|
|
58
|
+
assert_equal Time.new(2026, 6, 15, 10, 20, 30), result
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_at_preserves_date
|
|
62
|
+
result = @time.at(0, 0, 0)
|
|
63
|
+
assert_equal @time.year, result.year
|
|
64
|
+
assert_equal @time.month, result.month
|
|
65
|
+
assert_equal @time.day, result.day
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# ---------- all_day ----------
|
|
69
|
+
|
|
70
|
+
def test_all_day_returns_range
|
|
71
|
+
assert_kind_of Range, @time.all_day
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_all_day_begin
|
|
75
|
+
assert_equal @time.beginning_of_day, @time.all_day.first
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_all_day_end
|
|
79
|
+
assert_equal @time.end_of_day, @time.all_day.last
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_all_day_includes_noon
|
|
83
|
+
noon = Time.new(2026, 6, 15, 12, 0, 0)
|
|
84
|
+
assert @time.all_day.include?(noon)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# ---------- add_days ----------
|
|
88
|
+
|
|
89
|
+
def test_add_days
|
|
90
|
+
result = @time.add_days(3)
|
|
91
|
+
assert_equal Time.new(2026, 6, 18, 14, 30, 45), result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_add_days_zero
|
|
95
|
+
assert_equal @time, @time.add_days(0)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_add_days_crosses_month
|
|
99
|
+
result = Time.new(2026, 6, 30, 0, 0, 0).add_days(1)
|
|
100
|
+
assert_equal 7, result.month
|
|
101
|
+
assert_equal 1, result.day
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# ---------- subtract_days ----------
|
|
105
|
+
|
|
106
|
+
def test_subtract_days
|
|
107
|
+
result = @time.subtract_days(5)
|
|
108
|
+
assert_equal Time.new(2026, 6, 10, 14, 30, 45), result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_subtract_days_zero
|
|
112
|
+
assert_equal @time, @time.subtract_days(0)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_subtract_days_crosses_month
|
|
116
|
+
result = Time.new(2026, 6, 1, 0, 0, 0).subtract_days(1)
|
|
117
|
+
assert_equal 5, result.month
|
|
118
|
+
assert_equal 31, result.day
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_add_and_subtract_roundtrip
|
|
122
|
+
assert_equal @time, @time.add_days(7).subtract_days(7)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# ---------- closest_future_working_day ----------
|
|
126
|
+
|
|
127
|
+
def test_closest_future_working_day_from_weekday
|
|
128
|
+
# Monday — already a working day, should not advance
|
|
129
|
+
assert_equal @weekday, @weekday.closest_future_working_day
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_closest_future_working_day_from_saturday
|
|
133
|
+
result = @saturday.closest_future_working_day
|
|
134
|
+
assert_equal 1, result.wday # Monday
|
|
135
|
+
assert_equal 22, result.day
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_closest_future_working_day_from_sunday
|
|
139
|
+
result = @sunday.closest_future_working_day
|
|
140
|
+
assert_equal 1, result.wday # Monday
|
|
141
|
+
assert_equal 22, result.day
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_closest_future_working_day_not_weekend
|
|
145
|
+
result = @saturday.closest_future_working_day
|
|
146
|
+
refute [0, 6].include?(result.wday)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ---------- with_current_time ----------
|
|
150
|
+
|
|
151
|
+
def test_with_current_time_preserves_date
|
|
152
|
+
result = @time.with_current_time
|
|
153
|
+
assert_equal @time.year, result.year
|
|
154
|
+
assert_equal @time.month, result.month
|
|
155
|
+
assert_equal @time.day, result.day
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_with_current_time_uses_current_hours
|
|
159
|
+
result = @time.with_current_time
|
|
160
|
+
now = Time.now
|
|
161
|
+
assert_in_delta now.hour, result.hour, 1
|
|
162
|
+
assert_in_delta now.min, result.min, 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# ---------- class methods ----------
|
|
166
|
+
|
|
167
|
+
def test_today_is_time
|
|
168
|
+
assert_kind_of Time, Time.today
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_today_is_roughly_now
|
|
172
|
+
assert_in_delta Time.now.to_i, Time.today.to_i, 2
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def test_tomorrow_is_one_day_ahead
|
|
176
|
+
assert_in_delta Time.today.to_i + 86_400, Time.tomorrow.to_i, 2
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_yesterday_is_one_day_behind
|
|
180
|
+
assert_in_delta Time.today.to_i - 86_400, Time.yesterday.to_i, 2
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def test_beginning_of_today
|
|
184
|
+
t = Time.beginning_of_today
|
|
185
|
+
assert_equal 0, t.hour
|
|
186
|
+
assert_equal 0, t.min
|
|
187
|
+
assert_equal 0, t.sec
|
|
188
|
+
assert_equal Time.now.day, t.day
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_end_of_today
|
|
192
|
+
t = Time.end_of_today
|
|
193
|
+
assert_equal 23, t.hour
|
|
194
|
+
assert_equal 59, t.min
|
|
195
|
+
assert_equal 59, t.sec
|
|
196
|
+
assert_equal Time.now.day, t.day
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_start_of_today_equals_beginning_of_today
|
|
200
|
+
assert_equal Time.beginning_of_today, Time.start_of_today
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_beginning_and_end_of_today_same_date
|
|
204
|
+
b = Time.beginning_of_today
|
|
205
|
+
e = Time.end_of_today
|
|
206
|
+
assert_equal b.year, e.year
|
|
207
|
+
assert_equal b.month, e.month
|
|
208
|
+
assert_equal b.day, e.day
|
|
209
|
+
end
|
|
210
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ru.Bee
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleg Saltykov
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: irb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
description: Application web server written on Ruby
|
|
13
27
|
email:
|
|
14
28
|
- oleg.saltykov@gmail.com
|
|
@@ -238,6 +252,7 @@ files:
|
|
|
238
252
|
- lib/rubee/configuration.rb
|
|
239
253
|
- lib/rubee/controllers/base_controller.rb
|
|
240
254
|
- lib/rubee/controllers/extensions/auth_tokenable.rb
|
|
255
|
+
- lib/rubee/controllers/extensions/authorizable.rb
|
|
241
256
|
- lib/rubee/controllers/extensions/middlewarable.rb
|
|
242
257
|
- lib/rubee/controllers/middlewares/auth_token_middleware.rb
|
|
243
258
|
- lib/rubee/extensions/hookable.rb
|
|
@@ -245,11 +260,13 @@ files:
|
|
|
245
260
|
- lib/rubee/extensions/validatable.rb
|
|
246
261
|
- lib/rubee/features.rb
|
|
247
262
|
- lib/rubee/generator.rb
|
|
263
|
+
- lib/rubee/internal_middlewares/cookie_samesite_middleware.rb
|
|
248
264
|
- lib/rubee/logger.rb
|
|
249
265
|
- lib/rubee/models/assoc_array.rb
|
|
250
266
|
- lib/rubee/models/database_objectable.rb
|
|
251
267
|
- lib/rubee/models/db_tools.rb
|
|
252
268
|
- lib/rubee/models/sequel_object.rb
|
|
269
|
+
- lib/rubee/patches/io_ready.rb
|
|
253
270
|
- lib/rubee/pubsub/container.rb
|
|
254
271
|
- lib/rubee/pubsub/publisher.rb
|
|
255
272
|
- lib/rubee/pubsub/redis.rb
|
|
@@ -258,6 +275,7 @@ files:
|
|
|
258
275
|
- lib/rubee/router.rb
|
|
259
276
|
- lib/rubee/support/hash.rb
|
|
260
277
|
- lib/rubee/support/string.rb
|
|
278
|
+
- lib/rubee/support/time.rb
|
|
261
279
|
- lib/rubee/websocket/websocket.rb
|
|
262
280
|
- lib/rubee/websocket/websocket_connections.rb
|
|
263
281
|
- lib/tests/async/thread_async_test.rb
|
|
@@ -265,6 +283,7 @@ files:
|
|
|
265
283
|
- lib/tests/cli/db_test.rb
|
|
266
284
|
- lib/tests/configuration_test.rb
|
|
267
285
|
- lib/tests/controllers/auth_tokenable_test.rb
|
|
286
|
+
- lib/tests/controllers/authorizable_test.rb
|
|
268
287
|
- lib/tests/controllers/base_controller_test.rb
|
|
269
288
|
- lib/tests/controllers/hookable_test.rb
|
|
270
289
|
- lib/tests/controllers/rubeeapp_test.rb
|
|
@@ -285,6 +304,7 @@ files:
|
|
|
285
304
|
- lib/tests/models/user_model_test.rb
|
|
286
305
|
- lib/tests/rubee_attach_test.rb
|
|
287
306
|
- lib/tests/rubee_generator_test.rb
|
|
307
|
+
- lib/tests/support/time_extension_test.rb
|
|
288
308
|
- lib/tests/test_helper.rb
|
|
289
309
|
- readme.md
|
|
290
310
|
homepage: https://github.com/nucleom42/rubee
|
|
@@ -305,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
305
325
|
- !ruby/object:Gem::Version
|
|
306
326
|
version: '0'
|
|
307
327
|
requirements: []
|
|
308
|
-
rubygems_version: 4.0.
|
|
328
|
+
rubygems_version: 4.0.15
|
|
309
329
|
specification_version: 4
|
|
310
330
|
summary: Fast and lightweight Ruby application server designed for minimalism and
|
|
311
331
|
flexibility
|