bp3-activestorage 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c92e9ed2f063e2d3d1a777bdd9f3d3ddb23892a9ad70ef54796c4b4abf2e5f43
4
- data.tar.gz: f6bcf3de5ed1ee85c69dcebab7281bbde03049f927b455e4535928d592d8ec58
3
+ metadata.gz: 109755bc5d9c018fc401b343738355f976b96d73e2f34997ac3c19cd3e730afa
4
+ data.tar.gz: 05cab73a42dcfc96d4d3a2b29f4122eb4315ed4c2f11aff389ed65a908af8fd5
5
5
  SHA512:
6
- metadata.gz: a2fb3b472e8299d518b954a56b5a973a91601d23ad8dd7c9555b49b441423f4ddd44e0e6f166b2a33d34b2963c6d85c69b2ba33984dadf4cef2e16c07fabd990
7
- data.tar.gz: 37c2134952350031e4d4a994468d4e38f8a566a83257e59ffcf0d310a904f639cc660d648818b307f5a2b124f699bc5f119092a40089a81f2caae3f6e217b003
6
+ metadata.gz: '0927cc2fd1066da4137fb6eb3d5057d168e33efb90c42e3021e49f7e48157772a4caf9c40186e4798aa50d4c6767ff07fb4dbeb6d80668e42c32b87884dbc240'
7
+ data.tar.gz: 05ee612b2d54d5c3e3d877200565d560862bee6c73b1e6965d7d197657dba593840a3f0dc1b2bbde37d97409871c90a7841bb91c56fb857761ca1ffcabb8c34b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-11-03
4
+
5
+ ### Security
6
+
7
+ - Require a signed-in user, site admin or root for direct uploads
8
+
9
+ ### Fixed
10
+
11
+ - Reopen the Active Storage classes in `to_prepare`, so a development reload keeps what this gem adds
12
+
3
13
  ## [0.2.0] - 2026-10-30
4
14
 
5
15
  ### Security
data/CLAUDE.md CHANGED
@@ -20,8 +20,10 @@ shell is not already on it; a missing `rspec` means the wrong Ruby, not a missin
20
20
 
21
21
  ## Architecture
22
22
 
23
- `lib/bp3/activestorage/railtie.rb` reopens five classes in an `after_initialize` block. The Railtie is
24
- only defined when `Rails.env` is.
23
+ `lib/bp3/activestorage/railtie.rb` reopens six classes in a `to_prepare` block. Active Storage's models and
24
+ controllers are reloaded with the application's code in development, and `to_prepare` runs again after
25
+ each reload, where `after_initialize` would not. Each class is referenced before it is reopened, so that
26
+ it is autoloaded rather than defined anew. The Railtie is only defined when `Rails.env` is.
25
27
 
26
28
  **Models**: `ActiveStorage::Attachment`, `Blob` and `VariantRecord`
27
29
  - include `Bp3::Core::Rqid`, `Sqnr` and `Ransackable` (Blob also `Displayable`), call
@@ -34,8 +36,11 @@ only defined when `Rails.env` is.
34
36
 
35
37
  **Controllers**: `ActiveStorage::BaseController` and `ActiveStorage::Representations::ProxyController`
36
38
  include `Bp3::Core::Actions`, `Settings`, `FeatureFlags` and `Cookies`, so file requests get the same site,
37
- tenant and request tracking as the rest of the application. They do not authenticate;
38
- `authenticate_root!` breaks the proxy controller.
39
+ tenant and request tracking as the rest of the application. They do not authenticate: the signed URL is
40
+ the permission, and public pages and emails show files to people with no session.
41
+ `ActiveStorage::DirectUploadsController` does: it answers 401 unless `grsc.current_login` names someone. It
42
+ inherits `set_global_request_state` from `Bp3::Core::Actions` through `BaseController`, and its own check
43
+ runs after it, so it asks the request state rather than Devise.
39
44
 
40
45
  ## Testing
41
46
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bp3-activestorage (0.2.0)
4
+ bp3-activestorage (0.3.0)
5
5
  activestorage (~> 8.1)
6
6
  bp3-core (>= 0.4.0)
7
7
  paper_trail (>= 15.1)
@@ -60,9 +60,10 @@ GEM
60
60
  actionview (~> 8.1)
61
61
  activesupport (~> 8.1)
62
62
  bp3-request_state (~> 0.1)
63
- bp3-request_state (0.1.3)
63
+ bp3-request_state (0.1.4)
64
64
  actionpack (~> 8.1)
65
65
  activesupport (~> 8.1)
66
+ ostruct (>= 0.6)
66
67
  request_store (>= 1.5.1)
67
68
  builder (3.3.0)
68
69
  byebug (13.0.0)
@@ -91,6 +92,7 @@ GEM
91
92
  prism (~> 1.5)
92
93
  nokogiri (1.19.4-x86_64-darwin)
93
94
  racc (~> 1.4)
95
+ ostruct (0.6.3)
94
96
  paper_trail (17.0.0)
95
97
  activerecord (>= 7.1)
96
98
  request_store (~> 1.4)
@@ -7,101 +7,114 @@ module Bp3
7
7
  module Activestorage
8
8
  if defined?(Rails.env)
9
9
  class Railtie < Rails::Railtie
10
- initializer 'bp3.activestorage.railtie.register' do |app|
11
- app.config.after_initialize do
12
- ::ActiveStorage::Attachment # preload
13
- ::ActiveStorage::Blob # preload
14
- ::ActiveStorage::VariantRecord # preload
15
- ::ActiveStorage::BaseController # preload
16
- ::ActiveStorage::Representations::ProxyController # preload
17
- module ::ActiveStorage
18
- # CAUTION: do not include Tenantable, it breaks things (it may set site/tenant behind the scenes,
19
- # and sets the default scope, and then later the blob can't be found
20
- class Attachment
21
- include Bp3::Core::Rqid
22
- include Bp3::Core::Sqnr
23
- include Bp3::Core::Ransackable
24
-
25
- use_sqnr_for_ordering
26
- has_paper_trail
27
-
28
- belongs_to :sites_site, class_name: 'Sites::Site', optional: true
29
- alias site sites_site
30
- alias site= sites_site=
31
- belongs_to :tenant, optional: true
32
-
33
- def self.table_name_basis
34
- table_name.gsub(/\Apublic\./, '').singularize
35
- end
10
+ # to_prepare rather than after_initialize: Active Storage's models and controllers are reloaded
11
+ # with the application's code in development, and what is added to them here goes with them.
12
+ config.to_prepare do
13
+ ::ActiveStorage::Attachment # preload
14
+ ::ActiveStorage::Blob # preload
15
+ ::ActiveStorage::VariantRecord # preload
16
+ ::ActiveStorage::BaseController # preload
17
+ ::ActiveStorage::Representations::ProxyController # preload
18
+ ::ActiveStorage::DirectUploadsController # preload
19
+ module ::ActiveStorage
20
+ # CAUTION: do not include Tenantable, it breaks things (it may set site/tenant behind the scenes,
21
+ # and sets the default scope, and then later the blob can't be found
22
+ class Attachment
23
+ include Bp3::Core::Rqid
24
+ include Bp3::Core::Sqnr
25
+ include Bp3::Core::Ransackable
26
+
27
+ use_sqnr_for_ordering
28
+ has_paper_trail
29
+
30
+ belongs_to :sites_site, class_name: 'Sites::Site', optional: true
31
+ alias site sites_site
32
+ alias site= sites_site=
33
+ belongs_to :tenant, optional: true
34
+
35
+ def self.table_name_basis
36
+ table_name.gsub(/\Apublic\./, '').singularize
36
37
  end
38
+ end
39
+
40
+ class Blob
41
+ include Bp3::Core::Rqid
42
+ include Bp3::Core::Sqnr
43
+ include Bp3::Core::Displayable
44
+ include Bp3::Core::Ransackable
37
45
 
38
- class Blob
39
- include Bp3::Core::Rqid
40
- include Bp3::Core::Sqnr
41
- include Bp3::Core::Displayable
42
- include Bp3::Core::Ransackable
46
+ # The key locates the file in storage, so it is not to be guessed at one character at a time
47
+ self.unexplorable_columns = %w[key]
43
48
 
44
- # The key locates the file in storage, so it is not to be guessed at one character at a time
45
- self.unexplorable_columns = %w[key]
49
+ use_sqnr_for_ordering
50
+ has_paper_trail
46
51
 
47
- use_sqnr_for_ordering
48
- has_paper_trail
52
+ belongs_to :sites_site, class_name: 'Sites::Site', optional: true
53
+ alias site sites_site
54
+ alias site= sites_site=
55
+ belongs_to :tenant, optional: true
56
+
57
+ def self.table_name_basis
58
+ table_name.gsub(/\Apublic\./, '').singularize
59
+ end
60
+
61
+ private
62
+
63
+ def version_filter_mask
64
+ '[FILTERED][AB]'
65
+ end
66
+ end
49
67
 
50
- belongs_to :sites_site, class_name: 'Sites::Site', optional: true
51
- alias site sites_site
52
- alias site= sites_site=
53
- belongs_to :tenant, optional: true
68
+ class VariantRecord
69
+ include Bp3::Core::Rqid
70
+ include Bp3::Core::Sqnr
71
+ include Bp3::Core::Ransackable
54
72
 
55
- def self.table_name_basis
56
- table_name.gsub(/\Apublic\./, '').singularize
57
- end
73
+ use_sqnr_for_ordering
74
+ has_paper_trail
58
75
 
59
- private
76
+ belongs_to :sites_site, class_name: 'Sites::Site', optional: true
77
+ alias site sites_site
78
+ alias site= sites_site=
79
+ belongs_to :tenant, optional: true
60
80
 
61
- def version_filter_mask
62
- '[FILTERED][AB]'
63
- end
81
+ def self.table_name_basis
82
+ table_name.gsub(/\Apublic\./, '').singularize
64
83
  end
84
+ end
65
85
 
66
- class VariantRecord
67
- include Bp3::Core::Rqid
68
- include Bp3::Core::Sqnr
69
- include Bp3::Core::Ransackable
86
+ class BaseController
87
+ include Bp3::Core::Actions
88
+ include Bp3::Core::Settings
89
+ include Bp3::Core::FeatureFlags
90
+ include Bp3::Core::Cookies
91
+
92
+ # No login is asked of anyone fetching a file. The URL is signed, and holding it is the
93
+ # permission: a visitor reading a page and an email client showing a logo have no session.
94
+ end
70
95
 
71
- use_sqnr_for_ordering
72
- has_paper_trail
96
+ # A direct upload creates a blob and hands back a signed URL to store a file at, and Rails asks
97
+ # nothing of the caller. It is for signed-in work, such as a rich text editor, so it asks for a
98
+ # login. BaseController's Bp3::Core::Actions#set_global_request_state runs before this, so the
99
+ # request state knows who is signed in.
100
+ class DirectUploadsController
101
+ before_action :require_login_for_direct_upload
73
102
 
74
- belongs_to :sites_site, class_name: 'Sites::Site', optional: true
75
- alias site sites_site
76
- alias site= sites_site=
77
- belongs_to :tenant, optional: true
103
+ private
78
104
 
79
- def self.table_name_basis
80
- table_name.gsub(/\Apublic\./, '').singularize
81
- end
105
+ def require_login_for_direct_upload
106
+ head :unauthorized unless grsc.current_login
82
107
  end
108
+ end
83
109
 
84
- class BaseController
110
+ module Representations
111
+ class ProxyController
85
112
  include Bp3::Core::Actions
86
113
  include Bp3::Core::Settings
87
114
  include Bp3::Core::FeatureFlags
88
115
  include Bp3::Core::Cookies
89
116
 
90
- # TODO: is this needed?
91
- # before_action :authenticate_root!
92
- end
93
-
94
- module Representations
95
- class ProxyController
96
- include Bp3::Core::Actions
97
- include Bp3::Core::Settings
98
- include Bp3::Core::FeatureFlags
99
- include Bp3::Core::Cookies
100
-
101
- # using authenticate_root! breaks things
102
- # TODO: is this needed?
103
- # before_action :authenticate_root!
104
- end
117
+ # Like ActiveStorage::BaseController, asks no login: the signed URL is the permission.
105
118
  end
106
119
  end
107
120
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bp3
4
4
  module Activestorage
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bp3-activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wim den Braven
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubygems_version: 4.0.4
183
+ rubygems_version: 4.0.23
184
184
  specification_version: 4
185
185
  summary: bp3-activestorage adapts activestorage for BP3 (persuavis/black_phoebe_3).
186
186
  test_files: []