Skip to content

Commit

Permalink
Merge branch 'develop' into templates
Browse files Browse the repository at this point in the history
  • Loading branch information
Oaphi committed Sep 30, 2023
2 parents b4c3dff + 254740e commit 24e0a58
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ GEM
actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9)
public_suffix (5.0.0)
puma (5.6.5)
puma (5.6.7)
nio4r (~> 2.0)
racc (1.7.1)
rack (2.2.8)
Expand Down
6 changes: 5 additions & 1 deletion app/assets/javascripts/privileges.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ $(() => {
const $input = $td.find('.js-privilege-edit');
const name = $input.data('name');
const type = $input.data('type');
const value = parseFloat($input.val() || '') || null;

// incorrect input values will cause rawValue to be NaN
const rawValue = parseFloat($input.val())

const value = Number.isNaN(rawValue) ? null : rawValue;

const resp = await fetch(`/admin/privileges/${name}`, {
method: 'POST',
Expand Down
17 changes: 17 additions & 0 deletions app/controllers/active_storage/base_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class ActiveStorage::BaseController < ActionController::Base
before_action :enforce_signed_in
include ActiveStorage::SetCurrent
protect_from_forgery with: :exception

self.etag_with_template_digest = false

protected

def enforce_signed_in
# If not restricted, the user is signed in or the environment is test, allow all content.
return true if !SiteSetting['RestrictedAccess'] || user_signed_in? || Rails.env.test?

redirect_to '/', status: :forbidden
false
end
end
34 changes: 34 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :set_globals
before_action :enforce_signed_in, unless: :devise_controller?
before_action :check_if_warning_or_suspension_pending
before_action :distinguish_fake_community
before_action :stop_the_awful_troll
Expand Down Expand Up @@ -326,6 +327,39 @@ def enforce_2fa
end
end

# Ensure that the user is signed in before showing any content. If the user is not signed in, display the main page.
#
# Exceptions:
# - 4** and 500 error pages
# - stylesheets and javascript
# - assets
# - /help, /policy, /help/* and /policy/*
def enforce_signed_in
# If not restricted, the user is signed in or the environment is test, allow all content.
return true if !SiteSetting['RestrictedAccess'] || user_signed_in? || Rails.env.test?

# Allow error pages and assets
path = request.fullpath
return true if path.start_with?('/4') || path == '/500' ||
path.start_with?('/assets/') ||
path.end_with?('.css') || path.end_with?('.js')

# Make available to controller that the we should not leak posts in the sidebar
@prevent_sidebar = true

# Allow /help (help center), /help/* and /policy/* depending on settings
help = SiteSetting['RestrictedAccessHelpPagesPublic']
policy = SiteSetting['RestrictedAccessPolicyPagesPublic']
return true if (help && path.start_with?('/help/')) ||
(policy && path.start_with?('/policy/')) ||
(path == '/help' && (help || policy))

store_location_for(:user, request.fullpath) if storable_location?

render 'errors/restricted_content', layout: 'without_sidebar', status: :forbidden
false
end

def block_write_request(**add)
respond_to do |format|
format.html do
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ def document
if @post&.help_category == '$Moderator' && !current_user&.is_moderator
not_found
end

# Make sure we don't leak featured posts in the sidebar
render layout: 'without_sidebar' if @prevent_sidebar
end

def upload
Expand All @@ -431,6 +434,9 @@ def help_center
.order(:help_ordering, :title)
.group_by(&:post_type_id)
.transform_values { |posts| posts.group_by { |p| p.help_category.presence } }

# Make sure we don't leak featured posts in the sidebar
render layout: 'without_sidebar' if @prevent_sidebar
end

def change_category
Expand Down
101 changes: 56 additions & 45 deletions app/controllers/tags_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,57 +126,68 @@ def merge

@subordinate = Tag.find params[:merge_with_id]

AuditLog.moderator_audit event_type: 'tag_merge', related: @primary, user: current_user,
comment: "#{@subordinate.name} (#{@subordinate.id}) into #{@primary.name} (#{@primary.id})"

# Take the tag off posts
posts_sql = 'UPDATE posts INNER JOIN posts_tags ON posts.id = posts_tags.post_id ' \
'SET posts.tags_cache = REPLACE(posts.tags_cache, ?, ?) ' \
'WHERE posts_tags.tag_id = ?'
exec([posts_sql, "\n- #{@subordinate.name}", "\n- #{@primary.name}", @subordinate.id])

# Break hierarchies
tags_sql = 'UPDATE tags SET parent_id = NULL WHERE parent_id = ?'
exec([tags_sql, @subordinate.id])

# Remove references to the tag
sql = 'UPDATE IGNORE $TABLENAME SET tag_id = ? WHERE tag_id = ?'
exec([sql.gsub('$TABLENAME', 'posts_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'categories_required_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'categories_topic_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'post_history_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'suggested_edits_tags'), @primary.id, @subordinate.id])
exec([sql.gsub('$TABLENAME', 'suggested_edits_before_tags'), @primary.id, @subordinate.id])

# Nuke it from orbit
@subordinate.destroy
Post.transaction do
AuditLog.moderator_audit event_type: 'tag_merge', related: @primary, user: current_user, comment:
"#{@subordinate.name} (#{@subordinate.id}) into #{@primary.name} (#{@primary.id})"

# Replace subordinate with primary, except when a post already has primary (to avoid giving them a duplicate tag)
posts_sql = 'UPDATE posts INNER JOIN posts_tags ON posts.id = posts_tags.post_id ' \
'SET posts.tags_cache = REPLACE(posts.tags_cache, ?, ?) ' \
'WHERE posts_tags.tag_id = ? ' \
'AND posts_tags.post_id NOT IN (SELECT post_id FROM posts_tags WHERE tag_id = ?)'
exec_sql([posts_sql, "\n- #{@subordinate.name}\n", "\n- #{@primary.name}\n", @subordinate.id, @primary.id])

# Remove the subordinate tag from posts that still have it (the ones that were excluded from our previous query)
posts2_sql = 'UPDATE posts INNER JOIN posts_tags ON posts.id = posts_tags.post_id ' \
'SET posts.tags_cache = REPLACE(posts.tags_cache, ?, ?) ' \
'WHERE posts_tags.tag_id = ?'
exec_sql([posts2_sql, "\n- #{@subordinate.name}\n", "\n", @subordinate.id])

# Break hierarchies
tags_sql = 'UPDATE tags SET parent_id = NULL WHERE parent_id = ?'
exec_sql([tags_sql, @subordinate.id])

# Remove references to the tag
sql = 'UPDATE IGNORE $TABLENAME SET tag_id = ? WHERE tag_id = ?'
exec_sql([sql.gsub('$TABLENAME', 'posts_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'categories_required_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'categories_topic_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'post_history_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'suggested_edits_tags'), @primary.id, @subordinate.id])
exec_sql([sql.gsub('$TABLENAME', 'suggested_edits_before_tags'), @primary.id, @subordinate.id])

# Nuke it from orbit
@subordinate.destroy
end

flash[:success] = "Merged #{@subordinate.name} into #{@primary.name}."
redirect_to tag_path(id: @category.id, tag_id: @primary.id)
end

def nuke
AuditLog.admin_audit event_type: 'tag_nuke', related: @tag, user: current_user,
comment: "#{@tag.name} (#{@tag.id})"

tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
'post_history_tags', 'suggested_edits_tags', 'suggested_edits_before_tags']

# Remove tag from caches
caches_sql = 'UPDATE posts INNER JOIN posts_tags ON posts.id = posts_tags.post_id ' \
'SET posts.tags_cache = REPLACE(posts.tags_cache, ?, ?) ' \
'WHERE posts_tags.tag_id = ?'
exec([caches_sql, "\n- #{@tag.name}", '', @tag.id])

# Delete all references to the tag
tables.each do |tbl|
sql = "DELETE FROM #{tbl} WHERE tag_id = ?"
exec([sql, @tag.id])
end
Post.transaction do
AuditLog.admin_audit event_type: 'tag_nuke', related: @tag, user: current_user,
comment: "#{@tag.name} (#{@tag.id})"

tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
'post_history_tags', 'suggested_edits_tags', 'suggested_edits_before_tags']

# Remove tag from caches
caches_sql = 'UPDATE posts INNER JOIN posts_tags ON posts.id = posts_tags.post_id ' \
'SET posts.tags_cache = REPLACE(posts.tags_cache, ?, ?) ' \
'WHERE posts_tags.tag_id = ?'
exec_sql([caches_sql, "\n- #{@tag.name}\n", "\n", @tag.id])

# Delete all references to the tag
tables.each do |tbl|
sql = "DELETE FROM #{tbl} WHERE tag_id = ?"
exec_sql([sql, @tag.id])
end

# Nuke it
@tag.destroy
# Nuke it
@tag.destroy
end

flash[:success] = "Deleted #{@tag.name}"
redirect_to category_tags_path(@category)
Expand All @@ -199,7 +210,7 @@ def tag_params
tag_synonyms_attributes: [:id, :name, :_destroy])
end

def exec(sql_array)
def exec_sql(sql_array)
ApplicationRecord.connection.execute(ActiveRecord::Base.sanitize_sql_array(sql_array))
end
end
23 changes: 18 additions & 5 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ def filters_json

def filters
respond_to do |format|
format.html
format.html do
authenticate_user!
end
format.json do
render json: filters_json
end
Expand Down Expand Up @@ -342,14 +344,25 @@ def edit_profile
render layout: 'without_sidebar'
end

def validate_profile_website(profile_params)
uri = profile_params[:website]

if URI.parse(uri).instance_of?(URI::Generic)
# URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be
# parsed correctly in the view later on.
profile_params[:website] = "https://#{uri}"
end
rescue URI::InvalidURIError
profile_params.delete(:website)
flash[:danger] = 'Invalid profile website link.'
end

def update_profile
profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :twitter, :discord)
profile_params[:twitter] = profile_params[:twitter].delete('@')

if profile_params[:website].present? && URI.parse(profile_params[:website]).instance_of?(URI::Generic)
# URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be
# parsed correctly in the view later on.
profile_params[:website] = "https://#{profile_params[:website]}"
if profile_params[:website].present?
validate_profile_website(profile_params)
end

@user = current_user
Expand Down
2 changes: 2 additions & 0 deletions app/mailers/subscription_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class SubscriptionMailer < ApplicationMailer
helper UsersHelper

def subscription
@subscription = params[:subscription]
@questions = @subscription.questions&.includes(:user) || []
Expand Down
20 changes: 20 additions & 0 deletions app/views/errors/restricted_content.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<%= content_for :title, "Welcome to #{SiteSetting['SiteName']}" %>

<%- if request.fullpath != '/' %>
<div class="notice is-danger">
<div class="container">
<p>You need to sign in before you can access this site.</p>
</div>
</div>
<% end %>

<%= raw(sanitize(render_markdown(SiteSetting['RestrictedAccessFrontPageText']), scrubber: scrubber)) %>

<%- if sso_sign_in_enabled? %>
<%= link_to "SSO Sign in", new_saml_user_session_path, class: 'button is-extremely-large is-filled' %><br />
<% end %>

<%- if devise_sign_in_enabled? %>
<%= link_to "Sign in", new_user_session_path, class: 'button is-extremely-large is-muted is-outlined' %><br />
<%= link_to "Sign up", new_user_registration_path, class: 'button is-extremely-large is-muted is-filled' %><br />
<% end %>
2 changes: 1 addition & 1 deletion app/views/posts/_expanded.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@
<div class="widget">
<div class="widget--header">Why does this post require moderator attention?</div>
<% unless post.locked? %>
<% PostFlagType.where(post_type_id: post.post_type.id).or(PostFlagType.where(post_type_id: nil)).each do |reason| %>
<% PostFlagType.where(post_type_id: [post.post_type.id, nil]).where(active: 1).each do |reason| %>
<div class="widget--body">
<div class="grid">
<div class="grid--cell">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class DisableNeedsAuthorAttentionFlag < ActiveRecord::Migration[7.0]
def up
PostFlagType.unscoped.where(name: "needs author's attention").update_all(active: false)
end

def down
PostFlagType.unscoped.where(name: "needs author's attention").update_all(active: true)
end
end
5 changes: 4 additions & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
Community.all.each do |c|
RequestContext.community = c
post = Post.find_by doc_slug: seed['doc_slug']
if post.present? && PostHistory.where(post: post).count <= 1
if post.present? && PostHistory.where(post: post)
.where.not(post_history_type:
PostHistoryType.find_by(name: 'initial_revision'))
.count.zero?
# post exists, still original version: update post
post.update(seed.merge('community_id' => c.id))
updated += 1
Expand Down
2 changes: 1 addition & 1 deletion db/seeds/post_flag_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
description: >
This question is off-topic or cannot be reasonably answered in its current form and needs revision by its author.
confidential: false
active: true
active: false
post_type_id: <%= Question.post_type_id %>

- name: is a duplicate
Expand Down
33 changes: 33 additions & 0 deletions db/seeds/site_settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,36 @@
category: Integrations
description: >
Load Stripe JS API on all pages instead of just donation pages. May improve security and fraud detection.
- name: RestrictedAccess
value: false
value_type: boolean
category: SiteDetails
description: >
Whether the content of this community should be visible only to users who are signed in.
- name: RestrictedAccessFrontPageText
value: >
<h1>Welcome to our community!</h1>
<p>Please sign in to continue</p>
value_type: text
category: SiteDetails
description: >
This setting only has an effect when RestrictedAccess is enabled.
This is the text that will be displayed on the front page for users who are not signed in. Markdown allowed.
- name: RestrictedAccessHelpPagesPublic
value: true
value_type: boolean
category: SiteDetails
description: >
This setting only has an effect when RestrictedAccess is enabled.
Whether the help pages are publicly accessible.
- name: RestrictedAccessPolicyPagesPublic
value: true
value_type: boolean
category: SiteDetails
description: >
This setting only has an effect when RestrictedAccess is enabled.
Whether the policy pages are publicly accessible (Terms of Service, Privacy Policy, etc.).
2 changes: 1 addition & 1 deletion docker/local-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ cp ./docker/dummy.env ./docker/env
cp ./docker/compose-env .env
cp config/database.docker.yml config/database.yml
cp config/storage.docker.yml config/storage.yml
cp ./.irbrc.sample ./.irbrc
cp ./.sample.irbrc ./.irbrc
Loading

0 comments on commit 24e0a58

Please sign in to comment.