diff --git a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs index 9396e7f90f5..2079edb3e28 100644 --- a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs @@ -145,5 +145,9 @@ {{/if}} {{/load-more}} + {{else}} +
+ {{model.error}} +
{{/if}} {{/d-section}} diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index cc8666133ce..24a318e2fbe 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -782,3 +782,9 @@ .timezone-input { margin-bottom: 0.5em; } + +.user-invites-page { + .invite-error { + grid-column: 1 / span 2; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ebbf9db736b..aa82acb8a64 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -11,7 +11,7 @@ class UsersController < ApplicationController :update_second_factor, :create_second_factor_backup, :select_avatar, :notification_level, :revoke_auth_token, :register_second_factor_security_key, :create_second_factor_security_key, :feature_topic, :clear_featured_topic, - :bookmarks, :invited + :bookmarks, :invited, :invite_links ] skip_before_action :check_xhr, only: [ @@ -369,57 +369,87 @@ class UsersController < ApplicationController end def invited - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + offset = params[:offset].to_i || 0 + filter_by = params[:filter] || "redeemed" + inviter = fetch_user_from_params(include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts) - offset = params[:offset].to_i || 0 - filter_by = params[:filter] || "redeemed" - inviter = fetch_user_from_params(include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts) + invites = if guardian.can_see_invite_details?(inviter) && filter_by == "pending" + Invite.find_pending_invites_from(inviter, offset) + elsif filter_by == "redeemed" + Invite.find_redeemed_invites_from(inviter, offset) + else + [] + end - invites = if guardian.can_see_invite_details?(inviter) && filter_by == "pending" - Invite.find_pending_invites_from(inviter, offset) - elsif filter_by == "redeemed" - Invite.find_redeemed_invites_from(inviter, offset) + show_emails = guardian.can_see_invite_emails?(inviter) + if params[:search].present? && invites.present? + filter_sql = '(LOWER(users.username) LIKE :filter)' + filter_sql = '(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)' if show_emails + invites = invites.where(filter_sql, filter: "%#{params[:search].downcase}%") + end + + render json: MultiJson.dump(InvitedSerializer.new( + OpenStruct.new(invite_list: invites.to_a, show_emails: show_emails, inviter: inviter, type: filter_by), + scope: guardian, + root: false + )) else - [] - end + if current_user&.staff? + message = if SiteSetting.enable_sso + I18n.t("invite.disabled_errors.sso_enabled") + elsif !SiteSetting.enable_local_logins + I18n.t("invite.disabled_errors.local_logins_disabled") + end - show_emails = guardian.can_see_invite_emails?(inviter) - if params[:search].present? && invites.present? - filter_sql = '(LOWER(users.username) LIKE :filter)' - filter_sql = '(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)' if show_emails - invites = invites.where(filter_sql, filter: "%#{params[:search].downcase}%") + render_invite_error(message) + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end end - - render json: MultiJson.dump(InvitedSerializer.new( - OpenStruct.new(invite_list: invites.to_a, show_emails: show_emails, inviter: inviter, type: filter_by), - scope: guardian, - root: false - )) end def invite_links - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + guardian.ensure_can_see_invite_details!(inviter) - inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) - guardian.ensure_can_see_invite_details!(inviter) + offset = params[:offset].to_i || 0 + invites = Invite.find_links_invites_from(inviter, offset) - offset = params[:offset].to_i || 0 - invites = Invite.find_links_invites_from(inviter, offset) + render json: MultiJson.dump(invites: serialize_data(invites.to_a, InviteLinkSerializer), can_see_invite_details: guardian.can_see_invite_details?(inviter)) + else + if current_user&.staff? + message = if SiteSetting.enable_sso + I18n.t("invite.disabled_errors.sso_enabled") + elsif !SiteSetting.enable_local_logins + I18n.t("invite.disabled_errors.local_logins_disabled") + end - render json: MultiJson.dump(invites: serialize_data(invites.to_a, InviteLinkSerializer), can_see_invite_details: guardian.can_see_invite_details?(inviter)) + render_invite_error(message) + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end + end end def invited_count - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) - inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + pending_count = Invite.find_pending_invites_count(inviter) + redeemed_count = Invite.find_redeemed_invites_count(inviter) + links_count = Invite.find_links_invites_count(inviter) - pending_count = Invite.find_pending_invites_count(inviter) - redeemed_count = Invite.find_redeemed_invites_count(inviter) - links_count = Invite.find_links_invites_count(inviter) - - render json: { counts: { pending: pending_count, redeemed: redeemed_count, links: links_count, - total: (pending_count.to_i + redeemed_count.to_i) } } + render json: { counts: { pending: pending_count, redeemed: redeemed_count, links: links_count, + total: (pending_count.to_i + redeemed_count.to_i) } } + else + if current_user&.staff? + render json: { counts: 0 } + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end + end end def is_local_username @@ -1639,4 +1669,12 @@ class UsersController < ApplicationController def summary_cache_key(user) "user_summary:#{user.id}:#{current_user ? current_user.id : 0}" end + + def render_invite_error(message) + render json: { + invites: [], + can_see_invite_details: false, + error: message + } + end end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index b87d9e9ffc9..2458646d70c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -218,6 +218,10 @@ en: user_exists: "There's no need to invite %{email}, they already have an account!" confirm_email: "

You’re almost done! We sent an activation mail to your email address. Please follow the instructions in the mail to activate your account.

If it doesn’t arrive, check your spam folder.

" cant_invite_to_group: "You are not allowed to invite users to specified group(s). Make sure you are owner of the group(s) you are trying to invite to." + disabled_errors: + sso_enabled: "Invites are disabled because SSO is enabled." + local_logins_disabled: "Invites are disabled because the 'enable local logins' setting is disabled." + invalid_access: "You are not permitted to view the requested resource." bulk_invite: file_should_be_csv: "The uploaded file should be of csv format." diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index fc35cc08967..1358355dd86 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -1556,7 +1556,7 @@ describe UsersController do it "fails for anonymous users" do user = Fabricate(:user) get "/u/#{user.username}/invited_count.json" - expect(response.status).to eq(403) + expect(response.status).to eq(422) end it "works for users who can see invites" do @@ -1713,7 +1713,7 @@ describe UsersController do end get "/u/#{inviter.username}/invited/pending.json" - expect(response.status).to eq(403) + expect(response.status).to eq(422) end end end @@ -1760,6 +1760,21 @@ describe UsersController do expect(response.status).to eq(403) end end + + context 'when local logins are disabled' do + it 'explains why invites are disabled to staff users' do + SiteSetting.enable_local_logins = false + inviter = sign_in(Fabricate(:admin)) + Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required]) + + get "/u/#{inviter.username}/invite_links.json" + expect(response.status).to eq(200) + + expect(response.parsed_body['error']).to include(I18n.t( + 'invite.disabled_errors.local_logins_disabled' + )) + end + end end end end