Compare commits

..
38 Commits
Author SHA1 Message Date
Stanko Krtalić 94692026d3 Merge pull request #122 from basecamp/unfurl-blind-ssrf
Security: disallow blind SSRF to link-local IPs via URL unfurling
2025-12-03 08:12:24 +01:00
Stanko K.R. 0672673916 Disallow SSRF via IPv6 addresses mapped to IPv4 addresses 2025-12-03 08:08:34 +01:00
Jeremy Daer 5667262d1c Security: disallow blind SSRF to link-local IPs via URL unfurling 2025-12-02 21:33:44 -08:00
Stanko Krtalić 1babf3f9ed Merge pull request #121 from basecamp/fix-crash-on-mentions-when-upgrading-from-rails-7-to-8
Parse Rails 7 GIDs
2025-12-02 11:37:51 +01:00
Stanko K.R. 4d04f9beee Use urlsafe base64 decode 2025-12-02 11:34:12 +01:00
Stanko K.R. bebe518c74 Parse Rails 7 GIDs 2025-12-02 11:06:23 +01:00
Stanko K.R. 13897eac59 Set title on new releases to be the version number 2025-12-02 08:36:15 +01:00
Stanko Krtalić 6bb0ee2436 Merge pull request #120 from basecamp/allow-restricting-new-room-creation-to-admins
Add new has_json to add Account#settings to restrict room creation to only administrators
2025-12-02 08:27:29 +01:00
Stanko K.R. 550d4c75bd Invert the icon color in dark mode 2025-12-02 08:22:32 +01:00
Jason Zimdars 7b7b3f8a67 Rework toggle as switch 2025-12-01 23:13:54 -06:00
Stanko K.R. aec8747710 Fix failing system tests
Something broke when the dependencies were updated so I copied over the Gemfile.lock file from main - which is known to work
2025-12-01 16:34:51 +01:00
Stanko K.R. 71b5edae01 Run migrations 2025-12-01 15:31:53 +01:00
Stanko K.R. b1325ccee7 Bump Redis 2025-12-01 15:31:07 +01:00
David Heinemeier Hansson 5266ffc049 Always just go through the settings object 2025-12-01 15:26:06 +01:00
David Heinemeier Hansson bd3b0c5988 Not needed 2025-12-01 15:26:06 +01:00
David Heinemeier Hansson e8626f9d5d Use rails edge that now includes the feature 2025-12-01 15:26:06 +01:00
David Heinemeier Hansson 796195c2cc Give up on the auto delegation to get a cleaner API 2025-12-01 15:26:06 +01:00
David Heinemeier Hansson 559629537b We don't need to specify the default any more 2025-12-01 15:26:06 +01:00
David Heinemeier Hansson 42c411b660 Use upstream version of has_json 2025-12-01 15:25:39 +01:00
David Heinemeier Hansson 6c59b8c82b Use public_send instead of send 2025-12-01 15:25:39 +01:00
David Heinemeier Hansson 20ba1cf2ae Ensure mutable string is used to prevent warning 2025-12-01 15:25:06 +01:00
David Heinemeier Hansson d323c3cfc0 Now required to be explicitly included
Not sure why
2025-12-01 15:23:57 +01:00
David Heinemeier Hansson 53671b48e0 Update to latest Rails 8.2.0 alpha 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 66b4e41281 Rename 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 6476bab4cc Use consistent yield naming 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson f7c3aaa2a9 Allow for default values 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 593f8dd04c No need for self 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson d3b6507ce2 Layer on top a more pleasant API for the default case 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 32be03a240 Rely on method missing 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 8e94a4aa1e Better wording 2025-12-01 15:23:23 +01:00
David Heinemeier Hansson 15db4033bc Enforce restriction to create new rooms 2025-12-01 15:22:37 +01:00
David Heinemeier Hansson f56e33e323 Fix fetching missing values 2025-12-01 15:22:36 +01:00
David Heinemeier Hansson bea2c89c2b Add new has_json to add Account#settings to restrict room creation to only administrators 2025-12-01 15:22:36 +01:00
Stanko Krtalić b7c7d99dcd Merge pull request #119 from basecamp/upgrade-to-rails-8.2.alpha
Upgrade to Rails 8.2.0.alpha
2025-12-01 13:59:28 +01:00
Stanko K.R. 6197f20a31 Upgrade to Rails 8.2.0.alpha 2025-12-01 13:50:53 +01:00
Stanko Krtalić 02023b0f0c Merge pull request #118 from basecamp/modernize-scripts
Create release first
2025-12-01 12:22:04 +01:00
Stanko K.R. af81a4c1ac Create release first 2025-12-01 12:21:08 +01:00
Stanko Krtalić d4a132865a Merge pull request #117 from basecamp/modernize-scripts
Modernize scripts
2025-12-01 12:01:13 +01:00
22 changed files with 250 additions and 105 deletions

No files matched your search

+3 -1
View File
@@ -4,9 +4,11 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Rails # Rails
gem "rails", github: "rails/rails", branch: "main" gem "rails", github: "rails/rails", branch: "main"
gem "ostruct"
gem "benchmark"
# Drivers # Drivers
gem "sqlite3", "~> 2.7" gem "sqlite3"
gem "redis", "~> 5.4" gem "redis", "~> 5.4"
# Deployment # Deployment
+85 -80
View File
@@ -26,32 +26,32 @@ GIT
GIT GIT
remote: https://github.com/rails/rails.git remote: https://github.com/rails/rails.git
revision: 02660cd9e84aa57c6cf3bf6e08cc18e18292e2fa revision: 1a02651ac37fb64b4de2a2b73461d86acf9c98fb
branch: main branch: main
specs: specs:
actioncable (8.1.0.alpha) actioncable (8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.1.0.alpha) actionmailbox (8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
activejob (= 8.1.0.alpha) activejob (= 8.2.0.alpha)
activerecord (= 8.1.0.alpha) activerecord (= 8.2.0.alpha)
activestorage (= 8.1.0.alpha) activestorage (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.1.0.alpha) actionmailer (8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
actionview (= 8.1.0.alpha) actionview (= 8.2.0.alpha)
activejob (= 8.1.0.alpha) activejob (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.1.0.alpha) actionpack (8.2.0.alpha)
actionview (= 8.1.0.alpha) actionview (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@@ -59,65 +59,65 @@ GIT
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.0.alpha) actiontext (8.2.0.alpha)
action_text-trix (~> 2.1.15) action_text-trix (~> 2.1.15)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
activerecord (= 8.1.0.alpha) activerecord (= 8.2.0.alpha)
activestorage (= 8.1.0.alpha) activestorage (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.1.0.alpha) actionview (8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.1.0.alpha) activejob (8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.1.0.alpha) activemodel (8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
activerecord (8.1.0.alpha) activerecord (8.2.0.alpha)
activemodel (= 8.1.0.alpha) activemodel (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.1.0.alpha) activestorage (8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
activejob (= 8.1.0.alpha) activejob (= 8.2.0.alpha)
activerecord (= 8.1.0.alpha) activerecord (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.1.0.alpha) activesupport (8.2.0.alpha)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
json
logger (>= 1.4.2) logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1) uri (>= 0.13.1)
rails (8.1.0.alpha) rails (8.2.0.alpha)
actioncable (= 8.1.0.alpha) actioncable (= 8.2.0.alpha)
actionmailbox (= 8.1.0.alpha) actionmailbox (= 8.2.0.alpha)
actionmailer (= 8.1.0.alpha) actionmailer (= 8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
actiontext (= 8.1.0.alpha) actiontext (= 8.2.0.alpha)
actionview (= 8.1.0.alpha) actionview (= 8.2.0.alpha)
activejob (= 8.1.0.alpha) activejob (= 8.2.0.alpha)
activemodel (= 8.1.0.alpha) activemodel (= 8.2.0.alpha)
activerecord (= 8.1.0.alpha) activerecord (= 8.2.0.alpha)
activestorage (= 8.1.0.alpha) activestorage (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.1.0.alpha) railties (= 8.2.0.alpha)
railties (8.1.0.alpha) railties (8.2.0.alpha)
actionpack (= 8.1.0.alpha) actionpack (= 8.2.0.alpha)
activesupport (= 8.1.0.alpha) activesupport (= 8.2.0.alpha)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@@ -135,8 +135,8 @@ GEM
ast (2.4.3) ast (2.4.3)
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.4.1) benchmark (0.5.0)
bigdecimal (3.2.2) bigdecimal (3.3.1)
brakeman (7.1.1) brakeman (7.1.1)
racc racc
builder (3.3.0) builder (3.3.0)
@@ -151,17 +151,17 @@ GEM
xpath (~> 3.2) xpath (~> 3.2)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.3) connection_pool (2.5.5)
crack (1.0.0) crack (1.0.0)
bigdecimal bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
date (3.4.1) date (3.5.0)
debug (1.11.0) debug (1.11.0)
irb (~> 1.10) irb (~> 1.10)
reline (>= 0.3.8) reline (>= 0.3.8)
drb (2.2.3) drb (2.2.3)
erb (5.0.2) erb (6.0.0)
erubi (1.13.1) erubi (1.13.1)
faker (3.5.2) faker (3.5.2)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
@@ -172,7 +172,7 @@ GEM
geared_pagination (1.2.0) geared_pagination (1.2.0)
activesupport (>= 5.0) activesupport (>= 5.0)
addressable (>= 2.5.0) addressable (>= 2.5.0)
globalid (1.2.1) globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
hashdiff (1.2.0) hashdiff (1.2.0)
i18n (1.14.7) i18n (1.14.7)
@@ -181,7 +181,7 @@ GEM
mini_magick (>= 4.9.5, < 6) mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
io-console (0.8.1) io-console (0.8.1)
irb (1.15.2) irb (1.15.3)
pp (>= 0.6.0) pp (>= 0.6.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
@@ -201,17 +201,18 @@ GEM
loofah (2.24.1) loofah (2.24.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.9.0)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.0.4) marcel (1.1.0)
matrix (0.4.3) matrix (0.4.3)
mini_magick (5.3.1) mini_magick (5.3.1)
logger logger
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.5) minitest (5.26.2)
mocha (2.7.1) mocha (2.7.1)
ruby2_keywords (>= 0.0.5) ruby2_keywords (>= 0.0.5)
mono_logger (1.1.2) mono_logger (1.1.2)
@@ -220,7 +221,7 @@ GEM
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
net-http-persistent (4.0.6) net-http-persistent (4.0.6)
connection_pool (~> 2.2, >= 2.2.4) connection_pool (~> 2.2, >= 2.2.4)
net-imap (0.5.9) net-imap (0.5.12)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@@ -229,16 +230,17 @@ GEM
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.5)
nokogiri (1.18.9-aarch64-linux-gnu) nokogiri (1.18.10-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin) nokogiri (1.18.10-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-darwin) nokogiri (1.18.10-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu) nokogiri (1.18.10-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
openssl (3.3.0) openssl (3.3.0)
ostruct (0.6.3)
parallel (1.27.0) parallel (1.27.0)
parser (3.3.9.0) parser (3.3.9.0)
ast (~> 2.4.1) ast (~> 2.4.1)
@@ -246,7 +248,7 @@ GEM
platform_agent (1.0.1) platform_agent (1.0.1)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
useragent (~> 0.16.3) useragent (~> 0.16.3)
pp (0.6.2) pp (0.6.3)
prettyprint prettyprint
prettyprint (0.2.0) prettyprint (0.2.0)
prism (1.4.0) prism (1.4.0)
@@ -257,7 +259,7 @@ GEM
puma (6.6.1) puma (6.6.1)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.2.0) rack (3.2.4)
rack-protection (4.1.1) rack-protection (4.1.1)
base64 (>= 0.1.0) base64 (>= 0.1.0)
logger (>= 1.6.0) logger (>= 1.6.0)
@@ -281,10 +283,11 @@ GEM
activesupport (> 3.1) activesupport (> 3.1)
railties (> 3.1) railties (> 3.1)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.3.0) rake (13.3.1)
rdoc (6.14.2) rdoc (6.16.1)
erb erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort
redis (5.4.1) redis (5.4.1)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.25.2) redis-client (0.25.2)
@@ -292,7 +295,7 @@ GEM
redis-namespace (1.11.0) redis-namespace (1.11.0)
redis (>= 4) redis (>= 4)
regexp_parser (2.11.2) regexp_parser (2.11.2)
reline (0.6.2) reline (0.6.3)
io-console (~> 0.5) io-console (~> 0.5)
resque (2.7.0) resque (2.7.0)
mono_logger (~> 1) mono_logger (~> 1)
@@ -367,21 +370,21 @@ GEM
sqlite3 (2.7.3-x86_64-linux-gnu) sqlite3 (2.7.3-x86_64-linux-gnu)
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.7) stringio (3.1.8)
thor (1.4.0) thor (1.4.0)
thruster (0.1.15-aarch64-linux) thruster (0.1.15-aarch64-linux)
thruster (0.1.15-arm64-darwin) thruster (0.1.15-arm64-darwin)
thruster (0.1.15-x86_64-darwin) thruster (0.1.15-x86_64-darwin)
thruster (0.1.15-x86_64-linux) thruster (0.1.15-x86_64-linux)
tilt (2.6.1) tilt (2.6.1)
timeout (0.4.3) timeout (0.4.4)
tsort (0.2.0) tsort (0.2.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (3.1.5) unicode-display_width (3.1.5)
unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4) unicode-emoji (4.0.4)
uri (1.0.3) uri (1.1.1)
useragent (0.16.11) useragent (0.16.11)
web-push (3.0.2) web-push (3.0.2)
jwt (~> 3.0) jwt (~> 3.0)
@@ -407,6 +410,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
bcrypt bcrypt
benchmark
brakeman brakeman
capybara capybara
debug debug
@@ -418,6 +422,7 @@ DEPENDENCIES
kredis kredis
mocha mocha
net-http-persistent net-http-persistent
ostruct
platform_agent platform_agent
propshaft! propshaft!
puma (~> 6.6) puma (~> 6.6)
@@ -431,7 +436,7 @@ DEPENDENCIES
selenium-webdriver selenium-webdriver
sentry-rails sentry-rails
sentry-ruby sentry-ruby
sqlite3 (~> 2.7) sqlite3
stimulus-rails stimulus-rails
thruster thruster
turbo-rails! turbo-rails!
+1 -1
View File
@@ -17,7 +17,7 @@ class AccountsController < ApplicationController
end end
def account_params def account_params
params.require(:account).permit(:name, :logo) params.require(:account).permit(:name, :logo, settings: {})
end end
def account_users def account_users
@@ -3,6 +3,7 @@ class Rooms::ClosedsController < RoomsController
before_action :ensure_can_administer, only: %i[ update ] before_action :ensure_can_administer, only: %i[ update ]
before_action :remember_last_room_visited, only: :show before_action :remember_last_room_visited, only: :show
before_action :force_room_type, only: %i[ edit update ] before_action :force_room_type, only: %i[ edit update ]
before_action :ensure_permission_to_create_rooms, only: %i[ new create ]
DEFAULT_ROOM_NAME = "New room" DEFAULT_ROOM_NAME = "New room"
@@ -3,6 +3,7 @@ class Rooms::OpensController < RoomsController
before_action :ensure_can_administer, only: %i[ update ] before_action :ensure_can_administer, only: %i[ update ]
before_action :remember_last_room_visited, only: :show before_action :remember_last_room_visited, only: :show
before_action :force_room_type, only: %i[ edit update ] before_action :force_room_type, only: %i[ edit update ]
before_action :ensure_permission_to_create_rooms, only: %i[ new create ]
DEFAULT_ROOM_NAME = "New room" DEFAULT_ROOM_NAME = "New room"
+6
View File
@@ -31,6 +31,12 @@ class RoomsController < ApplicationController
head :forbidden unless Current.user.can_administer?(@room) head :forbidden unless Current.user.can_administer?(@room)
end end
def ensure_permission_to_create_rooms
if Current.account.settings.restrict_room_creation_to_administrators? && !Current.user.administrator?
head :forbidden
end
end
def find_messages def find_messages
messages = @room.messages.with_creator.with_attachment_details.with_boosts messages = @room.messages.with_creator.with_attachment_details.with_boosts
+1
View File
@@ -2,4 +2,5 @@ class Account < ApplicationRecord
include Joinable include Joinable
has_one_attached :logo has_one_attached :logo
has_json :settings, restrict_room_creation_to_administrators: false
end end
+1 -1
View File
@@ -53,7 +53,7 @@ class Webhook < ApplicationRecord
end end
def extract_text_from(response) def extract_text_from(response)
response.body.dup.force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) String.new(response.body).force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ])
end end
def receive_text_reply_to(room, text:) def receive_text_reply_to(room, text:)
+24
View File
@@ -64,6 +64,30 @@
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<div class="margin-block-start pad-block pad-inline-double fill-shade border-radius">
<%= form_with model: @account, method: :put, data: { controller: "form" }, class: "flex align-center gap center" do |form| %>
<div class="flex-item-grow flex align-center gap txt-align-start">
<%= image_tag "crown.svg", class: "colorize--black", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms
</div>
<%= form.fields_for :settings, @account.settings do |settings_form| %>
<%= settings_form.hidden_field :restrict_room_creation_to_administrators,
value: !Current.account.settings.restrict_room_creation_to_administrators? %>
<% end %>
<label class="switch">
<input type="checkbox"
class="switch__input"
<%= "checked" if Current.account.settings.restrict_room_creation_to_administrators? %>
data-action="change->form#submit">
<span class="switch__btn round"></span>
<span class="for-screen-reader">
Must be admin to create new rooms
</span>
</label>
<% end %>
</div>
<% else %> <% else %>
<%= account_logo_tag style: "txt-xx-large center" %> <%= account_logo_tag style: "txt-xx-large center" %>
<h1 class="flex-item-grow txt-x-large"><%= @account.name %></h1> <h1 class="flex-item-grow txt-x-large"><%= @account.name %></h1>
+4 -2
View File
@@ -36,8 +36,10 @@
<% end %> <% end %>
</div> </div>
<%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> <% if Current.user.administrator? || !Current.account.settings.restrict_room_creation_to_administrators? %>
<%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %>
<%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %>
<% end %>
<% end %> <% end %>
</div> </div>
+11 -14
View File
@@ -45,23 +45,20 @@ on(:local) do
end end
end end
announcing "Tagging as #{VERSION}" do announcing "Creating GitHub release v#{VERSION}..." do
on(:local) do notes_file = "tmp/release_notes.md"
execute :git, :fetch, :origin, "--tags"
unless system "git tag -a v#{VERSION} -m 'Version #{VERSION}\n\n- list important changes here' -e" on(:local) do
abort "Failed to create tag; check that the version doesn't already exist" execute :mkdir, "-p", "tmp"
File.write(notes_file, "# What's Changed\n\n- \n")
system ENV["EDITOR"] || "vim", notes_file
unless system "gh", "release", "create", "v#{VERSION}", "--title", "v#{VERSION}", "--notes-file", notes_file
abort "Failed to create release; check that the version doesn't already exist"
end end
unless test :git, :push, "--tags" execute :rm, notes_file
abort "Failed to push tag; check that the version doesn't already exist"
end
end
end
announcing "Creating GitHub release..." do
on(:local) do
execute :gh, :release, :create, "v#{VERSION}", "--notes-from-tag"
end end
end end
+1 -1
View File
@@ -7,7 +7,7 @@ Bundler.require(*Rails.groups)
module Campfire module Campfire
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 8.1 config.load_defaults 8.2
# Please, add to the `ignore` list any other `lib` subdirectories that do # Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded. # not contain `.rb` files, or that should not be reloaded or eager loaded.
@@ -0,0 +1,5 @@
class AddAccountSettings < ActiveRecord::Migration[7.2]
def change
add_column :accounts, :settings, :json
end
end
Generated
+2 -1
View File
@@ -10,12 +10,13 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_11_26_115722) do ActiveRecord::Schema[8.2].define(version: 2025_11_26_130131) do
create_table "accounts", force: :cascade do |t| create_table "accounts", force: :cascade do |t|
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.text "custom_styles" t.text "custom_styles"
t.string "join_code", null: false t.string "join_code", null: false
t.string "name", null: false t.string "name", null: false
t.json "settings"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
+8 -3
View File
@@ -16,13 +16,18 @@ ActiveSupport.on_load(:action_text_content) do
decoded_gid = if data = encoded_message.dig("_rails", "data") decoded_gid = if data = encoded_message.dig("_rails", "data")
data data
elsif data = encoded_message.dig("_rails", "message")
# Rails 7 used an older format of GID that serialized the payload using Marshall
# Since we intentionally skip signature verification, we can't safely unmarshal the data
# To work around this, we manually extract the GID from the marshaled data
Base64.urlsafe_decode64(data).match(%r{(gid://campfire/[^/]+/\d+)})&.to_s
else else
nil nil
end end
model = GlobalID.find(decoded_gid) if model = GlobalID.find(decoded_gid)
model.model_name.to_s.in?(ATTACHABLES_PERMITTED_WITH_INVALID_SIGNATURES) ? model : nil
model.model_name.to_s.in?(ATTACHABLES_PERMITTED_WITH_INVALID_SIGNATURES) ? model : nil end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
nil nil
+1 -1
View File
@@ -16,7 +16,7 @@ module RestrictedHTTP
def private_ip?(ip) def private_ip?(ip)
IPAddr.new(ip).then do |ipaddr| IPAddr.new(ip).then do |ipaddr|
ipaddr.private? || ipaddr.loopback? || LOCAL_IP.include?(ipaddr) ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? || ipaddr.ipv4_mapped? || LOCAL_IP.include?(ipaddr)
end end
rescue IPAddr::InvalidAddressError rescue IPAddr::InvalidAddressError
true true
@@ -29,6 +29,16 @@ class Rooms::ClosedsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to room_url(Room.last) assert_redirected_to room_url(Room.last)
end end
test "create forbidden by non-admin when account restricts creation to admins" do
accounts(:signal).settings.restrict_room_creation_to_administrators = true
accounts(:signal).save!
sign_in :jz
post rooms_closeds_url, params: { room: { name: "My New Room" }, user_ids: [ users(:david).id, users(:kevin).id, users(:jason).id ] }
assert_response :forbidden
end
test "update with membership revisions" do test "update with membership revisions" do
assert_difference -> { rooms(:designers).reload.users.count }, -1 do assert_difference -> { rooms(:designers).reload.users.count }, -1 do
put rooms_closed_url(rooms(:designers)), params: { put rooms_closed_url(rooms(:designers)), params: {
@@ -24,6 +24,15 @@ class Rooms::OpensControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to room_url(Room.last) assert_redirected_to room_url(Room.last)
end end
test "create forbidden by non-admin when account restricts creation to admins" do
accounts(:signal).settings.restrict_room_creation_to_administrators = true
accounts(:signal).save!
sign_in :jz
post rooms_opens_url, params: { room: { name: "My New Room" } }
assert_response :forbidden
end
test "only admins or creators can update" do test "only admins or creators can update" do
sign_in :jz sign_in :jz
@@ -0,0 +1,39 @@
require "test_helper"
class ActionText::AttachmentTest < ActiveSupport::TestCase
setup do
@user = users(:david)
end
test "from_node" do
html = %Q(<action-text-attachment sgid="#{@user.attachable_sgid}"></action-text-attachment>)
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
attachment = ActionText::Attachment.from_node(node)
assert_equal @user, attachment.attachable
end
test "from_node with a Rails 7 SGID" do
gid = @user.to_gid.to_s
marshaled_gid = Base64.urlsafe_encode64(Marshal.dump(gid))
rails7_payload = { "_rails" => { "message" => marshaled_gid, "exp" => nil, "pur" => "attachable" } }
rails7_message = Base64.strict_encode64(JSON.generate(rails7_payload))
rails7_sgid = "#{rails7_message}--invalidsignature"
html = %Q(<action-text-attachment sgid="#{rails7_sgid}"></action-text-attachment>)
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
attachment = ActionText::Attachment.from_node(node)
assert_equal @user, attachment.attachable
end
test "from_node with an invalid SGID" do
room = rooms(:pets).tap { |r| r.extend ActionText::Attachable }
html = %Q(<action-text-attachment sgid="#{room.attachable_sgid}invalid"></action-text-attachment>)
node = ActionText::Fragment.wrap(html).find_all(ActionText::Attachment.tag_name).first
attachment = ActionText::Attachment.from_node(node)
assert_kind_of ActionText::Attachables::MissingAttachable, attachment.attachable
end
end
+14
View File
@@ -1,4 +1,18 @@
require "test_helper" require "test_helper"
class AccountTest < ActiveSupport::TestCase class AccountTest < ActiveSupport::TestCase
test "settings" do
accounts(:signal).settings.restrict_room_creation_to_administrators = true
assert accounts(:signal).settings.restrict_room_creation_to_administrators?
assert_equal({ "restrict_room_creation_to_administrators" => true }, accounts(:signal)[:settings])
accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "true" })
assert accounts(:signal).reload.settings.restrict_room_creation_to_administrators?
accounts(:signal).settings.restrict_room_creation_to_administrators = false
assert_not accounts(:signal).settings.restrict_room_creation_to_administrators?
assert_equal({ "restrict_room_creation_to_administrators" => false }, accounts(:signal)[:settings])
accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" })
assert_not accounts(:signal).reload.settings.restrict_room_creation_to_administrators?
end
end end
+22
View File
@@ -21,6 +21,28 @@ class Opengraph::LocationTest < ActiveSupport::TestCase
assert_equal [ "is not public" ], location.errors[:url] assert_equal [ "is not public" ], location.errors[:url]
end end
test "link-local addresses are blocked" do
Resolv.stubs(:getaddress).with("metadata.internal").returns("169.254.169.254")
location = Opengraph::Location.new("https://metadata.internal")
assert_not location.valid?
assert_equal [ "is not public" ], location.errors[:url]
end
test "IPv6 addresses mapped to IPv4 addresses are blocked" do
Resolv.stubs(:getaddress).with("metadata.internal").returns("::ffff:192.168.1.1")
location = Opengraph::Location.new("https://metadata.internal")
assert_not location.valid?
assert_equal [ "is not public" ], location.errors[:url]
Resolv.stubs(:getaddress).with("metadata.internal").returns("::ffff:c0a8:0101")
location = Opengraph::Location.new("https://metadata.internal")
assert_not location.valid?
assert_equal [ "is not public" ], location.errors[:url]
end
test "avoid reading file urls when expecting HTML" do test "avoid reading file urls when expecting HTML" do
large_file = Opengraph::Location.new("https://www.example.com/100gb.zip") large_file = Opengraph::Location.new("https://www.example.com/100gb.zip")
+1
View File
@@ -5,6 +5,7 @@ require "rails/test_help"
require "minitest/unit" require "minitest/unit"
require "mocha/minitest" require "mocha/minitest"
require "webmock/minitest" require "webmock/minitest"
require "turbo/broadcastable/test_helper"
WebMock.enable! WebMock.enable!