mirror of
https://github.com/discourse/discourse.git
synced 2025-01-06 21:53:44 +08:00
SECURITY: Require groups to be given when inviting to a restricted category. (#6715)
This commit is contained in:
parent
e8b51feceb
commit
cffb3d7976
|
@ -289,7 +289,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
|
|
||||||
model.setProperties({ saving: true, error: false });
|
model.setProperties({ saving: true, error: false });
|
||||||
|
|
||||||
const onerror = function(e) {
|
const onerror = e => {
|
||||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||||
self.set("errorMessage", e.jqXHR.responseJSON.errors[0]);
|
self.set("errorMessage", e.jqXHR.responseJSON.errors[0]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -555,7 +555,7 @@ class TopicsController < ApplicationController
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
guardian.ensure_can_invite_to!(topic, groups)
|
guardian.ensure_can_invite_to!(topic)
|
||||||
group_ids = groups.map(&:id)
|
group_ids = groups.map(&:id)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
@ -568,7 +568,25 @@ class TopicsController < ApplicationController
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
render json: failed_json, status: 422
|
json = failed_json
|
||||||
|
|
||||||
|
unless topic.private_message?
|
||||||
|
group_names = topic.category
|
||||||
|
.visible_group_names(current_user)
|
||||||
|
.where(automatic: false)
|
||||||
|
.pluck(:name)
|
||||||
|
.join(", ")
|
||||||
|
|
||||||
|
if group_names.present?
|
||||||
|
json.merge!(errors: [
|
||||||
|
I18n.t("topic_invite.failed_to_invite",
|
||||||
|
group_names: group_names
|
||||||
|
)
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: json, status: 422
|
||||||
end
|
end
|
||||||
rescue Topic::UserExists => e
|
rescue Topic::UserExists => e
|
||||||
render json: { errors: [e.message] }, status: 422
|
render json: { errors: [e.message] }, status: 422
|
||||||
|
|
|
@ -475,6 +475,10 @@ class Category < ActiveRecord::Base
|
||||||
self.name_lower = name.downcase if self.name
|
self.name_lower = name.downcase if self.name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visible_group_names(user)
|
||||||
|
self.groups.visible_groups(user)
|
||||||
|
end
|
||||||
|
|
||||||
def secure_group_ids
|
def secure_group_ids
|
||||||
if self.read_restricted?
|
if self.read_restricted?
|
||||||
groups.pluck("groups.id")
|
groups.pluck("groups.id")
|
||||||
|
|
|
@ -57,19 +57,6 @@ class Invite < ActiveRecord::Base
|
||||||
InviteRedeemer.new(self, username, name, password, user_custom_fields).redeem unless expired? || destroyed? || !link_valid?
|
InviteRedeemer.new(self, username, name, password, user_custom_fields).redeem unless expired? || destroyed? || !link_valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.extend_permissions(topic, user, invited_by)
|
|
||||||
if topic.private_message?
|
|
||||||
topic.grant_permission_to_user(user.email)
|
|
||||||
elsif topic.category && topic.category.groups.any?
|
|
||||||
if Guardian.new(invited_by).can_invite_via_email?(topic)
|
|
||||||
(topic.category.groups - user.groups).each do |group|
|
|
||||||
group.add(user)
|
|
||||||
GroupActionLogger.new(Discourse.system_user, group).log_add_user_to_group(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.invite_by_email(email, invited_by, topic = nil, group_ids = nil, custom_message = nil)
|
def self.invite_by_email(email, invited_by, topic = nil, group_ids = nil, custom_message = nil)
|
||||||
create_invite_by_email(email, invited_by,
|
create_invite_by_email(email, invited_by,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
|
@ -103,8 +90,10 @@ class Invite < ActiveRecord::Base
|
||||||
lower_email = Email.downcase(email)
|
lower_email = Email.downcase(email)
|
||||||
|
|
||||||
if user = find_user_by_email(lower_email)
|
if user = find_user_by_email(lower_email)
|
||||||
extend_permissions(topic, user, invited_by) if topic
|
raise UserExists.new(I18n.t("invite.user_exists",
|
||||||
raise UserExists.new I18n.t("invite.user_exists", email: lower_email, username: user.username)
|
email: lower_email,
|
||||||
|
username: user.username
|
||||||
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
invite = Invite.with_deleted
|
invite = Invite.with_deleted
|
||||||
|
@ -134,14 +123,10 @@ class Invite < ActiveRecord::Base
|
||||||
|
|
||||||
if group_ids.present?
|
if group_ids.present?
|
||||||
group_ids = group_ids - invite.invited_groups.pluck(:group_id)
|
group_ids = group_ids - invite.invited_groups.pluck(:group_id)
|
||||||
|
|
||||||
group_ids.each do |group_id|
|
group_ids.each do |group_id|
|
||||||
invite.invited_groups.create!(group_id: group_id)
|
invite.invited_groups.create!(group_id: group_id)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
if topic && topic.category && Guardian.new(invited_by).can_invite_to?(topic)
|
|
||||||
group_ids = topic.category.groups.where(automatic: false).pluck(:id) - invite.invited_groups.pluck(:group_id)
|
|
||||||
group_ids.each { |group_id| invite.invited_groups.create!(group_id: group_id) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Jobs.enqueue(:invite_email, invite_id: invite.id) if send_email
|
Jobs.enqueue(:invite_email, invite_id: invite.id) if send_email
|
||||||
|
|
|
@ -840,62 +840,29 @@ class Topic < ActiveRecord::Base
|
||||||
def invite(invited_by, username_or_email, group_ids = nil, custom_message = nil)
|
def invite(invited_by, username_or_email, group_ids = nil, custom_message = nil)
|
||||||
target_user = User.find_by_username_or_email(username_or_email)
|
target_user = User.find_by_username_or_email(username_or_email)
|
||||||
guardian = Guardian.new(invited_by)
|
guardian = Guardian.new(invited_by)
|
||||||
|
is_email = username_or_email =~ /^.+@.+$/
|
||||||
|
|
||||||
if target_user && topic_allowed_users.where(user_id: target_user.id).exists?
|
if target_user
|
||||||
raise UserExists.new(I18n.t("topic_invite.user_exists"))
|
if topic_allowed_users.exists?(user_id: target_user.id)
|
||||||
end
|
raise UserExists.new(I18n.t("topic_invite.user_exists"))
|
||||||
|
|
||||||
return true if target_user && invite_existing_muted?(target_user, invited_by)
|
|
||||||
|
|
||||||
if private_message? && target_user && !guardian.can_send_private_message?(target_user)
|
|
||||||
raise UserExists.new(I18n.t("activerecord.errors.models.topic.attributes.base.cant_send_pm"))
|
|
||||||
end
|
|
||||||
|
|
||||||
if target_user && private_message? && topic_allowed_users.create!(user_id: target_user.id)
|
|
||||||
rate_limit_topic_invitation(invited_by)
|
|
||||||
add_small_action(invited_by, "invited_user", target_user.username)
|
|
||||||
|
|
||||||
create_invite_notification!(
|
|
||||||
target_user,
|
|
||||||
Notification.types[:invited_to_private_message],
|
|
||||||
invited_by.username
|
|
||||||
)
|
|
||||||
|
|
||||||
true
|
|
||||||
elsif username_or_email =~ /^.+@.+$/ && guardian.can_invite_via_email?(self)
|
|
||||||
|
|
||||||
if target_user
|
|
||||||
rate_limit_topic_invitation(invited_by)
|
|
||||||
Invite.extend_permissions(self, target_user, invited_by)
|
|
||||||
|
|
||||||
create_invite_notification!(
|
|
||||||
target_user,
|
|
||||||
Notification.types[:invited_to_topic],
|
|
||||||
invited_by.username
|
|
||||||
)
|
|
||||||
else
|
|
||||||
invite_by_email(invited_by, username_or_email, group_ids, custom_message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
if invite_existing_muted?(target_user, invited_by)
|
||||||
elsif target_user &&
|
return true
|
||||||
rate_limit_topic_invitation(invited_by) &&
|
end
|
||||||
topic_allowed_users.create!(user_id: target_user.id)
|
|
||||||
|
|
||||||
create_invite_notification!(
|
if private_message?
|
||||||
target_user,
|
!!invite_to_private_message(invited_by, target_user, guardian)
|
||||||
Notification.types[:invited_to_topic],
|
else
|
||||||
invited_by.username
|
!!invite_to_topic(invited_by, target_user, group_ids, guardian)
|
||||||
|
end
|
||||||
|
elsif is_email && guardian.can_invite_via_email?(self)
|
||||||
|
!!Invite.invite_by_email(
|
||||||
|
username_or_email, invited_by, self, group_ids, custom_message
|
||||||
)
|
)
|
||||||
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def invite_by_email(invited_by, email, group_ids = nil, custom_message = nil)
|
|
||||||
Invite.invite_by_email(email, invited_by, self, group_ids, custom_message)
|
|
||||||
end
|
|
||||||
|
|
||||||
def invite_existing_muted?(target_user, invited_by)
|
def invite_existing_muted?(target_user, invited_by)
|
||||||
if invited_by.id &&
|
if invited_by.id &&
|
||||||
MutedUser.where(user_id: target_user.id, muted_user_id: invited_by.id)
|
MutedUser.where(user_id: target_user.id, muted_user_id: invited_by.id)
|
||||||
|
@ -1398,6 +1365,55 @@ class Topic < ActiveRecord::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def invite_to_private_message(invited_by, target_user, guardian)
|
||||||
|
if !guardian.can_send_private_message?(target_user)
|
||||||
|
raise UserExists.new(I18n.t(
|
||||||
|
"activerecord.errors.models.topic.attributes.base.cant_send_pm"
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
Topic.transaction do
|
||||||
|
rate_limit_topic_invitation(invited_by)
|
||||||
|
topic_allowed_users.create!(user_id: target_user.id)
|
||||||
|
add_small_action(invited_by, "invited_user", target_user.username)
|
||||||
|
|
||||||
|
create_invite_notification!(
|
||||||
|
target_user,
|
||||||
|
Notification.types[:invited_to_private_message],
|
||||||
|
invited_by.username
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def invite_to_topic(invited_by, target_user, group_ids, guardian)
|
||||||
|
Topic.transaction do
|
||||||
|
rate_limit_topic_invitation(invited_by)
|
||||||
|
|
||||||
|
if group_ids
|
||||||
|
(
|
||||||
|
self.category.groups.where(id: group_ids).where(automatic: false) -
|
||||||
|
target_user.groups.where(automatic: false)
|
||||||
|
).each do |group|
|
||||||
|
if guardian.can_edit_group?(group)
|
||||||
|
group.add(target_user)
|
||||||
|
|
||||||
|
GroupActionLogger
|
||||||
|
.new(invited_by, group)
|
||||||
|
.log_add_user_to_group(target_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Guardian.new(target_user).can_see_topic?(self)
|
||||||
|
create_invite_notification!(
|
||||||
|
target_user,
|
||||||
|
Notification.types[:invited_to_topic],
|
||||||
|
invited_by.username
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update_category_topic_count_by(num)
|
def update_category_topic_count_by(num)
|
||||||
if category_id.present?
|
if category_id.present?
|
||||||
Category.where(['id = ?', category_id]).update_all("topic_count = topic_count " + (num > 0 ? '+' : '') + "#{num}")
|
Category.where(['id = ?', category_id]).update_all("topic_count = topic_count " + (num > 0 ? '+' : '') + "#{num}")
|
||||||
|
|
|
@ -190,6 +190,7 @@ en:
|
||||||
error: "There was an error uploading that file. Please try again later."
|
error: "There was an error uploading that file. Please try again later."
|
||||||
|
|
||||||
topic_invite:
|
topic_invite:
|
||||||
|
failed_to_invite: "The user cannot be invited into this topic without a group membership in either one of the following groups: %{group_names}."
|
||||||
user_exists: "Sorry, that user has already been invited. You may only invite a user to a topic once."
|
user_exists: "Sorry, that user has already been invited. You may only invite a user to a topic once."
|
||||||
|
|
||||||
backup:
|
backup:
|
||||||
|
|
|
@ -265,19 +265,25 @@ class Guardian
|
||||||
|
|
||||||
def can_invite_to?(object, groups = nil)
|
def can_invite_to?(object, groups = nil)
|
||||||
return false unless authenticated?
|
return false unless authenticated?
|
||||||
return true if is_admin?
|
is_topic = object.is_a?(Topic)
|
||||||
|
return true if is_admin? && !is_topic
|
||||||
return false if (SiteSetting.max_invites_per_day.to_i == 0 && !is_staff?)
|
return false if (SiteSetting.max_invites_per_day.to_i == 0 && !is_staff?)
|
||||||
return false unless can_see?(object)
|
return false unless can_see?(object)
|
||||||
return false if groups.present?
|
return false if groups.present?
|
||||||
|
|
||||||
if object.is_a?(Topic) && object.private_message?
|
if is_topic
|
||||||
return false unless SiteSetting.enable_personal_messages?
|
if object.private_message?
|
||||||
return false if object.reached_recipients_limit? && !is_staff?
|
return true if is_admin?
|
||||||
end
|
return false unless SiteSetting.enable_personal_messages?
|
||||||
|
return false if object.reached_recipients_limit? && !is_staff?
|
||||||
|
end
|
||||||
|
|
||||||
if object.is_a?(Topic) && object.category
|
if (category = object.category) && category.read_restricted
|
||||||
if object.category.groups.any?
|
if (groups = category.groups&.where(automatic: false))&.any?
|
||||||
return true if object.category.groups.all? { |g| can_edit_group?(g) }
|
return groups.any? { |g| can_edit_group?(g) } ? true : false
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -550,8 +550,8 @@ describe Guardian do
|
||||||
expect(Guardian.new(user).can_invite_to?(private_topic)).to be_falsey
|
expect(Guardian.new(user).can_invite_to?(private_topic)).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns true for admin on private topic' do
|
it 'returns false for admin on private topic' do
|
||||||
expect(Guardian.new(admin).can_invite_to?(private_topic)).to be_truthy
|
expect(Guardian.new(admin).can_invite_to?(private_topic)).to be(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns true for a group owner' do
|
it 'returns true for a group owner' do
|
||||||
|
@ -562,6 +562,49 @@ describe Guardian do
|
||||||
SiteSetting.enable_personal_messages = false
|
SiteSetting.enable_personal_messages = false
|
||||||
expect(Guardian.new(trust_level_2).can_invite_to?(topic)).to be_truthy
|
expect(Guardian.new(trust_level_2).can_invite_to?(topic)).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'for a private category for automatic and non-automatic group' do
|
||||||
|
let(:automatic_group) { Fabricate(:group, automatic: true) }
|
||||||
|
let(:group) { Fabricate(:group) }
|
||||||
|
|
||||||
|
let(:category) do
|
||||||
|
Fabricate(:category, read_restricted: true).tap do |category|
|
||||||
|
category.groups << automatic_group
|
||||||
|
category.groups << group
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:topic) { Fabricate(:topic, category: category) }
|
||||||
|
|
||||||
|
it 'should return true for an admin user' do
|
||||||
|
expect(Guardian.new(admin).can_invite_to?(topic)).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true for a group owner' do
|
||||||
|
expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false for a normal user' do
|
||||||
|
expect(Guardian.new(user).can_invite_to?(topic)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'for a private category for automatic groups' do
|
||||||
|
let(:group) { Fabricate(:group, automatic: true) }
|
||||||
|
|
||||||
|
let(:category) do
|
||||||
|
Fabricate(:private_category, group: group, read_restricted: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:group_owner) { Fabricate(:user).tap { |user| group.add_owner(user) } }
|
||||||
|
let(:topic) { Fabricate(:topic, category: category) }
|
||||||
|
|
||||||
|
it 'should return false for all type of users' do
|
||||||
|
expect(Guardian.new(admin).can_invite_to?(topic)).to eq(false)
|
||||||
|
expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(false)
|
||||||
|
expect(Guardian.new(user).can_invite_to?(topic)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "private messages" do
|
describe "private messages" do
|
||||||
|
|
|
@ -59,37 +59,38 @@ describe Invite do
|
||||||
|
|
||||||
context 'email' do
|
context 'email' do
|
||||||
it 'enqueues a job to email the invite' do
|
it 'enqueues a job to email the invite' do
|
||||||
Jobs.expects(:enqueue).with(:invite_email, has_key(:invite_id))
|
expect do
|
||||||
topic.invite_by_email(inviter, iceking)
|
Invite.invite_by_email(iceking, inviter, topic)
|
||||||
|
end.to change { Jobs::InviteEmail.jobs.size }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'destroyed' do
|
context 'destroyed' do
|
||||||
it "can invite the same user after their invite was destroyed" do
|
it "can invite the same user after their invite was destroyed" do
|
||||||
invite = topic.invite_by_email(inviter, iceking)
|
Invite.invite_by_email(iceking, inviter, topic).destroy!
|
||||||
invite.destroy
|
invite = Invite.invite_by_email(iceking, inviter, topic)
|
||||||
invite = topic.invite_by_email(inviter, iceking)
|
|
||||||
expect(invite).to be_present
|
expect(invite).to be_present
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'after created' do
|
context 'after created' do
|
||||||
before do
|
let(:invite) { Invite.invite_by_email(iceking, inviter, topic) }
|
||||||
@invite = topic.invite_by_email(inviter, iceking)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'belongs to the topic' do
|
it 'belongs to the topic' do
|
||||||
expect(topic.invites).to eq([@invite])
|
expect(topic.invites).to eq([invite])
|
||||||
expect(@invite.topics).to eq([topic])
|
expect(invite.topics).to eq([topic])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when added by another user' do
|
context 'when added by another user' do
|
||||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||||
let(:new_invite) { topic.invite_by_email(coding_horror, iceking) }
|
|
||||||
|
let(:new_invite) do
|
||||||
|
Invite.invite_by_email(iceking, coding_horror, topic)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns a different invite' do
|
it 'returns a different invite' do
|
||||||
expect(new_invite).not_to eq(@invite)
|
expect(new_invite).not_to eq(invite)
|
||||||
expect(new_invite.invite_key).not_to eq(@invite.invite_key)
|
expect(new_invite.invite_key).not_to eq(invite.invite_key)
|
||||||
expect(new_invite.topics).to eq([topic])
|
expect(new_invite.topics).to eq([topic])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -97,24 +98,36 @@ describe Invite do
|
||||||
|
|
||||||
context 'when adding a duplicate' do
|
context 'when adding a duplicate' do
|
||||||
it 'returns the original invite' do
|
it 'returns the original invite' do
|
||||||
expect(topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')).to eq(@invite)
|
%w{
|
||||||
expect(topic.invite_by_email(inviter, 'iceking@ADVENTURETIME.ooo')).to eq(@invite)
|
iceking@adventuretime.ooo
|
||||||
expect(topic.invite_by_email(inviter, 'ICEKING@adventuretime.ooo')).to eq(@invite)
|
iceking@ADVENTURETIME.ooo
|
||||||
|
ICEKING@adventuretime.ooo
|
||||||
|
}.each do |email|
|
||||||
|
expect(Invite.invite_by_email(
|
||||||
|
email, inviter, topic
|
||||||
|
)).to eq(invite)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates timestamp of existing invite' do
|
it 'updates timestamp of existing invite' do
|
||||||
@invite.created_at = 10.days.ago
|
invite.update!(created_at: 10.days.ago)
|
||||||
@invite.save
|
|
||||||
resend_invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
resend_invite = Invite.invite_by_email(
|
||||||
|
'iceking@adventuretime.ooo', inviter, topic
|
||||||
|
)
|
||||||
|
|
||||||
expect(resend_invite.created_at).to be_within(1.minute).of(Time.zone.now)
|
expect(resend_invite.created_at).to be_within(1.minute).of(Time.zone.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a new invite if the other has expired' do
|
it 'returns a new invite if the other has expired' do
|
||||||
SiteSetting.invite_expiry_days = 1
|
SiteSetting.invite_expiry_days = 1
|
||||||
@invite.created_at = 2.days.ago
|
invite.update!(created_at: 2.days.ago)
|
||||||
@invite.save
|
|
||||||
new_invite = topic.invite_by_email(inviter, 'iceking@adventuretime.ooo')
|
new_invite = Invite.invite_by_email(
|
||||||
expect(new_invite).not_to eq(@invite)
|
'iceking@adventuretime.ooo', inviter, topic
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(new_invite).not_to eq(invite)
|
||||||
expect(new_invite).not_to be_expired
|
expect(new_invite).not_to be_expired
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -123,64 +136,24 @@ describe Invite do
|
||||||
let!(:another_topic) { Fabricate(:topic, user: topic.user) }
|
let!(:another_topic) { Fabricate(:topic, user: topic.user) }
|
||||||
|
|
||||||
it 'should be the same invite' do
|
it 'should be the same invite' do
|
||||||
@new_invite = another_topic.invite_by_email(inviter, iceking)
|
new_invite = Invite.invite_by_email(iceking, inviter, another_topic)
|
||||||
expect(@new_invite).to eq(@invite)
|
expect(new_invite).to eq(invite)
|
||||||
expect(another_topic.invites).to eq([@invite])
|
expect(another_topic.invites).to eq([invite])
|
||||||
expect(@invite.topics).to match_array([topic, another_topic])
|
expect(invite.topics).to match_array([topic, another_topic])
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'to a group-private topic' do
|
|
||||||
let(:group) { Fabricate(:group) }
|
|
||||||
let(:private_category) { Fabricate(:private_category, group: group) }
|
|
||||||
let(:group_private_topic) { Fabricate(:topic, category: private_category) }
|
|
||||||
let(:inviter) { group_private_topic.user }
|
|
||||||
|
|
||||||
before do
|
|
||||||
group.add_owner(inviter)
|
|
||||||
@invite = group_private_topic.invite_by_email(inviter, iceking)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should add the groups to the invite' do
|
|
||||||
expect(@invite.groups).to eq([group])
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when duplicated' do
|
|
||||||
it 'should not duplicate the groups' do
|
|
||||||
expect(group_private_topic.invite_by_email(inviter, iceking)).to eq(@invite)
|
|
||||||
expect(@invite.groups).to eq([group])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'verifies that inviter is authorized to invite user to a topic' do
|
|
||||||
tl2_user = Fabricate(:user, trust_level: 2)
|
|
||||||
|
|
||||||
invite = group_private_topic.invite_by_email(tl2_user, 'foo@bar.com')
|
|
||||||
expect(invite.groups.count).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'automatic groups' do
|
|
||||||
it 'should not add invited user to automatic groups' do
|
|
||||||
group.update!(automatic: true)
|
|
||||||
expect(group_private_topic.invite_by_email(Fabricate(:admin), iceking).groups.count).to eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'an existing user' do
|
context 'an existing user' do
|
||||||
let(:topic) { Fabricate(:topic, category_id: nil, archetype: 'private_message') }
|
let(:topic) { Fabricate(:topic, category_id: nil, archetype: 'private_message') }
|
||||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
# doesn't create an invite
|
expect do
|
||||||
expect { topic.invite_by_email(topic.user, coding_horror.email) }.to raise_error(Invite::UserExists)
|
Invite.invite_by_email(coding_horror.email, topic.user, topic)
|
||||||
|
end.to raise_error(Invite::UserExists)
|
||||||
# gives the user permission to access the topic
|
|
||||||
expect(topic.allowed_users.include?(coding_horror)).to eq(true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -353,7 +326,6 @@ describe Invite do
|
||||||
it 'adds the user to the topic_users of the first topic' do
|
it 'adds the user to the topic_users of the first topic' do
|
||||||
expect(another_topic.invite(another_tl2_user, user.username)).to be_truthy # invited via username
|
expect(another_topic.invite(another_tl2_user, user.username)).to be_truthy # invited via username
|
||||||
expect(topic.allowed_users.include?(user)).to eq(true)
|
expect(topic.allowed_users.include?(user)).to eq(true)
|
||||||
expect(another_topic.allowed_users.include?(user)).to eq(true)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -608,35 +608,85 @@ describe Topic do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'public topic' do
|
describe 'public topic' do
|
||||||
def expect_the_right_notification_to_be_created
|
def expect_the_right_notification_to_be_created(inviter, invitee)
|
||||||
notification = Notification.last
|
notification = Notification.last
|
||||||
|
|
||||||
expect(notification.notification_type)
|
expect(notification.notification_type)
|
||||||
.to eq(Notification.types[:invited_to_topic])
|
.to eq(Notification.types[:invited_to_topic])
|
||||||
|
|
||||||
expect(notification.user).to eq(another_user)
|
expect(notification.user).to eq(invitee)
|
||||||
expect(notification.topic).to eq(topic)
|
expect(notification.topic).to eq(topic)
|
||||||
|
|
||||||
notification_data = JSON.parse(notification.data)
|
notification_data = JSON.parse(notification.data)
|
||||||
|
|
||||||
expect(notification_data["topic_title"]).to eq(topic.title)
|
expect(notification_data["topic_title"]).to eq(topic.title)
|
||||||
expect(notification_data["display_username"]).to eq(user.username)
|
expect(notification_data["display_username"]).to eq(inviter.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'by username' do
|
describe 'by username' do
|
||||||
it 'should invite user into a topic' do
|
it 'should invite user into a topic' do
|
||||||
topic.invite(user, another_user.username)
|
topic.invite(user, another_user.username)
|
||||||
|
expect_the_right_notification_to_be_created(user, another_user)
|
||||||
expect(topic.reload.allowed_users.last).to eq(another_user)
|
|
||||||
expect_the_right_notification_to_be_created
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'by email' do
|
describe 'by email' do
|
||||||
it 'should be able to invite a user' do
|
it 'should be able to invite a user' do
|
||||||
expect(topic.invite(user, another_user.email)).to eq(true)
|
expect(topic.invite(user, another_user.email)).to eq(true)
|
||||||
expect(topic.reload.allowed_users.last).to eq(another_user)
|
expect_the_right_notification_to_be_created(user, another_user)
|
||||||
expect_the_right_notification_to_be_created
|
end
|
||||||
|
|
||||||
|
describe 'when topic belongs to a private category' do
|
||||||
|
let(:group) { Fabricate(:group) }
|
||||||
|
|
||||||
|
let(:category) do
|
||||||
|
Fabricate(:category, groups: [group]).tap do |category|
|
||||||
|
category.set_permissions(group => :full)
|
||||||
|
category.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:topic) { Fabricate(:topic, category: category) }
|
||||||
|
let(:inviter) { Fabricate(:user).tap { |user| group.add_owner(user) } }
|
||||||
|
let(:invitee) { Fabricate(:user) }
|
||||||
|
|
||||||
|
describe 'as a group owner' do
|
||||||
|
it 'should be able to invite a user' do
|
||||||
|
expect do
|
||||||
|
expect(topic.invite(inviter, invitee.email, [group.id]))
|
||||||
|
.to eq(true)
|
||||||
|
end.to change { Notification.count } &
|
||||||
|
change { GroupHistory.count }
|
||||||
|
|
||||||
|
expect_the_right_notification_to_be_created(inviter, invitee)
|
||||||
|
|
||||||
|
group_history = GroupHistory.last
|
||||||
|
|
||||||
|
expect(group_history.acting_user).to eq(inviter)
|
||||||
|
expect(group_history.target_user).to eq(invitee)
|
||||||
|
|
||||||
|
expect(group_history.action).to eq(
|
||||||
|
GroupHistory.actions[:add_user_to_group]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when group ids are not given' do
|
||||||
|
it 'should not invite the user' do
|
||||||
|
expect do
|
||||||
|
expect(topic.invite(inviter, invitee.email)).to eq(false)
|
||||||
|
end.to_not change { Notification.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'as a normal user' do
|
||||||
|
it 'should not be able to invite a user' do
|
||||||
|
expect do
|
||||||
|
expect(topic.invite(Fabricate(:user), invitee.email, [group.id]))
|
||||||
|
.to eq(false)
|
||||||
|
end.to_not change { Notification.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "for a muted topic" do
|
context "for a muted topic" do
|
||||||
|
@ -2004,34 +2054,6 @@ describe Topic do
|
||||||
expect(topic.save).to eq(true)
|
expect(topic.save).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'invite by group manager' do
|
|
||||||
let(:group_manager) { Fabricate(:user) }
|
|
||||||
let(:group) { Fabricate(:group).tap { |g| g.add_owner(group_manager) } }
|
|
||||||
let(:private_category) { Fabricate(:private_category, group: group) }
|
|
||||||
let(:group_private_topic) { Fabricate(:topic, category: private_category, user: group_manager) }
|
|
||||||
|
|
||||||
context 'to an email' do
|
|
||||||
let(:randolph) { 'randolph@duke.ooo' }
|
|
||||||
|
|
||||||
it "should attach group to the invite" do
|
|
||||||
group_private_topic.invite(group_manager, randolph)
|
|
||||||
expect(Invite.last.groups).to eq([group])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# should work for an existing user - give access, send notification
|
|
||||||
context 'to an existing user' do
|
|
||||||
let(:walter) { Fabricate(:walter_white) }
|
|
||||||
|
|
||||||
it "should add user to the group" do
|
|
||||||
expect(Guardian.new(walter).can_see?(group_private_topic)).to be_falsey
|
|
||||||
group_private_topic.invite(group_manager, walter.email)
|
|
||||||
expect(walter.groups).to include(group)
|
|
||||||
expect(Guardian.new(walter).can_see?(group_private_topic)).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "Correctly sets #message_archived?" do
|
it "Correctly sets #message_archived?" do
|
||||||
topic = Fabricate(:private_message_topic)
|
topic = Fabricate(:private_message_topic)
|
||||||
user = topic.user
|
user = topic.user
|
||||||
|
|
|
@ -215,9 +215,13 @@ describe InvitesController do
|
||||||
|
|
||||||
context 'with a deleted invite' do
|
context 'with a deleted invite' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
|
||||||
|
let(:invite) do
|
||||||
|
Invite.invite_by_email("iceking@adventuretime.ooo", topic.user, topic)
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
invite.destroy
|
invite.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
it "redirects to the root" do
|
it "redirects to the root" do
|
||||||
|
@ -233,7 +237,9 @@ describe InvitesController do
|
||||||
|
|
||||||
context 'with a valid invite id' do
|
context 'with a valid invite id' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
let(:invite) do
|
||||||
|
Invite.invite_by_email("iceking@adventuretime.ooo", topic.user, topic)
|
||||||
|
end
|
||||||
|
|
||||||
it 'redeems the invite' do
|
it 'redeems the invite' do
|
||||||
put "/invites/show/#{invite.invite_key}.json"
|
put "/invites/show/#{invite.invite_key}.json"
|
||||||
|
@ -323,7 +329,11 @@ describe InvitesController do
|
||||||
|
|
||||||
context 'new registrations are disabled' do
|
context 'new registrations are disabled' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
|
||||||
|
let(:invite) do
|
||||||
|
Invite.invite_by_email("iceking@adventuretime.ooo", topic.user, topic)
|
||||||
|
end
|
||||||
|
|
||||||
before { SiteSetting.allow_new_registrations = false }
|
before { SiteSetting.allow_new_registrations = false }
|
||||||
|
|
||||||
it "doesn't redeem the invite" do
|
it "doesn't redeem the invite" do
|
||||||
|
@ -338,7 +348,11 @@ describe InvitesController do
|
||||||
|
|
||||||
context 'user is already logged in' do
|
context 'user is already logged in' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
|
||||||
|
let(:invite) do
|
||||||
|
Invite.invite_by_email("iceking@adventuretime.ooo", topic.user, topic)
|
||||||
|
end
|
||||||
|
|
||||||
let!(:user) { sign_in(Fabricate(:user)) }
|
let!(:user) { sign_in(Fabricate(:user)) }
|
||||||
|
|
||||||
it "doesn't redeem the invite" do
|
it "doesn't redeem the invite" do
|
||||||
|
|
|
@ -2010,14 +2010,46 @@ RSpec.describe TopicsController do
|
||||||
let(:recipient) { 'jake@adventuretime.ooo' }
|
let(:recipient) { 'jake@adventuretime.ooo' }
|
||||||
|
|
||||||
it "should attach group to the invite" do
|
it "should attach group to the invite" do
|
||||||
|
|
||||||
post "/t/#{group_private_topic.id}/invite.json", params: {
|
post "/t/#{group_private_topic.id}/invite.json", params: {
|
||||||
user: recipient
|
user: recipient,
|
||||||
|
group_ids: "#{group.id},123"
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(Invite.find_by(email: recipient).groups).to eq([group])
|
expect(Invite.find_by(email: recipient).groups).to eq([group])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'when group is available to automatic groups only' do
|
||||||
|
before do
|
||||||
|
group.update!(automatic: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/t/#{group_private_topic.id}/invite.json", params: {
|
||||||
|
user: Fabricate(:user)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when user is not part of the required group' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/t/#{group_private_topic.id}/invite.json", params: {
|
||||||
|
user: Fabricate(:user)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
|
||||||
|
response_body = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(response_body["errors"]).to eq([
|
||||||
|
I18n.t("topic_invite.failed_to_invite",
|
||||||
|
group_names: group.name
|
||||||
|
)
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when topic id is invalid' do
|
describe 'when topic id is invalid' do
|
||||||
|
|
Loading…
Reference in New Issue
Block a user