discourse/app/controllers/invites_controller.rb
David Taylor 821bb1e8cb
FEATURE: Rename 'Discourse SSO' to DiscourseConnect (#11978)
The 'Discourse SSO' protocol is being rebranded to DiscourseConnect. This should help to reduce confusion when 'SSO' is used in the generic sense.

This commit aims to:
- Rename `sso_` site settings. DiscourseConnect specific ones are prefixed `discourse_connect_`. Generic settings are prefixed `auth_`
- Add (server-side-only) backwards compatibility for the old setting names, with deprecation notices
- Copy `site_settings` database records to the new names
- Rename relevant translation keys
- Update relevant translations

This commit does **not** aim to:
- Rename any Ruby classes or methods. This might be done in a future commit
- Change any URLs. This would break existing integrations
- Make any changes to the protocol. This would break existing integrations
- Change any functionality. Further normalization across DiscourseConnect and other auth methods will be done separately

The risks are:
- There is no backwards compatibility for site settings on the client-side. Accessing auth-related site settings in Javascript is fairly rare, and an error on the client side would not be security-critical.
- If a plugin is monkey-patching parts of the auth process, changes to locale keys could cause broken error messages. This should also be unlikely. The old site setting names remain functional, so security-related overrides will remain working.

A follow-up commit will be made with a post-deploy migration to delete the old `site_settings` rows.
2021-02-08 10:04:33 +00:00

286 lines
8.8 KiB
Ruby

# frozen_string_literal: true
class InvitesController < ApplicationController
requires_login only: [
:destroy, :create, :create_invite_link, :rescind_all_invites,
:resend_invite, :resend_all_invites, :upload_csv
]
skip_before_action :check_xhr, except: [:perform_accept_invitation]
skip_before_action :preload_json, except: [:show]
skip_before_action :redirect_to_login_if_required
before_action :ensure_new_registrations_allowed, only: [:show, :perform_accept_invitation]
before_action :ensure_not_logged_in, only: [:show, :perform_accept_invitation]
def show
expires_now
invite = Invite.find_by(invite_key: params[:id])
if invite.present? && !invite.expired? && !invite.redeemed?
store_preloaded("invite_info", MultiJson.dump(
invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false),
email: invite.email,
username: UserNameSuggester.suggest(invite.email),
is_invite_link: invite.is_invite_link?)
)
render layout: 'application'
else
flash.now[:error] = if invite.present? && invite.expired?
I18n.t('invite.expired', base_url: Discourse.base_url)
elsif invite.present? && invite.redeemed?
I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url)
else
I18n.t('invite.not_found', base_url: Discourse.base_url)
end
render layout: 'no_ember'
end
end
def perform_accept_invitation
params.require(:id)
params.permit(:email, :username, :name, :password, :timezone, user_custom_fields: {})
invite = Invite.find_by(invite_key: params[:id])
if invite.present?
begin
user = if invite.is_invite_link?
invite.redeem_invite_link(email: params[:email], username: params[:username], name: params[:name], password: params[:password], user_custom_fields: params[:user_custom_fields], ip_address: request.remote_ip)
else
invite.redeem(username: params[:username], name: params[:name], password: params[:password], user_custom_fields: params[:user_custom_fields], ip_address: request.remote_ip)
end
if user.present?
log_on_user(user) if user.active?
user.update_timezone_if_missing(params[:timezone])
post_process_invite(user)
response = { success: true }
else
response = { success: false, message: I18n.t('invite.not_found_json') }
end
if user.present? && user.active?
topic = invite.topics.first
response[:redirect_to] = topic.present? ? path("#{topic.relative_url}") : path("/")
elsif user.present?
response[:message] = I18n.t('invite.confirm_email')
end
render json: response
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
render json: {
success: false,
errors: e.record&.errors&.to_hash || {},
message: I18n.t('invite.error_message')
}
rescue Invite::UserExists => e
render json: { success: false, message: [e.message] }
end
else
render json: { success: false, message: I18n.t('invite.not_found_json') }
end
end
def create
params.require(:email)
groups = Group.lookup_groups(
group_ids: params[:group_ids],
group_names: params[:group_names]
)
guardian.ensure_can_invite_to_forum!(groups)
group_ids = groups.map(&:id)
if Invite.exists?(email: params[:email])
return render json: failed_json, status: 422
end
begin
if Invite.invite_by_email(params[:email], current_user, nil, group_ids, params[:custom_message])
render json: success_json
else
render json: failed_json, status: 422
end
rescue Invite::UserExists, ActiveRecord::RecordInvalid => e
render json: { errors: [e.message] }, status: 422
end
end
def create_invite_link
params.permit(:email, :max_redemptions_allowed, :expires_at, :group_ids, :group_names, :topic_id)
is_single_invite = params[:email].present?
unless is_single_invite
guardian.ensure_can_send_invite_links!(current_user)
end
groups = Group.lookup_groups(
group_ids: params[:group_ids],
group_names: params[:group_names]
)
if !guardian.can_invite_to_forum?(groups)
raise StandardError.new I18n.t("invite.cant_invite_to_group")
end
group_ids = groups.map(&:id)
if is_single_invite
invite_exists = Invite.exists?(email: params[:email], invited_by_id: current_user.id)
if invite_exists && !guardian.can_send_multiple_invites?(current_user)
return render json: failed_json, status: 422
end
if params[:topic_id].present?
topic = Topic.find_by(id: params[:topic_id])
if topic.present?
guardian.ensure_can_invite_to!(topic)
else
raise Discourse::InvalidParameters.new(:topic_id)
end
end
end
invite_link = if is_single_invite
Invite.generate_single_use_invite_link(params[:email], current_user, topic, group_ids)
else
Invite.generate_multiple_use_invite_link(
invited_by: current_user,
max_redemptions_allowed: params[:max_redemptions_allowed],
expires_at: params[:expires_at],
group_ids: group_ids
)
end
if invite_link.present?
render_json_dump(invite_link)
else
render json: failed_json, status: 422
end
rescue => e
render json: { errors: [e.message] }, status: 422
end
def destroy
params.require(:id)
invite = Invite.find_by(invited_by_id: current_user.id, id: params[:id])
raise Discourse::InvalidParameters.new(:id) if invite.blank?
invite.trash!(current_user)
render json: success_json
end
def rescind_all_invites
guardian.ensure_can_rescind_all_invites!(current_user)
Invite.rescind_all_expired_invites_from(current_user)
render json: success_json
end
def resend_invite
params.require(:email)
RateLimiter.new(current_user, "resend-invite-per-hour", 10, 1.hour).performed!
invite = Invite.find_by(invited_by_id: current_user.id, email: params[:email])
raise Discourse::InvalidParameters.new(:email) if invite.blank?
invite.resend_invite
render json: success_json
rescue RateLimiter::LimitExceeded
render_json_error(I18n.t("rate_limiter.slow_down"))
end
def resend_all_invites
guardian.ensure_can_resend_all_invites!(current_user)
Invite.resend_all_invites_from(current_user.id)
render json: success_json
end
def upload_csv
require 'csv'
guardian.ensure_can_bulk_invite_to_forum!(current_user)
hijack do
begin
file = params[:file] || params[:files].first
count = 0
invites = []
max_bulk_invites = SiteSetting.max_bulk_invites
CSV.foreach(file.tempfile) do |row|
count += 1
invites.push(email: row[0], groups: row[1], topic_id: row[2]) if row[0].present?
break if count >= max_bulk_invites
end
if invites.present?
Jobs.enqueue(:bulk_invite, invites: invites, current_user_id: current_user.id)
if count >= max_bulk_invites
render json: failed_json.merge(errors: [I18n.t("bulk_invite.max_rows", max_bulk_invites: max_bulk_invites)]), status: 422
else
render json: success_json
end
else
render json: failed_json.merge(errors: [I18n.t("bulk_invite.error")]), status: 422
end
rescue
render json: failed_json.merge(errors: [I18n.t("bulk_invite.error")]), status: 422
end
end
end
def fetch_username
params.require(:username)
params[:username]
end
def fetch_email
params.require(:email)
params[:email]
end
def ensure_new_registrations_allowed
unless SiteSetting.allow_new_registrations
flash[:error] = I18n.t('login.new_registrations_disabled')
render layout: 'no_ember'
false
end
end
def ensure_not_logged_in
if current_user
flash[:error] = I18n.t("login.already_logged_in")
render layout: 'no_ember'
false
end
end
private
def post_process_invite(user)
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
Group.refresh_automatic_groups!(:admins, :moderators, :staff) if user.staff?
if user.has_password?
send_activation_email(user) unless user.active
elsif !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins
Jobs.enqueue(:invite_password_instructions_email, username: user.username)
end
end
def send_activation_email(user)
email_token = user.email_tokens.create!(email: user.email)
Jobs.enqueue(:critical_user_email,
type: :signup,
user_id: user.id,
email_token: email_token.token
)
end
end